HTTP 多部分响应 Netty

HTTP Multipart Response Netty

这是这个 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 的副本,它似乎可以完成创建多部分 POST 请求的工作。 Response 是否也有类似的东西?

创建类似于 HttpPostRequestEncoder 的等效逻辑并使其也适用于响应会很容易,但我想知道 Netty 中是否已经存在一些我不知道的东西。

我相信你是对的,没有这样的代码来进行多部分响应。 但我认为通过添加与 finalizeRequest() 类似的方法来完成 HttpResponse 来扩展 HttpPostRequestEncoder 可以很容易地实现它。

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

例如(未测试),添加以下方法至少是一个好的开始:

/**
 * 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;
    }
}

或者更简单:

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;
    }
}