[#1293] Fix handling of chunked requests in HttpPostRequestEncoder and the multipart examples

This commit is contained in:
Norman Maurer 2013-05-06 15:00:18 +02:00
parent a4a92ee14a
commit a170f05b4b
3 changed files with 196 additions and 44 deletions

View File

@ -17,12 +17,17 @@ package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.MessageBuf; 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.DefaultHttpContent;
import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpConstants; import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod; 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 io.netty.handler.stream.ChunkedMessageInput;
import java.io.File; import java.io.File;
@ -77,7 +82,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
/** /**
* Request to encode * Request to encode
*/ */
private final FullHttpRequest request; private final HttpRequest request;
/** /**
* Default charset to use * Default charset to use
@ -130,7 +135,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
* @throws ErrorDataEncoderException * @throws ErrorDataEncoderException
* if the request is not a POST * 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, this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, multipart,
HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738); HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738);
} }
@ -148,7 +153,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
* @throws ErrorDataEncoderException * @throws ErrorDataEncoderException
* if the request is not a POST * 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 { throws ErrorDataEncoderException {
this(factory, request, multipart, HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738); this(factory, request, multipart, HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738);
} }
@ -171,7 +176,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
* if the request is not a POST * if the request is not a POST
*/ */
public HttpPostRequestEncoder( public HttpPostRequestEncoder(
HttpDataFactory factory, FullHttpRequest request, boolean multipart, Charset charset, HttpDataFactory factory, HttpRequest request, boolean multipart, Charset charset,
EncoderMode encoderMode) EncoderMode encoderMode)
throws ErrorDataEncoderException { throws ErrorDataEncoderException {
if (factory == null) { if (factory == null) {
@ -635,7 +640,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
* @throws ErrorDataEncoderException * @throws ErrorDataEncoderException
* if the encoding is in error or if the finalize were already done * 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 // Finalize the multipartHttpDatas
if (!headerFinalized) { if (!headerFinalized) {
if (isMultipart) { if (isMultipart) {
@ -701,13 +706,22 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
} }
} }
HttpHeaders.setTransferEncodingChunked(request); HttpHeaders.setTransferEncodingChunked(request);
request.content().clear();
// wrap to hide the possible content
return new WrappedHttpRequest(request);
} else { } else {
// get the only one body and set it to the request // get the only one body and set it to the request
HttpContent chunk = nextChunk(); 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<HttpContent>
super(msg, cause); 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);
}
}
} }

View File

@ -23,9 +23,11 @@ import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.ClientCookieEncoder; import io.netty.handler.codec.http.ClientCookieEncoder;
import io.netty.handler.codec.http.DefaultCookie; import io.netty.handler.codec.http.DefaultCookie;
import io.netty.handler.codec.http.DefaultFullHttpRequest; 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.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod; 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.HttpVersion;
import io.netty.handler.codec.http.QueryStringEncoder; import io.netty.handler.codec.http.QueryStringEncoder;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
@ -220,8 +222,8 @@ public class HttpUploadClient {
Channel channel = bootstrap.connect(host, port).sync().channel(); Channel channel = bootstrap.connect(host, port).sync().channel();
// Prepare the HTTP request. // Prepare the HTTP request.
FullHttpRequest request = HttpRequest request =
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriSimple.toASCIIString()); new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriSimple.toASCIIString());
// Use the PostBody encoder // Use the PostBody encoder
HttpPostRequestEncoder bodyRequestEncoder = null; HttpPostRequestEncoder bodyRequestEncoder = null;
@ -304,7 +306,7 @@ public class HttpUploadClient {
Channel channel = bootstrap.connect(host, port).sync().channel(); Channel channel = bootstrap.connect(host, port).sync().channel();
// Prepare the HTTP request. // Prepare the HTTP request.
FullHttpRequest request = HttpRequest request =
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriFile.toASCIIString()); new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriFile.toASCIIString());
// Use the PostBody encoder // Use the PostBody encoder
@ -338,7 +340,7 @@ public class HttpUploadClient {
// finalize request // finalize request
try { try {
bodyRequestEncoder.finalizeRequest(); request = bodyRequestEncoder.finalizeRequest();
} catch (ErrorDataEncoderException e) { } catch (ErrorDataEncoderException e) {
// if an encoding error occurs // if an encoding error occurs
e.printStackTrace(); e.printStackTrace();

View File

@ -30,6 +30,7 @@ import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion; 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.QueryStringDecoder;
import io.netty.handler.codec.http.ServerCookieEncoder; import io.netty.handler.codec.http.ServerCookieEncoder;
import io.netty.handler.codec.http.multipart.Attribute; import io.netty.handler.codec.http.multipart.Attribute;
@ -96,12 +97,6 @@ public class HttpUploadServerHandler extends ChannelInboundMessageHandlerAdapter
@Override @Override
public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) { if (msg instanceof HttpRequest) {
// clean previous FileUpload if Any
if (decoder != null) {
decoder.cleanFiles();
decoder = null;
}
HttpRequest request = this.request = (HttpRequest) msg; HttpRequest request = this.request = (HttpRequest) msg;
URI uri = new URI(request.getUri()); URI uri = new URI(request.getUri());
if (!uri.getPath().startsWith("/form")) { if (!uri.getPath().startsWith("/form")) {
@ -172,38 +167,48 @@ public class HttpUploadServerHandler extends ChannelInboundMessageHandlerAdapter
// Chunk version // Chunk version
responseContent.append("Chunks: "); responseContent.append("Chunks: ");
readingChunks = true; 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 // check if the decoder was constructed before
HttpContent chunk = (HttpContent) msg; // if not it handles the form get
try { if (decoder != null) {
decoder.offer(chunk); if (msg instanceof HttpContent) {
} catch (ErrorDataDecoderException e1) { // New chunk is received
e1.printStackTrace(); HttpContent chunk = (HttpContent) msg;
responseContent.append(e1.getMessage()); try {
writeResponse(ctx.channel()); decoder.offer(chunk);
ctx.channel().close(); } catch (ErrorDataDecoderException e1) {
return; e1.printStackTrace();
} responseContent.append(e1.getMessage());
responseContent.append('o'); writeResponse(ctx.channel());
// example of reading chunk by chunk (minimize memory usage due to ctx.channel().close();
// Factory) return;
readHttpDataChunkByChunk(); }
// example of reading only if at the end responseContent.append('o');
if (chunk instanceof HttpContent) { // example of reading chunk by chunk (minimize memory usage due to
readHttpDataAllReceive(ctx.channel()); // Factory)
writeResponse(ctx.channel()); readHttpDataChunkByChunk();
readingChunks = false; // 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 * Example of reading all InterfaceHttpData from finished transfer
*/ */