From a170f05b4bf7cb4b7bed244275fc90f978654988 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 6 May 2013 15:00:18 +0200 Subject: [PATCH] [#1293] Fix handling of chunked requests in HttpPostRequestEncoder and the multipart examples --- .../multipart/HttpPostRequestEncoder.java | 161 +++++++++++++++++- .../example/http/upload/HttpUploadClient.java | 10 +- .../http/upload/HttpUploadServerHandler.java | 69 ++++---- 3 files changed, 196 insertions(+), 44 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java index 7aa2197784..e27fbcf42f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java @@ -17,12 +17,17 @@ package io.netty.handler.codec.http.multipart; import io.netty.buffer.ByteBuf; import io.netty.buffer.MessageBuf; +import io.netty.handler.codec.DecoderResult; +import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.DefaultHttpContent; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpConstants; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.stream.ChunkedMessageInput; import java.io.File; @@ -77,7 +82,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput /** * Request to encode */ - private final FullHttpRequest request; + private final HttpRequest request; /** * Default charset to use @@ -130,7 +135,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput * @throws ErrorDataEncoderException * if the request is not a POST */ - public HttpPostRequestEncoder(FullHttpRequest request, boolean multipart) throws ErrorDataEncoderException { + public HttpPostRequestEncoder(HttpRequest request, boolean multipart) throws ErrorDataEncoderException { this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, multipart, HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738); } @@ -148,7 +153,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput * @throws ErrorDataEncoderException * if the request is not a POST */ - public HttpPostRequestEncoder(HttpDataFactory factory, FullHttpRequest request, boolean multipart) + public HttpPostRequestEncoder(HttpDataFactory factory, HttpRequest request, boolean multipart) throws ErrorDataEncoderException { this(factory, request, multipart, HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738); } @@ -171,7 +176,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput * if the request is not a POST */ public HttpPostRequestEncoder( - HttpDataFactory factory, FullHttpRequest request, boolean multipart, Charset charset, + HttpDataFactory factory, HttpRequest request, boolean multipart, Charset charset, EncoderMode encoderMode) throws ErrorDataEncoderException { if (factory == null) { @@ -635,7 +640,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput * @throws ErrorDataEncoderException * if the encoding is in error or if the finalize were already done */ - public FullHttpRequest finalizeRequest() throws ErrorDataEncoderException { + public HttpRequest finalizeRequest() throws ErrorDataEncoderException { // Finalize the multipartHttpDatas if (!headerFinalized) { if (isMultipart) { @@ -701,13 +706,22 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput } } HttpHeaders.setTransferEncodingChunked(request); - request.content().clear(); + + // wrap to hide the possible content + return new WrappedHttpRequest(request); } else { // get the only one body and set it to the request HttpContent chunk = nextChunk(); - request.content().clear().writeBytes(chunk.content()); + if (request instanceof FullHttpRequest) { + FullHttpRequest fullRequest = (FullHttpRequest) request; + if (!fullRequest.content().equals(chunk.content())) { + fullRequest.content().clear().writeBytes(chunk.content()); + } + return fullRequest; + } else { + return new WrappedFullHttpRequest(request, chunk); + } } - return request; } /** @@ -1052,4 +1066,135 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput super(msg, cause); } } + + private static class WrappedHttpRequest implements HttpRequest { + private final HttpRequest request; + WrappedHttpRequest(HttpRequest request) { + this.request = request; + } + + @Override + public HttpRequest setProtocolVersion(HttpVersion version) { + request.setProtocolVersion(version); + return this; + } + + @Override + public HttpRequest setMethod(HttpMethod method) { + request.setMethod(method); + return this; + } + + @Override + public HttpRequest setUri(String uri) { + request.setUri(uri); + return this; + } + + @Override + public HttpMethod getMethod() { + return request.getMethod(); + } + + @Override + public String getUri() { + return request.getUri(); + } + + @Override + public HttpVersion getProtocolVersion() { + return request.getProtocolVersion(); + } + + @Override + public HttpHeaders headers() { + return request.headers(); + } + + @Override + public DecoderResult getDecoderResult() { + return request.getDecoderResult(); + } + + @Override + public void setDecoderResult(DecoderResult result) { + request.setDecoderResult(result); + } + } + + private static final class WrappedFullHttpRequest extends WrappedHttpRequest implements FullHttpRequest { + private final HttpContent content; + private WrappedFullHttpRequest(HttpRequest request, HttpContent content) { + super(request); + this.content = content; + } + + @Override + public FullHttpRequest setProtocolVersion(HttpVersion version) { + super.setProtocolVersion(version); + return this; + } + + @Override + public FullHttpRequest setMethod(HttpMethod method) { + super.setMethod(method); + return this; + } + + @Override + public FullHttpRequest setUri(String uri) { + super.setUri(uri); + return this; + } + + @Override + public FullHttpRequest copy() { + DefaultFullHttpRequest copy = new DefaultFullHttpRequest( + getProtocolVersion(), getMethod(), getUri(), content().copy()); + copy.headers().set(headers()); + copy.trailingHeaders().set(trailingHeaders()); + return copy; + } + + @Override + public FullHttpRequest retain(int increment) { + content.retain(increment); + return this; + } + + @Override + public FullHttpRequest retain() { + content.retain(); + return this; + } + + @Override + public ByteBuf content() { + return content.content(); + } + + @Override + public HttpHeaders trailingHeaders() { + if (content instanceof LastHttpContent) { + return ((LastHttpContent) content).trailingHeaders(); + } else { + return HttpHeaders.EMPTY_HEADERS; + } + } + + @Override + public int refCnt() { + return content.refCnt(); + } + + @Override + public boolean release() { + return content.release(); + } + + @Override + public boolean release(int decrement) { + return content.release(decrement); + } + } } diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java index c9e21aa618..51fec007c4 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java @@ -23,9 +23,11 @@ import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.ClientCookieEncoder; import io.netty.handler.codec.http.DefaultCookie; import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.QueryStringEncoder; import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; @@ -220,8 +222,8 @@ public class HttpUploadClient { Channel channel = bootstrap.connect(host, port).sync().channel(); // Prepare the HTTP request. - FullHttpRequest request = - new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriSimple.toASCIIString()); + HttpRequest request = + new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriSimple.toASCIIString()); // Use the PostBody encoder HttpPostRequestEncoder bodyRequestEncoder = null; @@ -304,7 +306,7 @@ public class HttpUploadClient { Channel channel = bootstrap.connect(host, port).sync().channel(); // Prepare the HTTP request. - FullHttpRequest request = + HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriFile.toASCIIString()); // Use the PostBody encoder @@ -338,7 +340,7 @@ public class HttpUploadClient { // finalize request try { - bodyRequestEncoder.finalizeRequest(); + request = bodyRequestEncoder.finalizeRequest(); } catch (ErrorDataEncoderException e) { // if an encoding error occurs e.printStackTrace(); diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java index 3a37057882..3a2ba07cd3 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java @@ -30,6 +30,7 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.handler.codec.http.ServerCookieEncoder; import io.netty.handler.codec.http.multipart.Attribute; @@ -96,12 +97,6 @@ public class HttpUploadServerHandler extends ChannelInboundMessageHandlerAdapter @Override public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpRequest) { - // clean previous FileUpload if Any - if (decoder != null) { - decoder.cleanFiles(); - decoder = null; - } - HttpRequest request = this.request = (HttpRequest) msg; URI uri = new URI(request.getUri()); if (!uri.getPath().startsWith("/form")) { @@ -172,38 +167,48 @@ public class HttpUploadServerHandler extends ChannelInboundMessageHandlerAdapter // Chunk version responseContent.append("Chunks: "); readingChunks = true; - } else { - // Not chunk version - readHttpDataAllReceive(ctx.channel()); - responseContent.append("\r\n\r\nEND OF NOT CHUNKED CONTENT\r\n"); - writeResponse(ctx.channel()); } } - if (msg instanceof HttpContent) { - // New chunk is received - HttpContent chunk = (HttpContent) msg; - try { - decoder.offer(chunk); - } catch (ErrorDataDecoderException e1) { - e1.printStackTrace(); - responseContent.append(e1.getMessage()); - writeResponse(ctx.channel()); - ctx.channel().close(); - return; - } - responseContent.append('o'); - // example of reading chunk by chunk (minimize memory usage due to - // Factory) - readHttpDataChunkByChunk(); - // example of reading only if at the end - if (chunk instanceof HttpContent) { - readHttpDataAllReceive(ctx.channel()); - writeResponse(ctx.channel()); - readingChunks = false; + + // check if the decoder was constructed before + // if not it handles the form get + if (decoder != null) { + if (msg instanceof HttpContent) { + // New chunk is received + HttpContent chunk = (HttpContent) msg; + try { + decoder.offer(chunk); + } catch (ErrorDataDecoderException e1) { + e1.printStackTrace(); + responseContent.append(e1.getMessage()); + writeResponse(ctx.channel()); + ctx.channel().close(); + return; + } + responseContent.append('o'); + // example of reading chunk by chunk (minimize memory usage due to + // Factory) + readHttpDataChunkByChunk(); + // example of reading only if at the end + if (chunk instanceof LastHttpContent) { + readHttpDataAllReceive(ctx.channel()); + writeResponse(ctx.channel()); + readingChunks = false; + + reset(); + } } } } + private void reset() { + request = null; + + // clean previous FileUpload if Any + decoder.cleanFiles(); + decoder = null; + } + /** * Example of reading all InterfaceHttpData from finished transfer */