Search code examples
nettymultipart

HTTP Multipart Response Netty


This is a duplicate of this question and I understand the accepted answer for that. But I wonder is there some inbuilt capability to send back multipart response message in Netty. I came across this class HttpPostRequestEncoder which seems to do the job for creating Multipart POST requests. Is there something equivalent for Response as well?

It would be easy to create an equivalent logic similar to HttpPostRequestEncoder and make it work for responses as well, but I wonder maybe there is something already in Netty I am not aware of.


Solution

  • I believe you're right, there is no such code to make a Multipart response. But I think it could be achived quite easily by extending the HttpPostRequestEncoder by adding a similar method than finalizeRequest() to finalize an HttpResponse.

    https://github.com/netty/netty/blob/1529ef1794e0a6654fa4334fd979b769d6940e61/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java#L736

    For instance (not tested), adding the following method could be at least a good start:

    /**
     * Finalize the response by preparing the Header in the response and returns the response ready to be sent.<br>
     * Once finalized, no data must be added.<br>
     * If the response does not need chunk (isChunked() == false), this response is the only object to send to the remote
     * server.
     *
     * @return the response object (chunked or not according to size of body)
     * @throws ErrorDataEncoderException
     *             if the encoding is in error or if the finalize were already done
     */
    public HttpResponse finalizeResponse(HttpResponseStatus status) throws ErrorDataEncoderException {
        // Finalize the multipartHttpDatas
        if (!headerFinalized) {
            if (isMultipart) {
                InternalAttribute internal = new InternalAttribute(charset);
                if (duringMixedMode) {
                    internal.addValue("\r\n--" + multipartMixedBoundary + "--");
                }
                internal.addValue("\r\n--" + multipartDataBoundary + "--\r\n");
                multipartHttpDatas.add(internal);
                multipartMixedBoundary = null;
                currentFileUpload = null;
                duringMixedMode = false;
                globalBodySize += internal.size();
            }
            headerFinalized = true;
        } else {
            throw new ErrorDataEncoderException("Header already encoded");
        }
    
        HttpHeaders headers = request.headers();
        List<String> contentTypes = headers.getAll(HttpHeaderNames.CONTENT_TYPE);
        List<String> transferEncoding = headers.getAll(HttpHeaderNames.TRANSFER_ENCODING);
        if (contentTypes != null) {
            headers.remove(HttpHeaderNames.CONTENT_TYPE);
            for (String contentType : contentTypes) {
                // "multipart/form-data; boundary=--89421926422648"
                String lowercased = contentType.toLowerCase();
                if (lowercased.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA.toString()) ||
                        lowercased.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())) {
                    // ignore
                } else {
                    headers.add(HttpHeaderNames.CONTENT_TYPE, contentType);
                }
            }
        }
        if (isMultipart) {
            String value = HttpHeaderValues.MULTIPART_FORM_DATA + "; " + HttpHeaderValues.BOUNDARY + '='
                    + multipartDataBoundary;
            headers.add(HttpHeaderNames.CONTENT_TYPE, value);
        } else {
            // Not multipart
            headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED);
        }
        // Now consider size for chunk or not
        long realSize = globalBodySize;
        if (!isMultipart) {
            realSize -= 1; // last '&' removed
        }
        iterator = multipartHttpDatas.listIterator();
    
        headers.set(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(realSize));
        if (realSize > HttpPostBodyUtil.chunkSize || isMultipart) {
            HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), status);
            response.headers().add(headers);
            isChunked = true;
            if (transferEncoding != null) {
                headers.remove(HttpHeaderNames.TRANSFER_ENCODING);
                for (CharSequence v : transferEncoding) {
                    if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(v)) {
                        // ignore
                    } else {
                        headers.add(HttpHeaderNames.TRANSFER_ENCODING, v);
                    }
                }
            }
            HttpUtil.setTransferEncodingChunked(response, true);
            
            return response;
        } else {
            // get the only one body and set it to the request
            HttpContent chunk = nextChunk();
            DefaultFullHttpResponse response = new DefaultFullHttpResponse(request.protocolVersion(), status, chunk.content());
            response.headers().add(headers);
            return response;
        }
    }
    

    Or even simpler:

    public HttpResponse finalizeResponse(HttpResponseStatus status) throws ErrorDataEncoderException {
        // Finalize the multipartHttpDatas
        HttpRequest request = finalizeRequest();
        if (request instanceof WrappedFullHttpRequest) {
            DefaultFullHttpResponse response = new DefaultFullHttpResponse(request.protocolVersion(), status,
                    ((WrappedFullHttpRequest) request).content());
            response.headers().add(request.headers());
            return response;
        } else {
            HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), status);
            response.headers().add(request.headers());
            return response;
        }
    }