[#677] Overhaul HTTP codec

This commit tries to simplify the handling of Http easier and more consistent. This has a effect of many channges. Including:
 - HttpMessage was renamed to HttpHeader and the setContent and getContent methods were removed
 - HttpChunk was renamed to HttpContent
 - HttpChunkTrailer was renamed to LastHttpContent
 - HttpCodecUtil was merged into HttpHeaders

Now a "complete" Http message (request or response) contains of the following parts:
 - HttpHeader (HttpRequestHeader or HttpResponseHeader)
 - 0 - n HttpContent objects which contains parts of the content of the message
 - 1 LastHttpContent which marks the end of the message and contains the remaining data of the content

I also changed the sematic of HttpResponse and HttpRequest, these now represent a "complete" message which contains the HttpHeader and the HttpLastContent, and so can be used to eeasily send requests. The HttpMessageAggregator was renamed to HttpObjectAggregator and produce HttpResponse / HttpRequest message.
This commit is contained in:
Norman Maurer 2013-01-14 16:52:30 +01:00
parent 506474f569
commit b7de868003
77 changed files with 1537 additions and 1577 deletions

View File

@ -22,7 +22,7 @@ import static io.netty.handler.codec.http.CookieEncoderUtil.*;
* the HTTP cookie version 0, 1, and 2.
* <pre>
* // Example
* {@link HttpRequest} req = ...;
* {@link HttpRequestHeader} req = ...;
* res.setHeader("Cookie", {@link ClientCookieEncoder}.encode("JSESSIONID", "1234"));
* </pre>
*

View File

@ -29,7 +29,7 @@ import java.util.TreeSet;
* the HTTP cookie version 0, 1, and 2.
*
* <pre>
* {@link HttpRequest} req = ...;
* {@link HttpRequestHeader} req = ...;
* String value = req.getHeader("Cookie");
* Set&lt;{@link Cookie}&gt; cookies = new {@link CookieDecoder}().decode(value);
* </pre>

View File

@ -18,18 +18,16 @@ package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
/**
* The default {@link HttpChunk} implementation.
* The default {@link HttpContent} implementation.
*/
public class DefaultHttpChunk extends DefaultHttpObject implements HttpChunk {
public class DefaultHttpContent extends DefaultHttpObject implements HttpContent {
private ByteBuf content;
private boolean last;
/**
* Creates a new instance with the specified chunk content. If an empty
* buffer is specified, this chunk becomes the 'end of content' marker.
* Creates a new instance with the specified chunk content.
*/
public DefaultHttpChunk(ByteBuf content) {
public DefaultHttpContent(ByteBuf content) {
setContent(content);
}
@ -43,27 +41,16 @@ public class DefaultHttpChunk extends DefaultHttpObject implements HttpChunk {
if (content == null) {
throw new NullPointerException("content");
}
last = !content.readable();
this.content = content;
}
@Override
public boolean isLast() {
return last;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName());
final boolean last = isLast();
buf.append("(last: ");
buf.append(last);
if (!last) {
buf.append(", size: ");
buf.append(getContent().readableBytes());
}
buf.append(" size: ");
buf.append(getContent().readableBytes());
buf.append(", decodeResult: ");
buf.append(getDecoderResult());

View File

@ -0,0 +1,127 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import io.netty.util.internal.StringUtil;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The default {@link HttpHeader} implementation.
*/
public abstract class DefaultHttpHeader extends DefaultHttpObject implements HttpHeader {
private final HttpHeaders headers = new HttpHeaders();
private HttpVersion version;
/**
* Creates a new instance.
*/
protected DefaultHttpHeader(final HttpVersion version) {
setProtocolVersion(version);
}
@Override
public void addHeader(final String name, final Object value) {
headers.addHeader(name, value);
}
@Override
public void setHeader(final String name, final Object value) {
headers.setHeader(name, value);
}
@Override
public void setHeader(final String name, final Iterable<?> values) {
headers.setHeader(name, values);
}
@Override
public void removeHeader(final String name) {
headers.removeHeader(name);
}
@Override
public void clearHeaders() {
headers.clearHeaders();
}
@Override
public String getHeader(final String name) {
return headers.getHeader(name);
}
@Override
public List<String> getHeaders(final String name) {
return headers.getHeaders(name);
}
@Override
public List<Map.Entry<String, String>> getHeaders() {
return headers.getHeaders();
}
@Override
public boolean containsHeader(final String name) {
return headers.containsHeader(name);
}
@Override
public Set<String> getHeaderNames() {
return headers.getHeaderNames();
}
@Override
public HttpVersion getProtocolVersion() {
return version;
}
@Override
public void setProtocolVersion(HttpVersion version) {
if (version == null) {
throw new NullPointerException("version");
}
this.version = version;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName());
buf.append("(version: ");
buf.append(getProtocolVersion().getText());
buf.append(", keepAlive: ");
buf.append(HttpHeaders.isKeepAlive(this));
buf.append(')');
buf.append(StringUtil.NEWLINE);
appendHeaders(buf);
// Remove the last newline.
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
return buf.toString();
}
void appendHeaders(StringBuilder buf) {
for (Map.Entry<String, String> e: getHeaders()) {
buf.append(e.getKey());
buf.append(": ");
buf.append(e.getValue());
buf.append(StringUtil.NEWLINE);
}
}
}

View File

@ -17,169 +17,29 @@ package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.StringUtil;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The default {@link HttpMessage} implementation.
* Combination of {@link HttpHeader} and {@link LastHttpContent} which can be used to <i>combine</i> the headers and
* the actual content. {@link HttpObjectAggregator} makes use of this.
*
*/
public class DefaultHttpMessage extends DefaultHttpObject implements HttpMessage {
private final HttpHeaders headers = new HttpHeaders();
private HttpVersion version;
public abstract class DefaultHttpMessage extends DefaultHttpHeader implements LastHttpContent {
private ByteBuf content = Unpooled.EMPTY_BUFFER;
private HttpTransferEncoding te = HttpTransferEncoding.SINGLE;
/**
* Creates a new instance.
*/
protected DefaultHttpMessage(final HttpVersion version) {
setProtocolVersion(version);
public DefaultHttpMessage(HttpVersion version) {
super(version);
}
@Override
public void addHeader(final String name, final Object value) {
headers.addHeader(name, value);
}
@Override
public void setHeader(final String name, final Object value) {
headers.setHeader(name, value);
}
@Override
public void setHeader(final String name, final Iterable<?> values) {
headers.setHeader(name, values);
}
@Override
public void removeHeader(final String name) {
headers.removeHeader(name);
}
@Override
public HttpTransferEncoding getTransferEncoding() {
return te;
}
@Override
public void setTransferEncoding(HttpTransferEncoding te) {
if (te == null) {
throw new NullPointerException("te (transferEncoding)");
}
this.te = te;
switch (te) {
case SINGLE:
HttpCodecUtil.removeTransferEncodingChunked(this);
break;
case STREAMED:
HttpCodecUtil.removeTransferEncodingChunked(this);
setContent(Unpooled.EMPTY_BUFFER);
break;
case CHUNKED:
if (!HttpCodecUtil.isTransferEncodingChunked(this)) {
addHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
}
removeHeader(HttpHeaders.Names.CONTENT_LENGTH);
setContent(Unpooled.EMPTY_BUFFER);
break;
}
}
@Override
public void clearHeaders() {
headers.clearHeaders();
public ByteBuf getContent() {
return content;
}
@Override
public void setContent(ByteBuf content) {
if (content == null) {
content = Unpooled.EMPTY_BUFFER;
throw new NullPointerException("content");
}
if (!getTransferEncoding().isSingle() && content.readable()) {
throw new IllegalArgumentException(
"non-empty content disallowed if this.transferEncoding != SINGLE");
}
this.content = content;
}
@Override
public String getHeader(final String name) {
return headers.getHeader(name);
}
@Override
public List<String> getHeaders(final String name) {
return headers.getHeaders(name);
}
@Override
public List<Map.Entry<String, String>> getHeaders() {
return headers.getHeaders();
}
@Override
public boolean containsHeader(final String name) {
return headers.containsHeader(name);
}
@Override
public Set<String> getHeaderNames() {
return headers.getHeaderNames();
}
@Override
public HttpVersion getProtocolVersion() {
return version;
}
@Override
public void setProtocolVersion(HttpVersion version) {
if (version == null) {
throw new NullPointerException("version");
}
this.version = version;
}
@Override
public ByteBuf getContent() {
if (getTransferEncoding() == HttpTransferEncoding.SINGLE) {
return content;
} else {
return Unpooled.EMPTY_BUFFER;
}
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName());
buf.append("(version: ");
buf.append(getProtocolVersion().getText());
buf.append(", keepAlive: ");
buf.append(HttpHeaders.isKeepAlive(this));
buf.append(", transferEncoding: ");
buf.append(getTransferEncoding());
buf.append(')');
buf.append(StringUtil.NEWLINE);
appendHeaders(buf);
// Remove the last newline.
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
return buf.toString();
}
void appendHeaders(StringBuilder buf) {
for (Map.Entry<String, String> e: getHeaders()) {
buf.append(e.getKey());
buf.append(": ");
buf.append(e.getValue());
buf.append(StringUtil.NEWLINE);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -15,75 +15,34 @@
*/
package io.netty.handler.codec.http;
import io.netty.util.internal.StringUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
/**
* The default {@link HttpRequest} implementation.
* Default implementation of {@link HttpRequest}.
*/
public class DefaultHttpRequest extends DefaultHttpMessage implements HttpRequest {
public class DefaultHttpRequest extends DefaultHttpRequestHeader implements HttpRequest {
private ByteBuf content = Unpooled.EMPTY_BUFFER;
private HttpMethod method;
private String uri;
/**
* Creates a new instance.
*
* @param httpVersion the HTTP version of the request
* @param method the HTTP method of the request
* @param uri the URI or path of the request
*/
public DefaultHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri) {
super(httpVersion);
setMethod(method);
setUri(uri);
this(httpVersion, method, uri, Unpooled.EMPTY_BUFFER);
}
public DefaultHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, ByteBuf content) {
super(httpVersion, method, uri);
setContent(content);
}
@Override
public HttpMethod getMethod() {
return method;
public ByteBuf getContent() {
return content;
}
@Override
public void setMethod(HttpMethod method) {
if (method == null) {
throw new NullPointerException("method");
public void setContent(ByteBuf content) {
if (content == null) {
throw new NullPointerException("content");
}
this.method = method;
}
@Override
public String getUri() {
return uri;
}
@Override
public void setUri(String uri) {
if (uri == null) {
throw new NullPointerException("uri");
}
this.uri = uri;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName());
buf.append("(transferEncoding: ");
buf.append(getTransferEncoding());
buf.append(", decodeResult: ");
buf.append(getDecoderResult());
buf.append(')');
buf.append(StringUtil.NEWLINE);
buf.append(getMethod().toString());
buf.append(' ');
buf.append(getUri());
buf.append(' ');
buf.append(getProtocolVersion().getText());
buf.append(StringUtil.NEWLINE);
appendHeaders(buf);
// Remove the last newline.
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
return buf.toString();
this.content = content;
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import io.netty.util.internal.StringUtil;
/**
* The default {@link HttpRequestHeader} implementation.
*/
public class DefaultHttpRequestHeader extends DefaultHttpHeader implements HttpRequestHeader {
private HttpMethod method;
private String uri;
/**
* Creates a new instance.
*
* @param httpVersion the HTTP version of the request
* @param method the HTTP method of the request
* @param uri the URI or path of the request
*/
public DefaultHttpRequestHeader(HttpVersion httpVersion, HttpMethod method, String uri) {
super(httpVersion);
setMethod(method);
setUri(uri);
}
@Override
public HttpMethod getMethod() {
return method;
}
@Override
public void setMethod(HttpMethod method) {
if (method == null) {
throw new NullPointerException("method");
}
this.method = method;
}
@Override
public String getUri() {
return uri;
}
@Override
public void setUri(String uri) {
if (uri == null) {
throw new NullPointerException("uri");
}
this.uri = uri;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName());
buf.append(", decodeResult: ");
buf.append(getDecoderResult());
buf.append(')');
buf.append(StringUtil.NEWLINE);
buf.append(getMethod().toString());
buf.append(' ');
buf.append(getUri());
buf.append(' ');
buf.append(getProtocolVersion().getText());
buf.append(StringUtil.NEWLINE);
appendHeaders(buf);
// Remove the last newline.
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
return buf.toString();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -15,57 +15,35 @@
*/
package io.netty.handler.codec.http;
import io.netty.util.internal.StringUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
/**
* The default {@link HttpResponse} implementation.
* Default implementation of a {@link HttpResponse}.
*/
public class DefaultHttpResponse extends DefaultHttpMessage implements HttpResponse {
public class DefaultHttpResponse extends DefaultHttpResponseHeader implements HttpResponse {
private ByteBuf content = Unpooled.EMPTY_BUFFER;
private HttpResponseStatus status;
/**
* Creates a new instance.
*
* @param version the HTTP version of this response
* @param status the status of this response
*/
public DefaultHttpResponse(HttpVersion version, HttpResponseStatus status) {
super(version);
setStatus(status);
this(version, status, Unpooled.EMPTY_BUFFER);
}
public DefaultHttpResponse(HttpVersion version, HttpResponseStatus status, ByteBuf content) {
super(version, status);
setContent(content);
}
@Override
public HttpResponseStatus getStatus() {
return status;
public ByteBuf getContent() {
return content;
}
@Override
public void setStatus(HttpResponseStatus status) {
if (status == null) {
throw new NullPointerException("status");
public void setContent(ByteBuf content) {
if (content == null) {
throw new NullPointerException("content");
}
this.status = status;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName());
buf.append("(transferEncoding: ");
buf.append(getTransferEncoding());
buf.append(", decodeResult: ");
buf.append(getDecoderResult());
buf.append(')');
buf.append(StringUtil.NEWLINE);
buf.append(getProtocolVersion().getText());
buf.append(' ');
buf.append(getStatus().toString());
buf.append(StringUtil.NEWLINE);
appendHeaders(buf);
// Remove the last newline.
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
return buf.toString();
this.content = content;
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import io.netty.util.internal.StringUtil;
/**
* The default {@link HttpResponseHeader} implementation.
*/
public class DefaultHttpResponseHeader extends DefaultHttpHeader implements HttpResponseHeader {
private HttpResponseStatus status;
/**
* Creates a new instance.
*
* @param version the HTTP version of this response
* @param status the status of this response
*/
public DefaultHttpResponseHeader(HttpVersion version, HttpResponseStatus status) {
super(version);
setStatus(status);
}
@Override
public HttpResponseStatus getStatus() {
return status;
}
@Override
public void setStatus(HttpResponseStatus status) {
if (status == null) {
throw new NullPointerException("status");
}
this.status = status;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName());
buf.append(", decodeResult: ");
buf.append(getDecoderResult());
buf.append(')');
buf.append(StringUtil.NEWLINE);
buf.append(getProtocolVersion().getText());
buf.append(' ');
buf.append(getStatus().toString());
buf.append(StringUtil.NEWLINE);
appendHeaders(buf);
// Remove the last newline.
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
return buf.toString();
}
}

View File

@ -24,14 +24,14 @@ import java.util.Map;
import java.util.Set;
/**
* The default {@link HttpChunkTrailer} implementation.
* The default {@link LastHttpContent} implementation.
*/
public class DefaultHttpChunkTrailer extends DefaultHttpObject implements HttpChunkTrailer {
public class DefaultLastHttpContent extends DefaultHttpContent implements LastHttpContent {
private final HttpHeaders headers = new HttpHeaders() {
@Override
void validateHeaderName(String name) {
super.validateHeaderName(name);
void validateHeaderName0(String name) {
super.validateHeaderName0(name);
if (name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) ||
name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) ||
name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) {
@ -41,9 +41,12 @@ public class DefaultHttpChunkTrailer extends DefaultHttpObject implements HttpCh
}
};
@Override
public boolean isLast() {
return true;
public DefaultLastHttpContent() {
this(Unpooled.EMPTY_BUFFER);
}
public DefaultLastHttpContent(ByteBuf content) {
super(content);
}
@Override
@ -96,29 +99,12 @@ public class DefaultHttpChunkTrailer extends DefaultHttpObject implements HttpCh
return headers.getHeaderNames();
}
@Override
public ByteBuf getContent() {
return Unpooled.EMPTY_BUFFER;
}
@Override
public void setContent(ByteBuf content) {
throw new IllegalStateException("read-only");
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName());
final boolean last = isLast();
buf.append("(last: ");
buf.append(last);
if (!last) {
buf.append(", size: ");
buf.append(getContent().readableBytes());
}
buf.append(", size: ");
buf.append(getContent().readableBytes());
buf.append(", decodeResult: ");
buf.append(getDecoderResult());
buf.append(')');

View File

@ -84,17 +84,15 @@ public class HttpClientCodec extends CombinedChannelHandler {
@Override
protected void encode(
ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
if (msg instanceof HttpRequest && !done) {
queue.offer(((HttpRequest) msg).getMethod());
if (msg instanceof HttpRequestHeader && !done) {
queue.offer(((HttpRequestHeader) msg).getMethod());
}
super.encode(ctx, msg, out);
if (failOnMissingResponse) {
// check if the request is chunked if so do not increment
if (msg instanceof HttpRequest && ((HttpMessage) msg).getTransferEncoding().isSingle()) {
requestResponseCounter.incrementAndGet();
} else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) {
if (msg instanceof LastHttpContent) {
// increment as its the last chunk
requestResponseCounter.incrementAndGet();
}
@ -127,21 +125,20 @@ public class HttpClientCodec extends CombinedChannelHandler {
return;
}
// check if it's an HttpMessage and its transfer encoding is SINGLE.
if (msg instanceof HttpMessage && ((HttpMessage) msg).getTransferEncoding().isSingle()) {
requestResponseCounter.decrementAndGet();
} else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) {
// check if it's an Header and its transfer encoding is not chunked.
if (msg instanceof LastHttpContent) {
requestResponseCounter.decrementAndGet();
} else if (msg instanceof Object[]) {
// we just decrement it here as we only use this if the end of the chunk is reached
// It would be more safe to check all the objects in the array but would also be slower
requestResponseCounter.decrementAndGet();
Object[] objects = (Object[]) msg;
for (Object obj: objects) {
decrement(obj);
}
}
}
@Override
protected boolean isContentAlwaysEmpty(HttpMessage msg) {
final int statusCode = ((HttpResponse) msg).getStatus().getCode();
protected boolean isContentAlwaysEmpty(HttpHeader msg) {
final int statusCode = ((HttpResponseHeader) msg).getStatus().getCode();
if (statusCode == 100) {
// 100-continue response should be excluded from paired comparison.
return true;

View File

@ -1,174 +0,0 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import java.util.List;
/**
* A utility class mainly for use with HTTP codec classes
*/
final class HttpCodecUtil {
/**
* Validates the name of a header
*
* @param headerName The header name being validated
*/
static void validateHeaderName(String headerName) {
//Check to see if the name is null
if (headerName == null) {
throw new NullPointerException("Header names cannot be null");
}
//Go through each of the characters in the name
for (int index = 0; index < headerName.length(); index ++) {
//Actually get the character
char character = headerName.charAt(index);
//Check to see if the character is not an ASCII character
if (character > 127) {
throw new IllegalArgumentException(
"Header name cannot contain non-ASCII characters: " + headerName);
}
//Check for prohibited characters.
switch (character) {
case '\t': case '\n': case 0x0b: case '\f': case '\r':
case ' ': case ',': case ':': case ';': case '=':
throw new IllegalArgumentException(
"Header name cannot contain the following prohibited characters: " +
"=,;: \\t\\r\\n\\v\\f: " + headerName);
}
}
}
/**
* Validates the specified header value
*
* @param headerValue The value being validated
*/
static void validateHeaderValue(String headerValue) {
//Check to see if the value is null
if (headerValue == null) {
throw new NullPointerException("Header values cannot be null");
}
/*
* Set up the state of the validation
*
* States are as follows:
*
* 0: Previous character was neither CR nor LF
* 1: The previous character was CR
* 2: The previous character was LF
*/
int state = 0;
//Start looping through each of the character
for (int index = 0; index < headerValue.length(); index ++) {
char character = headerValue.charAt(index);
//Check the absolutely prohibited characters.
switch (character) {
case 0x0b: // Vertical tab
throw new IllegalArgumentException(
"Header value contains a prohibited character '\\v': " + headerValue);
case '\f':
throw new IllegalArgumentException(
"Header value contains a prohibited character '\\f': " + headerValue);
}
// Check the CRLF (HT | SP) pattern
switch (state) {
case 0:
switch (character) {
case '\r':
state = 1;
break;
case '\n':
state = 2;
break;
}
break;
case 1:
switch (character) {
case '\n':
state = 2;
break;
default:
throw new IllegalArgumentException(
"Only '\\n' is allowed after '\\r': " + headerValue);
}
break;
case 2:
switch (character) {
case '\t': case ' ':
state = 0;
break;
default:
throw new IllegalArgumentException(
"Only ' ' and '\\t' are allowed after '\\n': " + headerValue);
}
}
}
if (state != 0) {
throw new IllegalArgumentException(
"Header value must not end with '\\r' or '\\n':" + headerValue);
}
}
/**
* Checks to see if the transfer encoding in a specified {@link HttpMessage} is chunked
*
* @param message The message to check
* @return True if transfer encoding is chunked, otherwise false
*/
static boolean isTransferEncodingChunked(HttpMessage message) {
List<String> transferEncodingHeaders = message.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING);
if (transferEncodingHeaders.isEmpty()) {
return false;
}
for (String value: transferEncodingHeaders) {
if (value.equalsIgnoreCase(HttpHeaders.Values.CHUNKED)) {
return true;
}
}
return false;
}
static void removeTransferEncodingChunked(HttpMessage m) {
List<String> values = m.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING);
values.remove(HttpHeaders.Values.CHUNKED);
if (values.isEmpty()) {
m.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
} else {
m.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, values);
}
}
static boolean isContentLengthSet(HttpMessage m) {
List<String> contentLength = m.getHeaders(HttpHeaders.Names.CONTENT_LENGTH);
return !contentLength.isEmpty();
}
/**
* A constructor to ensure that instances of this class are never made
*/
private HttpCodecUtil() {
}
}

View File

@ -27,19 +27,17 @@ import java.util.Set;
/**
* An HTTP chunk which is used for HTTP chunked transfer-encoding.
* {@link HttpMessageDecoder} generates {@link HttpChunk} after
* {@link HttpMessage} when the content is large or the encoding of the content
* is 'chunked. If you prefer not to receive {@link HttpChunk} in your handler,
* please {@link HttpChunkAggregator} after {@link HttpMessageDecoder} in the
* {@link HttpObjectDecoder} generates {@link HttpContent} after
* {@link HttpHeader} when the content is large or the encoding of the content
* is 'chunked. If you prefer not to receive {@link HttpContent} in your handler,
* please {@link HttpObjectAggregator} after {@link HttpObjectDecoder} in the
* {@link ChannelPipeline}.
* @apiviz.landmark
*/
public interface HttpChunk extends HttpObject {
public interface HttpContent extends HttpObject {
HttpContent EMPTY = new HttpContent() {
/**
* The 'end of content' marker in chunked encoding.
*/
HttpChunkTrailer LAST_CHUNK = new HttpChunkTrailer() {
@Override
public ByteBuf getContent() {
return Unpooled.EMPTY_BUFFER;
@ -51,8 +49,28 @@ public interface HttpChunk extends HttpObject {
}
@Override
public boolean isLast() {
return true;
public DecoderResult getDecoderResult() {
return DecoderResult.SUCCESS;
}
@Override
public void setDecoderResult(DecoderResult result) {
throw new IllegalStateException("read-only");
}
};
/**
* The 'end of content' marker in chunked encoding.
*/
LastHttpContent LAST_CONTENT = new LastHttpContent() {
@Override
public ByteBuf getContent() {
return Unpooled.EMPTY_BUFFER;
}
@Override
public void setContent(ByteBuf content) {
throw new IllegalStateException("read-only");
}
@Override
@ -116,12 +134,6 @@ public interface HttpChunk extends HttpObject {
}
};
/**
* Returns {@code true} if and only if this chunk is the 'end of content'
* marker.
*/
boolean isLast();
/**
* Returns the content of this chunk. If this is the 'end of content'
* marker, {@link Unpooled#EMPTY_BUFFER} will be returned.

View File

@ -21,7 +21,7 @@ import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.util.internal.StringUtil;
/**
* Compresses an {@link HttpMessage} and an {@link HttpChunk} in {@code gzip} or
* Compresses an {@link HttpHeader} and an {@link HttpContent} in {@code gzip} or
* {@code deflate} encoding while respecting the {@code "Accept-Encoding"} header.
* If there is no matching encoding, no compression is done. For more
* information on how this handler modifies the message, please refer to
@ -93,8 +93,8 @@ public class HttpContentCompressor extends HttpContentEncoder {
}
@Override
protected Result beginEncode(HttpMessage msg, String acceptEncoding) throws Exception {
String contentEncoding = msg.getHeader(HttpHeaders.Names.CONTENT_ENCODING);
protected Result beginEncode(HttpHeader header, HttpContent msg, String acceptEncoding) throws Exception {
String contentEncoding = header.getHeader(HttpHeaders.Names.CONTENT_ENCODING);
if (contentEncoding != null &&
!HttpHeaders.Values.IDENTITY.equalsIgnoreCase(contentEncoding)) {
return null;

View File

@ -22,7 +22,7 @@ import io.netty.channel.embedded.EmbeddedByteChannel;
import io.netty.handler.codec.MessageToMessageDecoder;
/**
* Decodes the content of the received {@link HttpRequest} and {@link HttpChunk}.
* Decodes the content of the received {@link HttpRequestHeader} and {@link HttpContent}.
* The original content is replaced with the new content decoded by the
* {@link EmbeddedByteChannel}, which is created by {@link #newContentDecoder(String)}.
* Once decoding is finished, the value of the <tt>'Content-Encoding'</tt>
@ -36,13 +36,15 @@ import io.netty.handler.codec.MessageToMessageDecoder;
* and implement {@link #newContentDecoder(String)} properly to make this class
* functional. For example, refer to the source code of {@link HttpContentDecompressor}.
* <p>
* This handler must be placed after {@link HttpMessageDecoder} in the pipeline
* so that this handler can intercept HTTP requests after {@link HttpMessageDecoder}
* This handler must be placed after {@link HttpObjectDecoder} in the pipeline
* so that this handler can intercept HTTP requests after {@link HttpObjectDecoder}
* converts {@link ByteBuf}s into HTTP requests.
*/
public abstract class HttpContentDecoder extends MessageToMessageDecoder<Object> {
private EmbeddedByteChannel decoder;
private HttpHeader header;
private boolean decodeStarted;
/**
* Creates a new instance.
@ -53,84 +55,90 @@ public abstract class HttpContentDecoder extends MessageToMessageDecoder<Object>
@Override
protected Object decode(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpResponse && ((HttpResponse) msg).getStatus().getCode() == 100) {
if (msg instanceof HttpResponseHeader && ((HttpResponseHeader) msg).getStatus().getCode() == 100) {
// 100-continue response must be passed through.
return msg;
}
if (msg instanceof HttpMessage) {
HttpMessage m = (HttpMessage) msg;
if (msg instanceof HttpHeader) {
assert header == null;
header = (HttpHeader) msg;
cleanup();
}
// Determine the content encoding.
String contentEncoding = m.getHeader(HttpHeaders.Names.CONTENT_ENCODING);
if (contentEncoding != null) {
contentEncoding = contentEncoding.trim();
} else {
contentEncoding = HttpHeaders.Values.IDENTITY;
}
if (msg instanceof HttpContent) {
HttpContent c = (HttpContent) msg;
boolean hasContent =
m.getTransferEncoding().isMultiple() || m.getContent().readable();
if (hasContent && (decoder = newContentDecoder(contentEncoding)) != null) {
// Decode the content and remove or replace the existing headers
// so that the message looks like a decoded message.
String targetContentEncoding = getTargetContentEncoding(contentEncoding);
if (HttpHeaders.Values.IDENTITY.equals(targetContentEncoding)) {
// Do NOT set the 'Content-Encoding' header if the target encoding is 'identity'
// as per: http://tools.ietf.org/html/rfc2616#section-14.11
m.removeHeader(HttpHeaders.Names.CONTENT_ENCODING);
if (!decodeStarted) {
decodeStarted = true;
HttpHeader header = this.header;
this.header = null;
// Determine the content encoding.
String contentEncoding = header.getHeader(HttpHeaders.Names.CONTENT_ENCODING);
if (contentEncoding != null) {
contentEncoding = contentEncoding.trim();
} else {
m.setHeader(HttpHeaders.Names.CONTENT_ENCODING, targetContentEncoding);
contentEncoding = HttpHeaders.Values.IDENTITY;
}
if (m.getTransferEncoding().isSingle()) {
ByteBuf content = m.getContent();
// Decode the content
ByteBuf newContent = Unpooled.buffer();
decode(content, newContent);
finishDecode(newContent);
if ((decoder = newContentDecoder(contentEncoding)) != null) {
// Decode the content and remove or replace the existing headers
// so that the message looks like a decoded message.
String targetContentEncoding = getTargetContentEncoding(contentEncoding);
if (HttpHeaders.Values.IDENTITY.equals(targetContentEncoding)) {
// Do NOT set the 'Content-Encoding' header if the target encoding is 'identity'
// as per: http://tools.ietf.org/html/rfc2616#section-14.11
header.removeHeader(HttpHeaders.Names.CONTENT_ENCODING);
} else {
header.setHeader(HttpHeaders.Names.CONTENT_ENCODING, targetContentEncoding);
}
Object[] decoded = decodeContent(header, c);
// Replace the content.
m.setContent(newContent);
if (m.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) {
m.setHeader(
if (header.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) {
header.setHeader(
HttpHeaders.Names.CONTENT_LENGTH,
Integer.toString(newContent.readableBytes()));
}
}
}
} else if (msg instanceof HttpChunk) {
HttpChunk c = (HttpChunk) msg;
ByteBuf content = c.getContent();
// Decode the chunk if necessary.
if (decoder != null) {
if (!c.isLast()) {
ByteBuf newContent = Unpooled.buffer();
decode(content, newContent);
if (newContent.readable()) {
c.setContent(newContent);
} else {
return null;
}
} else {
ByteBuf lastProduct = Unpooled.buffer();
finishDecode(lastProduct);
// Generate an additional chunk if the decoder produced
// the last product on closure,
if (lastProduct.readable()) {
return new Object[] { new DefaultHttpChunk(lastProduct), c };
Integer.toString(((HttpContent) decoded[1]).getContent().readableBytes()));
}
return decoded;
}
return new Object[] { header, c };
}
return decodeContent(null, c);
}
// Because HttpMessage and HttpChunk is a mutable object, we can simply forward it.
return msg;
}
private Object[] decodeContent(HttpHeader header, HttpContent c) {
ByteBuf newContent = Unpooled.buffer();
ByteBuf content = c.getContent();
decode(content, newContent);
if (c instanceof LastHttpContent) {
ByteBuf lastProduct = Unpooled.buffer();
finishDecode(lastProduct);
// Generate an additional chunk if the decoder produced
// the last product on closure,
if (lastProduct.readable()) {
if (header == null) {
return new Object[] { new DefaultHttpContent(newContent), new DefaultLastHttpContent(lastProduct)};
} else {
return new Object[] { header, new DefaultHttpContent(newContent),
new DefaultLastHttpContent(lastProduct)};
}
}
}
if (header == null) {
return new Object[] { new DefaultHttpContent(newContent) };
} else {
return new Object[] { header, new DefaultHttpContent(newContent) };
}
}
/**
* Returns a new {@link EmbeddedByteChannel} that decodes the HTTP message
* content encoded in the specified <tt>contentEncoding</tt>.

View File

@ -20,7 +20,7 @@ import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
/**
* Decompresses an {@link HttpMessage} and an {@link HttpChunk} compressed in
* Decompresses an {@link HttpHeader} and an {@link HttpContent} compressed in
* {@code gzip} or {@code deflate} encoding. For more information on how this
* handler modifies the message, please refer to {@link HttpContentDecoder}.
*/

View File

@ -25,43 +25,45 @@ import java.util.ArrayDeque;
import java.util.Queue;
/**
* Encodes the content of the outbound {@link HttpResponse} and {@link HttpChunk}.
* Encodes the content of the outbound {@link HttpResponseHeader} and {@link HttpContent}.
* The original content is replaced with the new content encoded by the
* {@link EmbeddedByteChannel}, which is created by {@link #beginEncode(HttpMessage, String)}.
* {@link EmbeddedByteChannel}, which is created by {@link #beginEncode(HttpHeader, HttpContent, String)}.
* Once encoding is finished, the value of the <tt>'Content-Encoding'</tt> header
* is set to the target content encoding, as returned by
* {@link #beginEncode(HttpMessage, String)}.
* {@link #beginEncode(HttpHeader, HttpContent, String)}.
* Also, the <tt>'Content-Length'</tt> header is updated to the length of the
* encoded content. If there is no supported or allowed encoding in the
* corresponding {@link HttpRequest}'s {@code "Accept-Encoding"} header,
* {@link #beginEncode(HttpMessage, String)} should return {@code null} so that
* corresponding {@link HttpRequestHeader}'s {@code "Accept-Encoding"} header,
* {@link #beginEncode(HttpHeader, HttpContent, String)} should return {@code null} so that
* no encoding occurs (i.e. pass-through).
* <p>
* Please note that this is an abstract class. You have to extend this class
* and implement {@link #beginEncode(HttpMessage, String)} properly to make
* and implement {@link #beginEncode(HttpHeader, HttpContent, String)} properly to make
* this class functional. For example, refer to the source code of
* {@link HttpContentCompressor}.
* <p>
* This handler must be placed after {@link HttpMessageEncoder} in the pipeline
* so that this handler can intercept HTTP responses before {@link HttpMessageEncoder}
* This handler must be placed after {@link HttpObjectEncoder} in the pipeline
* so that this handler can intercept HTTP responses before {@link HttpObjectEncoder}
* converts them into {@link ByteBuf}s.
*/
public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpMessage, Object> {
public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpHeader, Object> {
private final Queue<String> acceptEncodingQueue = new ArrayDeque<String>();
private EmbeddedByteChannel encoder;
private HttpHeader header;
private boolean encodeStarted;
/**
* Creates a new instance.
*/
protected HttpContentEncoder() {
super(
new Class<?>[] { HttpMessage.class },
new Class<?>[] { HttpHeader.class },
new Class<?>[] { HttpObject.class });
}
@Override
protected Object decode(ChannelHandlerContext ctx, HttpMessage msg)
protected Object decode(ChannelHandlerContext ctx, HttpHeader msg)
throws Exception {
String acceptedEncoding = msg.getHeader(HttpHeaders.Names.ACCEPT_ENCODING);
if (acceptedEncoding == null) {
@ -75,88 +77,118 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpMessa
@Override
public Object encode(ChannelHandlerContext ctx, Object msg)
throws Exception {
if (msg instanceof HttpResponse && ((HttpResponse) msg).getStatus().getCode() == 100) {
if (msg instanceof HttpResponseHeader && ((HttpResponseHeader) msg).getStatus().getCode() == 100) {
// 100-continue response must be passed through.
return msg;
}
if (msg instanceof HttpMessage) {
HttpMessage m = (HttpMessage) msg;
if (msg instanceof HttpHeader) {
assert header == null;
// check if this message is also of type HttpContent is such case just make a safe copy of the headers
// as the content will get handled later and this simplify the handling
if (msg instanceof HttpContent) {
if (msg instanceof HttpRequestHeader) {
HttpRequestHeader reqHeader = (HttpRequestHeader) msg;
header = new DefaultHttpRequestHeader(reqHeader.getProtocolVersion(), reqHeader.getMethod(),
reqHeader.getUri());
HttpHeaders.setHeaders(reqHeader, header);
} else if (msg instanceof HttpResponseHeader) {
HttpResponseHeader responseHeader = (HttpResponseHeader) msg;
header = new DefaultHttpResponseHeader(responseHeader.getProtocolVersion(),
responseHeader.getStatus());
HttpHeaders.setHeaders(responseHeader, header);
} else {
return msg;
}
} else {
header = (HttpHeader) msg;
}
cleanup();
}
// Determine the content encoding.
String acceptEncoding = acceptEncodingQueue.poll();
if (acceptEncoding == null) {
throw new IllegalStateException("cannot send more responses than requests");
}
if (msg instanceof HttpContent) {
HttpContent c = (HttpContent) msg;
boolean hasContent = m.getTransferEncoding().isMultiple() || m.getContent().readable();
if (!hasContent) {
return m;
}
if (!encodeStarted) {
encodeStarted = true;
HttpHeader header = this.header;
this.header = null;
Result result = beginEncode(m, acceptEncoding);
if (result == null) {
return m;
}
encoder = result.getContentEncoder();
// Encode the content and remove or replace the existing headers
// so that the message looks like a decoded message.
m.setHeader(
HttpHeaders.Names.CONTENT_ENCODING,
result.getTargetContentEncoding());
if (m.getTransferEncoding().isSingle()) {
ByteBuf content = m.getContent();
// Encode the content.
ByteBuf newContent = Unpooled.buffer();
encode(content, newContent);
finishEncode(newContent);
// Replace the content.
m.setContent(newContent);
if (m.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) {
m.setHeader(
HttpHeaders.Names.CONTENT_LENGTH,
Integer.toString(newContent.readableBytes()));
// Determine the content encoding.
String acceptEncoding = acceptEncodingQueue.poll();
if (acceptEncoding == null) {
throw new IllegalStateException("cannot send more responses than requests");
}
Result result = beginEncode(header, c, acceptEncoding);
if (result == null) {
return new Object[] { header, c };
}
encoder = result.getContentEncoder();
// Encode the content and remove or replace the existing headers
// so that the message looks like a decoded message.
header.setHeader(
HttpHeaders.Names.CONTENT_ENCODING,
result.getTargetContentEncoding());
Object[] encoded = encodeContent(header, c);
if (!HttpHeaders.isTransferEncodingChunked(header) && encoded.length == 3) {
if (header.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) {
long length = ((HttpContent) encoded[1]).getContent().readableBytes() +
((HttpContent) encoded[2]).getContent().readableBytes();
header.setHeader(
HttpHeaders.Names.CONTENT_LENGTH,
Long.toString(length));
}
}
return encoded;
}
} else if (msg instanceof HttpChunk) {
HttpChunk c = (HttpChunk) msg;
ByteBuf content = c.getContent();
// Encode the chunk if necessary.
if (encoder != null) {
if (!c.isLast()) {
ByteBuf newContent = Unpooled.buffer();
encode(content, newContent);
if (content.readable()) {
c.setContent(newContent);
} else {
return null;
}
} else {
ByteBuf lastProduct = Unpooled.buffer();
finishEncode(lastProduct);
return encodeContent(null, c);
}
return msg;
}
return null;
}
// Generate an additional chunk if the decoder produced
// the last product on closure,
if (lastProduct.readable()) {
return new Object[] { new DefaultHttpChunk(lastProduct), c };
}
private Object[] encodeContent(HttpHeader header, HttpContent c) {
ByteBuf newContent = Unpooled.buffer();
ByteBuf content = c.getContent();
encode(content, newContent);
if (c instanceof LastHttpContent) {
ByteBuf lastProduct = Unpooled.buffer();
finishEncode(lastProduct);
// Generate an additional chunk if the decoder produced
// the last product on closure,
if (lastProduct.readable()) {
if (header == null) {
return new Object[] { new DefaultHttpContent(newContent), new DefaultLastHttpContent(lastProduct)};
} else {
return new Object[] { header, new DefaultHttpContent(newContent),
new DefaultLastHttpContent(lastProduct)};
}
}
}
// Because HttpMessage and HttpChunk is a mutable object, we can simply forward it.
return msg;
if (header == null) {
return new Object[] { new DefaultHttpContent(newContent) };
} else {
return new Object[] { header, new DefaultHttpContent(newContent) };
}
}
/**
* Prepare to encode the HTTP message content.
*
* @param header
* the header
* @param msg
* the HTTP message whose content should be encoded
* @param acceptEncoding
@ -168,7 +200,7 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpMessa
* {@code null} if {@code acceptEncoding} is unsupported or rejected
* and thus the content should be handled as-is (i.e. no encoding).
*/
protected abstract Result beginEncode(HttpMessage msg, String acceptEncoding) throws Exception;
protected abstract Result beginEncode(HttpHeader header, HttpContent msg, String acceptEncoding) throws Exception;
@Override
public void afterRemove(ChannelHandlerContext ctx) throws Exception {
@ -198,6 +230,7 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpMessa
if (encoder.finish()) {
fetchEncoderOutput(out);
}
encodeStarted = false;
encoder = null;
}

View File

@ -0,0 +1,150 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* An interface that defines a HTTP message, providing common properties for
* {@link HttpRequestHeader} and {@link HttpResponseHeader}.
* @see HttpResponseHeader
* @see HttpRequestHeader
* @see HttpHeaders
*
* @apiviz.landmark
* @apiviz.has io.netty.handler.codec.http.HttpChunk oneway - - is followed by
*/
public interface HttpHeader extends HttpObject {
/**
* Returns the value of a header with the specified name. If there are
* more than one values for the specified name, the first value is returned.
*
* @param name The name of the header to search
* @return The first header value or {@code null} if there is no such header
*/
String getHeader(String name);
/**
* Returns the values of headers with the specified name
*
* @param name The name of the headers to search
* @return A {@link List} of header values which will be empty if no values
* are found
*/
List<String> getHeaders(String name);
/**
* Returns the all headers that this message contains.
*
* @return A {@link List} of the header name-value entries, which will be
* empty if no pairs are found
*/
List<Map.Entry<String, String>> getHeaders();
/**
* Checks to see if there is a header with the specified name
*
* @param name The name of the header to search for
* @return True if at least one header is found
*/
boolean containsHeader(String name);
/**
* Gets a {@link Set} of all header names that this message contains
*
* @return A {@link Set} of all header names
*/
Set<String> getHeaderNames();
/**
* Returns the protocol version of this {@link HttpHeader}
*
* @return The protocol version
*/
HttpVersion getProtocolVersion();
/**
* Sets the protocol version of this {@link HttpHeader}
*
* @param version The version to set
*/
void setProtocolVersion(HttpVersion version);
/**
* Adds a new header with the specified name and value.
*
* If the specified value is not a {@link String}, it is converted
* into a {@link String} by {@link Object#toString()}, except in the cases
* of {@link Date} and {@link Calendar}, which are formatted to the date
* format defined in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
*
* @param name The name of the header being added
* @param value The value of the header being added
*/
void addHeader(String name, Object value);
/**
* Sets a header with the specified name and value.
*
* If there is an existing header with the same name, it is removed.
* If the specified value is not a {@link String}, it is converted into a
* {@link String} by {@link Object#toString()}, except for {@link Date}
* and {@link Calendar}, which are formatted to the date format defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
*
* @param name The name of the header being set
* @param value The value of the header being set
*/
void setHeader(String name, Object value);
/**
* Sets a header with the specified name and values.
*
* If there is an existing header with the same name, it is removed.
* This method can be represented approximately as the following code:
* <pre>
* m.removeHeader(name);
* for (Object v: values) {
* if (v == null) {
* break;
* }
* m.addHeader(name, v);
* }
* </pre>
*
* @param name The name of the headers being set
* @param values The values of the headers being set
*/
void setHeader(String name, Iterable<?> values);
/**
* Removes the header with the specified name.
*
* @param name The name of the header to remove
*/
void removeHeader(String name);
/**
* Removes all headers from this {@link HttpHeader}.
*/
void clearHeaders();
}

View File

@ -27,7 +27,7 @@ import java.util.TreeSet;
/**
* Provides the constants for the standard HTTP header names and values and
* commonly used utility methods that accesses an {@link HttpMessage}.
* commonly used utility methods that accesses an {@link HttpHeader}.
* @apiviz.landmark
* @apiviz.stereotype static
*/
@ -483,7 +483,7 @@ public class HttpHeaders {
* {@code "Connection"} header first and then the return value of
* {@link HttpVersion#isKeepAliveDefault()}.
*/
public static boolean isKeepAlive(HttpMessage message) {
public static boolean isKeepAlive(HttpHeader message) {
String connection = message.getHeader(Names.CONNECTION);
if (Values.CLOSE.equalsIgnoreCase(connection)) {
return false;
@ -515,7 +515,7 @@ public class HttpHeaders {
* </ul></li>
* </ul>
*/
public static void setKeepAlive(HttpMessage message, boolean keepAlive) {
public static void setKeepAlive(HttpHeader message, boolean keepAlive) {
if (message.getProtocolVersion().isKeepAliveDefault()) {
if (keepAlive) {
message.removeHeader(Names.CONNECTION);
@ -538,7 +538,7 @@ public class HttpHeaders {
*
* @return the header value or {@code null} if there is no such header
*/
public static String getHeader(HttpMessage message, String name) {
public static String getHeader(HttpHeader message, String name) {
return message.getHeader(name);
}
@ -550,7 +550,7 @@ public class HttpHeaders {
* @return the header value or the {@code defaultValue} if there is no such
* header
*/
public static String getHeader(HttpMessage message, String name, String defaultValue) {
public static String getHeader(HttpHeader message, String name, String defaultValue) {
String value = message.getHeader(name);
if (value == null) {
return defaultValue;
@ -566,7 +566,7 @@ public class HttpHeaders {
* and {@link Calendar} which are formatted to the date format defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
*/
public static void setHeader(HttpMessage message, String name, Object value) {
public static void setHeader(HttpHeader message, String name, Object value) {
message.setHeader(name, value);
}
@ -584,7 +584,7 @@ public class HttpHeaders {
* }
* </pre>
*/
public static void setHeader(HttpMessage message, String name, Iterable<?> values) {
public static void setHeader(HttpHeader message, String name, Iterable<?> values) {
message.setHeader(name, values);
}
@ -595,21 +595,21 @@ public class HttpHeaders {
* and {@link Calendar} which are formatted to the date format defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
*/
public static void addHeader(HttpMessage message, String name, Object value) {
public static void addHeader(HttpHeader message, String name, Object value) {
message.addHeader(name, value);
}
/**
* Removes the header with the specified name.
*/
public static void removeHeader(HttpMessage message, String name) {
public static void removeHeader(HttpHeader message, String name) {
message.removeHeader(name);
}
/**
* Removes all headers from the specified message.
*/
public static void clearHeaders(HttpMessage message) {
public static void clearHeaders(HttpHeader message) {
message.clearHeaders();
}
@ -622,7 +622,7 @@ public class HttpHeaders {
* @throws NumberFormatException
* if there is no such header or the header value is not a number
*/
public static int getIntHeader(HttpMessage message, String name) {
public static int getIntHeader(HttpHeader message, String name) {
String value = getHeader(message, name);
if (value == null) {
throw new NumberFormatException("header not found: " + name);
@ -638,7 +638,7 @@ public class HttpHeaders {
* @return the header value or the {@code defaultValue} if there is no such
* header or the header value is not a number
*/
public static int getIntHeader(HttpMessage message, String name, int defaultValue) {
public static int getIntHeader(HttpHeader message, String name, int defaultValue) {
String value = getHeader(message, name);
if (value == null) {
return defaultValue;
@ -655,7 +655,7 @@ public class HttpHeaders {
* Sets a new integer header with the specified name and value. If there
* is an existing header with the same name, the existing header is removed.
*/
public static void setIntHeader(HttpMessage message, String name, int value) {
public static void setIntHeader(HttpHeader message, String name, int value) {
message.setHeader(name, value);
}
@ -663,14 +663,14 @@ public class HttpHeaders {
* Sets a new integer header with the specified name and values. If there
* is an existing header with the same name, the existing header is removed.
*/
public static void setIntHeader(HttpMessage message, String name, Iterable<Integer> values) {
public static void setIntHeader(HttpHeader message, String name, Iterable<Integer> values) {
message.setHeader(name, values);
}
/**
* Adds a new integer header with the specified name and value.
*/
public static void addIntHeader(HttpMessage message, String name, int value) {
public static void addIntHeader(HttpHeader message, String name, int value) {
message.addHeader(name, value);
}
@ -683,7 +683,7 @@ public class HttpHeaders {
* @throws ParseException
* if there is no such header or the header value is not a formatted date
*/
public static Date getDateHeader(HttpMessage message, String name) throws ParseException {
public static Date getDateHeader(HttpHeader message, String name) throws ParseException {
String value = getHeader(message, name);
if (value == null) {
throw new ParseException("header not found: " + name, 0);
@ -699,7 +699,7 @@ public class HttpHeaders {
* @return the header value or the {@code defaultValue} if there is no such
* header or the header value is not a formatted date
*/
public static Date getDateHeader(HttpMessage message, String name, Date defaultValue) {
public static Date getDateHeader(HttpHeader message, String name, Date defaultValue) {
final String value = getHeader(message, name);
if (value == null) {
return defaultValue;
@ -718,7 +718,7 @@ public class HttpHeaders {
* The specified value is formatted as defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>
*/
public static void setDateHeader(HttpMessage message, String name, Date value) {
public static void setDateHeader(HttpHeader message, String name, Date value) {
if (value != null) {
message.setHeader(name, new HttpHeaderDateFormat().format(value));
} else {
@ -732,7 +732,7 @@ public class HttpHeaders {
* The specified values are formatted as defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>
*/
public static void setDateHeader(HttpMessage message, String name, Iterable<Date> values) {
public static void setDateHeader(HttpHeader message, String name, Iterable<Date> values) {
message.setHeader(name, values);
}
@ -741,13 +741,13 @@ public class HttpHeaders {
* value is formatted as defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>
*/
public static void addDateHeader(HttpMessage message, String name, Date value) {
public static void addDateHeader(HttpHeader message, String name, Date value) {
message.addHeader(name, value);
}
/**
* Returns the length of the content. Please note that this value is
* not retrieved from {@link HttpMessage#getContent()} but from the
* not retrieved from {@link HttpContent#getContent()} but from the
* {@code "Content-Length"} header, and thus they are independent from each
* other.
*
@ -757,7 +757,7 @@ public class HttpHeaders {
* if the message does not have the {@code "Content-Length"} header
* or its value is not a number
*/
public static long getContentLength(HttpMessage message) {
public static long getContentLength(HttpHeader message) {
String value = getHeader(message, Names.CONTENT_LENGTH);
if (value != null) {
return Long.parseLong(value);
@ -776,7 +776,7 @@ public class HttpHeaders {
/**
* Returns the length of the content. Please note that this value is
* not retrieved from {@link HttpMessage#getContent()} but from the
* not retrieved from {@link HttpContent#getContent()} but from the
* {@code "Content-Length"} header, and thus they are independent from each
* other.
*
@ -784,7 +784,7 @@ public class HttpHeaders {
* not have the {@code "Content-Length"} header or its value is not
* a number
*/
public static long getContentLength(HttpMessage message, long defaultValue) {
public static long getContentLength(HttpHeader message, long defaultValue) {
String contentLength = message.getHeader(Names.CONTENT_LENGTH);
if (contentLength != null) {
try {
@ -809,17 +809,17 @@ public class HttpHeaders {
* Returns the content length of the specified web socket message. If the
* specified message is not a web socket message, {@code -1} is returned.
*/
private static int getWebSocketContentLength(HttpMessage message) {
private static int getWebSocketContentLength(HttpHeader message) {
// WebSockset messages have constant content-lengths.
if (message instanceof HttpRequest) {
HttpRequest req = (HttpRequest) message;
if (message instanceof HttpRequestHeader) {
HttpRequestHeader req = (HttpRequestHeader) message;
if (HttpMethod.GET.equals(req.getMethod()) &&
req.containsHeader(Names.SEC_WEBSOCKET_KEY1) &&
req.containsHeader(Names.SEC_WEBSOCKET_KEY2)) {
return 8;
}
} else if (message instanceof HttpResponse) {
HttpResponse res = (HttpResponse) message;
} else if (message instanceof HttpResponseHeader) {
HttpResponseHeader res = (HttpResponseHeader) message;
if (res.getStatus().getCode() == 101 &&
res.containsHeader(Names.SEC_WEBSOCKET_ORIGIN) &&
res.containsHeader(Names.SEC_WEBSOCKET_LOCATION)) {
@ -834,14 +834,14 @@ public class HttpHeaders {
/**
* Sets the {@code "Content-Length"} header.
*/
public static void setContentLength(HttpMessage message, long length) {
public static void setContentLength(HttpHeader message, long length) {
message.setHeader(Names.CONTENT_LENGTH, length);
}
/**
* Returns the value of the {@code "Host"} header.
*/
public static String getHost(HttpMessage message) {
public static String getHost(HttpHeader message) {
return message.getHeader(Names.HOST);
}
@ -849,14 +849,14 @@ public class HttpHeaders {
* Returns the value of the {@code "Host"} header. If there is no such
* header, the {@code defaultValue} is returned.
*/
public static String getHost(HttpMessage message, String defaultValue) {
public static String getHost(HttpHeader message, String defaultValue) {
return getHeader(message, Names.HOST, defaultValue);
}
/**
* Sets the {@code "Host"} header.
*/
public static void setHost(HttpMessage message, String value) {
public static void setHost(HttpHeader message, String value) {
message.setHeader(Names.HOST, value);
}
@ -866,7 +866,7 @@ public class HttpHeaders {
* @throws ParseException
* if there is no such header or the header value is not a formatted date
*/
public static Date getDate(HttpMessage message) throws ParseException {
public static Date getDate(HttpHeader message) throws ParseException {
return getDateHeader(message, Names.DATE);
}
@ -875,14 +875,14 @@ public class HttpHeaders {
* header or the header is not a formatted date, the {@code defaultValue}
* is returned.
*/
public static Date getDate(HttpMessage message, Date defaultValue) {
public static Date getDate(HttpHeader message, Date defaultValue) {
return getDateHeader(message, Names.DATE, defaultValue);
}
/**
* Sets the {@code "Date"} header.
*/
public static void setDate(HttpMessage message, Date value) {
public static void setDate(HttpHeader message, Date value) {
if (value != null) {
message.setHeader(Names.DATE, new HttpHeaderDateFormat().format(value));
} else {
@ -894,9 +894,9 @@ public class HttpHeaders {
* Returns {@code true} if and only if the specified message contains the
* {@code "Expect: 100-continue"} header.
*/
public static boolean is100ContinueExpected(HttpMessage message) {
public static boolean is100ContinueExpected(HttpHeader message) {
// Expect: 100-continue is for requests only.
if (!(message instanceof HttpRequest)) {
if (!(message instanceof HttpRequestHeader)) {
return false;
}
@ -928,7 +928,7 @@ public class HttpHeaders {
* If there is any existing {@code "Expect"} header, they are replaced with
* the new one.
*/
public static void set100ContinueExpected(HttpMessage message) {
public static void set100ContinueExpected(HttpHeader message) {
set100ContinueExpected(message, true);
}
@ -939,7 +939,7 @@ public class HttpHeaders {
* {@code "Expect"} headers are removed. Otherwise, all {@code "Expect"}
* headers are removed completely.
*/
public static void set100ContinueExpected(HttpMessage message, boolean set) {
public static void set100ContinueExpected(HttpHeader message, boolean set) {
if (set) {
message.setHeader(Names.EXPECT, Values.CONTINUE);
} else {
@ -947,6 +947,163 @@ public class HttpHeaders {
}
}
/**
* Set the headers on the dst like they are set on the src
*/
public static void setHeaders(HttpHeader src, HttpHeader dst) {
for (String name: src.getHeaderNames()) {
dst.setHeader(name, src.getHeaders(name));
}
}
/**
* Validates the name of a header
*
* @param headerName The header name being validated
*/
static void validateHeaderName(String headerName) {
//Check to see if the name is null
if (headerName == null) {
throw new NullPointerException("Header names cannot be null");
}
//Go through each of the characters in the name
for (int index = 0; index < headerName.length(); index ++) {
//Actually get the character
char character = headerName.charAt(index);
//Check to see if the character is not an ASCII character
if (character > 127) {
throw new IllegalArgumentException(
"Header name cannot contain non-ASCII characters: " + headerName);
}
//Check for prohibited characters.
switch (character) {
case '\t': case '\n': case 0x0b: case '\f': case '\r':
case ' ': case ',': case ':': case ';': case '=':
throw new IllegalArgumentException(
"Header name cannot contain the following prohibited characters: " +
"=,;: \\t\\r\\n\\v\\f: " + headerName);
}
}
}
/**
* Validates the specified header value
*
* @param headerValue The value being validated
*/
static void validateHeaderValue(String headerValue) {
//Check to see if the value is null
if (headerValue == null) {
throw new NullPointerException("Header values cannot be null");
}
/*
* Set up the state of the validation
*
* States are as follows:
*
* 0: Previous character was neither CR nor LF
* 1: The previous character was CR
* 2: The previous character was LF
*/
int state = 0;
//Start looping through each of the character
for (int index = 0; index < headerValue.length(); index ++) {
char character = headerValue.charAt(index);
//Check the absolutely prohibited characters.
switch (character) {
case 0x0b: // Vertical tab
throw new IllegalArgumentException(
"Header value contains a prohibited character '\\v': " + headerValue);
case '\f':
throw new IllegalArgumentException(
"Header value contains a prohibited character '\\f': " + headerValue);
}
// Check the CRLF (HT | SP) pattern
switch (state) {
case 0:
switch (character) {
case '\r':
state = 1;
break;
case '\n':
state = 2;
break;
}
break;
case 1:
switch (character) {
case '\n':
state = 2;
break;
default:
throw new IllegalArgumentException(
"Only '\\n' is allowed after '\\r': " + headerValue);
}
break;
case 2:
switch (character) {
case '\t': case ' ':
state = 0;
break;
default:
throw new IllegalArgumentException(
"Only ' ' and '\\t' are allowed after '\\n': " + headerValue);
}
}
}
if (state != 0) {
throw new IllegalArgumentException(
"Header value must not end with '\\r' or '\\n':" + headerValue);
}
}
/**
* Checks to see if the transfer encoding in a specified {@link HttpHeader} is chunked
*
* @param message The message to check
* @return True if transfer encoding is chunked, otherwise false
*/
public static boolean isTransferEncodingChunked(HttpHeader message) {
List<String> transferEncodingHeaders = message.getHeaders(Names.TRANSFER_ENCODING);
if (transferEncodingHeaders.isEmpty()) {
return false;
}
for (String value: transferEncodingHeaders) {
if (value.equalsIgnoreCase(Values.CHUNKED)) {
return true;
}
}
return false;
}
public static void removeTransferEncodingChunked(HttpHeader m) {
List<String> values = m.getHeaders(Names.TRANSFER_ENCODING);
values.remove(Values.CHUNKED);
if (values.isEmpty()) {
m.removeHeader(Names.TRANSFER_ENCODING);
} else {
m.setHeader(Names.TRANSFER_ENCODING, values);
}
}
public static void setTransferEncodingChunked(HttpHeader m) {
addHeader(m, Names.TRANSFER_ENCODING, Values.CHUNKED);
removeHeader(m, Names.CONTENT_LENGTH);
}
public static boolean isContentLengthSet(HttpHeader m) {
List<String> contentLength = m.getHeaders(Names.CONTENT_LENGTH);
return !contentLength.isEmpty();
}
private static final int BUCKET_SIZE = 17;
private static int hash(String name) {
@ -1003,14 +1160,14 @@ public class HttpHeaders {
head.before = head.after = head;
}
void validateHeaderName(String name) {
HttpCodecUtil.validateHeaderName(name);
void validateHeaderName0(String headerName) {
validateHeaderName(headerName);
}
void addHeader(final String name, final Object value) {
validateHeaderName(name);
validateHeaderName0(name);
String strVal = toString(value);
HttpCodecUtil.validateHeaderValue(strVal);
validateHeaderValue(strVal);
int h = hash(name);
int i = index(h);
addHeader0(h, i, name, strVal);
@ -1073,9 +1230,9 @@ public class HttpHeaders {
}
void setHeader(final String name, final Object value) {
validateHeaderName(name);
validateHeaderName0(name);
String strVal = toString(value);
HttpCodecUtil.validateHeaderValue(strVal);
validateHeaderValue(strVal);
int h = hash(name);
int i = index(h);
removeHeader0(h, i, name);
@ -1087,7 +1244,7 @@ public class HttpHeaders {
throw new NullPointerException("values");
}
validateHeaderName(name);
validateHeaderName0(name);
int h = hash(name);
int i = index(h);
@ -1098,7 +1255,7 @@ public class HttpHeaders {
break;
}
String strVal = toString(v);
HttpCodecUtil.validateHeaderValue(strVal);
validateHeaderValue(strVal);
addHeader0(h, i, name, strVal);
}
}
@ -1234,7 +1391,7 @@ public class HttpHeaders {
if (value == null) {
throw new NullPointerException("value");
}
HttpCodecUtil.validateHeaderValue(value);
validateHeaderValue(value);
String oldValue = this.value;
this.value = value;
return oldValue;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -15,188 +15,9 @@
*/
package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* An interface that defines a HTTP message, providing common properties for
* {@link HttpRequest} and {@link HttpResponse}.
* @see HttpResponse
* @see HttpRequest
* @see HttpHeaders
*
* @apiviz.landmark
* @apiviz.has io.netty.handler.codec.http.HttpChunk oneway - - is followed by
* Combines {@link HttpMessage} and {@link LastHttpContent} into one
* message. So it represent a <i>complete</i> http message.
*/
public interface HttpMessage extends HttpObject {
/**
* Returns the value of a header with the specified name. If there are
* more than one values for the specified name, the first value is returned.
*
* @param name The name of the header to search
* @return The first header value or {@code null} if there is no such header
*/
String getHeader(String name);
/**
* Returns the values of headers with the specified name
*
* @param name The name of the headers to search
* @return A {@link List} of header values which will be empty if no values
* are found
*/
List<String> getHeaders(String name);
/**
* Returns the all headers that this message contains.
*
* @return A {@link List} of the header name-value entries, which will be
* empty if no pairs are found
*/
List<Map.Entry<String, String>> getHeaders();
/**
* Checks to see if there is a header with the specified name
*
* @param name The name of the header to search for
* @return True if at least one header is found
*/
boolean containsHeader(String name);
/**
* Gets a {@link Set} of all header names that this message contains
*
* @return A {@link Set} of all header names
*/
Set<String> getHeaderNames();
/**
* Returns the protocol version of this {@link HttpMessage}
*
* @return The protocol version
*/
HttpVersion getProtocolVersion();
/**
* Sets the protocol version of this {@link HttpMessage}
*
* @param version The version to set
*/
void setProtocolVersion(HttpVersion version);
/**
* Returns the content of this {@link HttpMessage}.
*
* If there is no content or {@link #getTransferEncoding()} returns
* {@link HttpTransferEncoding#STREAMED} or {@link HttpTransferEncoding#CHUNKED},
* an {@link Unpooled#EMPTY_BUFFER} is returned.
*
* @return A {@link ByteBuf} containing this {@link HttpMessage}'s content
*/
ByteBuf getContent();
/**
* Sets the content of this {@link HttpMessage}.
*
* If {@code null} is specified, the content of this message
* will be set to {@link Unpooled#EMPTY_BUFFER}
*
* @param content The {@link ByteBuf} containing the content to use
*/
void setContent(ByteBuf content);
/**
* Adds a new header with the specified name and value.
*
* If the specified value is not a {@link String}, it is converted
* into a {@link String} by {@link Object#toString()}, except in the cases
* of {@link Date} and {@link Calendar}, which are formatted to the date
* format defined in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
*
* @param name The name of the header being added
* @param value The value of the header being added
*/
void addHeader(String name, Object value);
/**
* Sets a header with the specified name and value.
*
* If there is an existing header with the same name, it is removed.
* If the specified value is not a {@link String}, it is converted into a
* {@link String} by {@link Object#toString()}, except for {@link Date}
* and {@link Calendar}, which are formatted to the date format defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
*
* @param name The name of the header being set
* @param value The value of the header being set
*/
void setHeader(String name, Object value);
/**
* Sets a header with the specified name and values.
*
* If there is an existing header with the same name, it is removed.
* This method can be represented approximately as the following code:
* <pre>
* m.removeHeader(name);
* for (Object v: values) {
* if (v == null) {
* break;
* }
* m.addHeader(name, v);
* }
* </pre>
*
* @param name The name of the headers being set
* @param values The values of the headers being set
*/
void setHeader(String name, Iterable<?> values);
/**
* Removes the header with the specified name.
*
* @param name The name of the header to remove
*/
void removeHeader(String name);
/**
* Removes all headers from this {@link HttpMessage}.
*/
void clearHeaders();
/**
* Returns the transfer encoding of this {@link HttpMessage}.
* <ul>
* <li>{@link HttpTransferEncoding#CHUNKED} - an HTTP message whose {@code "Transfer-Encoding"}
* is {@code "chunked"}.</li>
* <li>{@link HttpTransferEncoding#STREAMED} - an HTTP message which is not chunked, but
* is followed by {@link HttpChunk}s that represent its content. {@link #getContent()}
* returns an empty buffer.</li>
* <li>{@link HttpTransferEncoding#SINGLE} - a self-contained HTTP message which is not chunked
* and {@link #getContent()} returns the full content.</li>
* </ul>
*/
HttpTransferEncoding getTransferEncoding();
/**
* Sets the transfer encoding of this {@link HttpMessage}.
* <ul>
* <li>If set to {@link HttpTransferEncoding#CHUNKED}, the {@code "Transfer-Encoding: chunked"}
* header is set and the {@code "Content-Length"} header and the content of this message are
* removed automatically.</li>
* <li>If set to {@link HttpTransferEncoding#STREAMED}, the {@code "Transfer-Encoding: chunked"}
* header and the content of this message are removed automatically.</li>
* <li>If set to {@link HttpTransferEncoding#SINGLE}, the {@code "Transfer-Encoding: chunked"}
* header is removed automatically.</li>
* </ul>
* For more information about what {@link HttpTransferEncoding} means, see {@link #getTransferEncoding()}.
*/
void setTransferEncoding(HttpTransferEncoding te);
public interface HttpMessage extends HttpHeader, LastHttpContent {
}

View File

@ -30,16 +30,16 @@ import io.netty.util.CharsetUtil;
import java.util.Map.Entry;
/**
* A {@link ChannelHandler} that aggregates an {@link HttpMessage}
* and its following {@link HttpChunk}s into a single {@link HttpMessage} with
* no following {@link HttpChunk}s. It is useful when you don't want to take
* A {@link ChannelHandler} that aggregates an {@link HttpHeader}
* and its following {@link HttpContent}s into a single {@link HttpHeader} with
* no following {@link HttpContent}s. It is useful when you don't want to take
* care of HTTP messages whose transfer encoding is 'chunked'. Insert this
* handler after {@link HttpMessageDecoder} in the {@link ChannelPipeline}:
* handler after {@link HttpObjectDecoder} in the {@link ChannelPipeline}:
* <pre>
* {@link ChannelPipeline} p = ...;
* ...
* p.addLast("decoder", new {@link HttpRequestDecoder}());
* p.addLast("aggregator", <b>new {@link HttpChunkAggregator}(1048576)</b>);
* p.addLast("aggregator", <b>new {@link HttpObjectAggregator}(1048576)</b>);
* ...
* p.addLast("encoder", new {@link HttpResponseEncoder}());
* p.addLast("handler", new HttpRequestHandler());
@ -47,7 +47,7 @@ import java.util.Map.Entry;
* @apiviz.landmark
* @apiviz.has io.netty.handler.codec.http.HttpChunk oneway - - filters out
*/
public class HttpChunkAggregator extends MessageToMessageDecoder<HttpObject> {
public class HttpObjectAggregator extends MessageToMessageDecoder<HttpObject> {
public static final int DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS = 1024;
private static final ByteBuf CONTINUE = Unpooled.copiedBuffer(
"HTTP/1.1 100 Continue\r\n\r\n", CharsetUtil.US_ASCII);
@ -66,7 +66,7 @@ public class HttpChunkAggregator extends MessageToMessageDecoder<HttpObject> {
* If the length of the aggregated content exceeds this value,
* a {@link TooLongFrameException} will be raised.
*/
public HttpChunkAggregator(int maxContentLength) {
public HttpObjectAggregator(int maxContentLength) {
super(HttpObject.class);
if (maxContentLength <= 0) {
@ -113,8 +113,8 @@ public class HttpChunkAggregator extends MessageToMessageDecoder<HttpObject> {
protected Object decode(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
HttpMessage currentMessage = this.currentMessage;
if (msg instanceof HttpMessage) {
HttpMessage m = (HttpMessage) msg;
if (msg instanceof HttpHeader) {
HttpHeader m = (HttpHeader) msg;
// Handle the 'Expect: 100-continue' header if necessary.
// TODO: Respond with 413 Request Entity Too Large
@ -126,36 +126,37 @@ public class HttpChunkAggregator extends MessageToMessageDecoder<HttpObject> {
}
if (!m.getDecoderResult().isSuccess()) {
m.setTransferEncoding(HttpTransferEncoding.SINGLE);
removeTransferEncodingChunked(m);
this.currentMessage = null;
return m;
}
if (msg instanceof HttpRequestHeader) {
HttpRequestHeader header = (HttpRequestHeader) msg;
this.currentMessage = new DefaultHttpRequest(header.getProtocolVersion(),
header.getMethod(), header.getUri());
} else {
HttpResponseHeader header = (HttpResponseHeader) msg;
this.currentMessage = new DefaultHttpResponse(header.getProtocolVersion(), header.getStatus());
}
for (String name: m.getHeaderNames()) {
this.currentMessage.setHeader(name, m.getHeaders(name));
}
// A streamed message - initialize the cumulative buffer, and wait for incoming chunks.
removeTransferEncodingChunked(m);
this.currentMessage.setContent(Unpooled.compositeBuffer(maxCumulationBufferComponents));
return null;
switch (m.getTransferEncoding()) {
case SINGLE:
this.currentMessage = null;
return m;
case STREAMED:
case CHUNKED:
// A streamed message - initialize the cumulative buffer, and wait for incoming chunks.
m.setTransferEncoding(HttpTransferEncoding.SINGLE);
m.setContent(Unpooled.compositeBuffer(maxCumulationBufferComponents));
this.currentMessage = m;
return null;
default:
throw new Error();
}
} else if (msg instanceof HttpChunk) {
} else if (msg instanceof HttpContent) {
// Sanity check
if (currentMessage == null) {
throw new IllegalStateException(
"received " + HttpChunk.class.getSimpleName() +
" without " + HttpMessage.class.getSimpleName() +
"received " + HttpContent.class.getSimpleName() +
" without " + HttpHeader.class.getSimpleName() +
" or last message's transfer encoding was 'SINGLE'");
}
// Merge the received chunk into the content of the current message.
HttpChunk chunk = (HttpChunk) msg;
HttpContent chunk = (HttpContent) msg;
ByteBuf content = currentMessage.getContent();
if (content.readableBytes() > maxContentLength - chunk.getContent().readableBytes()) {
@ -177,15 +178,15 @@ public class HttpChunkAggregator extends MessageToMessageDecoder<HttpObject> {
DecoderResult.partialFailure(chunk.getDecoderResult().cause()));
last = true;
} else {
last = chunk.isLast();
last = msg instanceof LastHttpContent;
}
if (last) {
this.currentMessage = null;
// Merge trailing headers into the message.
if (chunk instanceof HttpChunkTrailer) {
HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
if (chunk instanceof LastHttpContent) {
LastHttpContent trailer = (LastHttpContent) chunk;
for (Entry<String, String> header: trailer.getHeaders()) {
currentMessage.setHeader(header.getKey(), header.getValue());
}
@ -203,8 +204,8 @@ public class HttpChunkAggregator extends MessageToMessageDecoder<HttpObject> {
}
} else {
throw new IllegalStateException(
"Only " + HttpMessage.class.getSimpleName() + " and " +
HttpChunk.class.getSimpleName() + " are accepted: " + msg.getClass().getName());
"Only " + HttpHeader.class.getSimpleName() + " and " +
HttpContent.class.getSimpleName() + " are accepted: " + msg.getClass().getName());
}
}

View File

@ -26,8 +26,8 @@ import io.netty.handler.codec.TooLongFrameException;
import java.util.List;
/**
* Decodes {@link ByteBuf}s into {@link HttpMessage}s and
* {@link HttpChunk}s.
* Decodes {@link ByteBuf}s into {@link HttpHeader}s and
* {@link HttpContent}s.
*
* <h3>Parameters that prevents excessive memory consumption</h3>
* <table border="1">
@ -50,7 +50,7 @@ import java.util.List;
* <td>{@code maxChunkSize}</td>
* <td>The maximum length of the content or each chunk. If the content length
* (or the length of each chunk) exceeds this value, the content or chunk
* will be split into multiple {@link HttpChunk}s whose length is
* will be split into multiple {@link HttpContent}s whose length is
* {@code maxChunkSize} at maximum.</td>
* </tr>
* </table>
@ -59,8 +59,8 @@ import java.util.List;
*
* If the content of an HTTP message is greater than {@code maxChunkSize} or
* the transfer encoding of the HTTP message is 'chunked', this decoder
* generates one {@link HttpMessage} instance and its following
* {@link HttpChunk}s per single HTTP message to avoid excessive memory
* generates one {@link HttpHeader} instance and its following
* {@link HttpContent}s per single HTTP message to avoid excessive memory
* consumption. For example, the following HTTP message:
* <pre>
* GET / HTTP/1.1
@ -74,17 +74,16 @@ import java.util.List;
* Content-MD5: ...
* <i>[blank line]</i>
* </pre>
* triggers {@link HttpRequestDecoder} to generate 4 objects:
* triggers {@link HttpRequestDecoder} to generate 3 objects:
* <ol>
* <li>An {@link HttpRequest} whose {@link HttpMessage#getTransferEncoding()}
* property is {@link HttpTransferEncoding#CHUNKED},</li>
* <li>The first {@link HttpChunk} whose content is {@code 'abcdefghijklmnopqrstuvwxyz'},</li>
* <li>The second {@link HttpChunk} whose content is {@code '1234567890abcdef'}, and</li>
* <li>An {@link HttpChunkTrailer} which marks the end of the content.</li>
* <li>An {@link HttpRequestHeader},</li>
* <li>The first {@link HttpContent} whose content is {@code 'abcdefghijklmnopqrstuvwxyz'},</li>
* <li>The second {@link LastHttpContent} whose content is {@code '1234567890abcdef'}, which marks
* the end of the content.</li>
* </ol>
*
* If you prefer not to handle {@link HttpChunk}s by yourself for your
* convenience, insert {@link HttpChunkAggregator} after this decoder in the
* If you prefer not to handle {@link HttpContent}s by yourself for your
* convenience, insert {@link HttpObjectAggregator} after this decoder in the
* {@link ChannelPipeline}. However, please note that your server might not
* be as memory efficient as without the aggregator.
*
@ -98,19 +97,19 @@ import java.util.List;
* implement all abstract methods properly.
* @apiviz.landmark
*/
public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDecoder.State> {
public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecoder.State> {
private final int maxInitialLineLength;
private final int maxHeaderSize;
private final int maxChunkSize;
private HttpMessage message;
private ByteBuf content;
private HttpHeader message;
private long chunkSize;
private int headerSize;
private int contentRead;
/**
* The internal state of {@link HttpMessageDecoder}.
* The internal state of {@link HttpObjectDecoder}.
* <em>Internal use only</em>.
* @apiviz.exclude
*/
@ -135,14 +134,14 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
* {@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and
* {@code maxChunkSize (8192)}.
*/
protected HttpMessageDecoder() {
protected HttpObjectDecoder() {
this(4096, 8192, 8192);
}
/**
* Creates a new instance with the specified parameters.
*/
protected HttpMessageDecoder(
protected HttpObjectDecoder(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
super(State.SKIP_CONTROL_CHARS);
@ -188,6 +187,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
message = createMessage(initialLine);
checkpoint(State.READ_HEADER);
} catch (Exception e) {
return invalidMessage(e);
}
@ -213,7 +213,6 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
if (contentLength > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
// Generate HttpMessage first. HttpChunks will follow.
checkpoint(State.READ_FIXED_LENGTH_CONTENT_AS_CHUNKS);
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
// chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT_AS_CHUNKS
// state reads data chunk by chunk.
chunkSize = HttpHeaders.getContentLength(message, -1);
@ -224,7 +223,6 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
if (buffer.readableBytes() > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
// Generate HttpMessage first. HttpChunks will follow.
checkpoint(State.READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS);
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
return message;
}
break;
@ -241,13 +239,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
if (toRead > maxChunkSize) {
toRead = maxChunkSize;
}
if (message.getTransferEncoding() != HttpTransferEncoding.STREAMED) {
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
return new Object[] { message, new DefaultHttpChunk(buffer.readBytes(toRead))};
} else {
return new DefaultHttpChunk(buffer.readBytes(toRead));
}
return new Object[] { message, new DefaultHttpContent(buffer.readBytes(toRead))};
}
case READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS: {
// Keep reading data as a chunk until the end of connection is reached.
@ -255,17 +247,12 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
if (toRead > maxChunkSize) {
toRead = maxChunkSize;
}
HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(toRead));
ByteBuf content = buffer.readBytes(toRead);
if (!buffer.readable()) {
// Reached to the end of the connection.
reset();
if (!chunk.isLast()) {
// Append the last chunk.
return new Object[] { chunk, HttpChunk.LAST_CHUNK };
}
return new DefaultLastHttpContent(content);
}
return chunk;
return new DefaultHttpContent(content);
}
case READ_FIXED_LENGTH_CONTENT: {
return readFixedLengthContent(buffer);
@ -291,7 +278,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
if (toRead > chunkSize) {
toRead = (int) chunkSize;
}
HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(toRead));
ByteBuf content = buffer.readBytes(toRead);
if (chunkSize > toRead) {
chunkSize -= toRead;
} else {
@ -302,12 +289,9 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
if (chunkSize == 0) {
// Read all content.
reset();
if (!chunk.isLast()) {
// Append the last chunk.
return new Object[] { chunk, HttpChunk.LAST_CHUNK };
}
return new DefaultLastHttpContent(content);
}
return chunk;
return new DefaultHttpContent(content);
}
/**
* everything else after this point takes care of reading chunked content. basically, read chunk size,
@ -331,7 +315,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
}
case READ_CHUNKED_CONTENT: {
assert chunkSize <= Integer.MAX_VALUE;
HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes((int) chunkSize));
HttpContent chunk = new DefaultHttpContent(buffer.readBytes((int) chunkSize));
checkpoint(State.READ_CHUNK_DELIMITER);
return chunk;
}
@ -357,7 +341,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
if (toRead > readLimit) {
toRead = readLimit;
}
HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(toRead));
HttpContent chunk = new DefaultHttpContent(buffer.readBytes(toRead));
if (chunkSize > toRead) {
chunkSize -= toRead;
} else {
@ -370,9 +354,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
checkpoint(State.READ_CHUNK_DELIMITER);
}
if (!chunk.isLast()) {
return chunk;
}
return chunk;
}
case READ_CHUNK_DELIMITER: {
for (;;) {
@ -391,7 +373,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
}
}
case READ_CHUNK_FOOTER: try {
HttpChunkTrailer trailer = readTrailingHeaders(buffer);
LastHttpContent trailer = readTrailingHeaders(buffer);
if (maxChunkSize == 0) {
// Chunked encoding disabled.
return reset();
@ -414,9 +396,9 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
}
}
protected boolean isContentAlwaysEmpty(HttpMessage msg) {
if (msg instanceof HttpResponse) {
HttpResponse res = (HttpResponse) msg;
protected boolean isContentAlwaysEmpty(HttpHeader msg) {
if (msg instanceof HttpResponseHeader) {
HttpResponseHeader res = (HttpResponseHeader) msg;
int code = res.getStatus().getCode();
// Correctly handle return codes of 1xx.
@ -441,20 +423,25 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
}
private Object reset() {
HttpMessage message = this.message;
HttpHeader message = this.message;
ByteBuf content = this.content;
LastHttpContent httpContent;
if (content != null) {
message.setContent(content);
this.content = null;
if (content == null || !content.readable()) {
httpContent = HttpContent.LAST_CONTENT;
} else {
httpContent = new DefaultLastHttpContent(content);
}
Object[] messages = { message, httpContent };
this.content = null;
this.message = null;
checkpoint(State.SKIP_CONTROL_CHARS);
return message;
return messages;
}
private HttpMessage invalidMessage(Exception cause) {
private HttpHeader invalidMessage(Exception cause) {
checkpoint(State.BAD_MESSAGE);
if (message != null) {
message.setDecoderResult(DecoderResult.partialFailure(cause));
@ -465,9 +452,9 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
return message;
}
private HttpChunk invalidChunk(Exception cause) {
private HttpContent invalidChunk(Exception cause) {
checkpoint(State.BAD_MESSAGE);
HttpChunk chunk = new DefaultHttpChunk(Unpooled.EMPTY_BUFFER);
HttpContent chunk = new DefaultHttpContent(Unpooled.EMPTY_BUFFER);
chunk.setDecoderResult(DecoderResult.failure(cause));
return chunk;
}
@ -493,12 +480,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
}
contentRead += toRead;
if (length < contentRead) {
if (message.getTransferEncoding() != HttpTransferEncoding.STREAMED) {
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
return new Object[] {message, new DefaultHttpChunk(buffer.readBytes(toRead))};
} else {
return new DefaultHttpChunk(buffer.readBytes(toRead));
}
return new Object[] {message, new DefaultHttpContent(buffer.readBytes(toRead))};
}
if (content == null) {
content = buffer.readBytes((int) length);
@ -510,7 +492,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
private State readHeaders(ByteBuf buffer) {
headerSize = 0;
final HttpMessage message = this.message;
final HttpHeader message = this.message;
String line = readHeader(buffer);
String name = null;
String value = null;
@ -541,10 +523,9 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
State nextState;
if (isContentAlwaysEmpty(message)) {
message.setTransferEncoding(HttpTransferEncoding.SINGLE);
HttpHeaders.removeTransferEncodingChunked(message);
nextState = State.SKIP_CONTROL_CHARS;
} else if (HttpCodecUtil.isTransferEncodingChunked(message)) {
message.setTransferEncoding(HttpTransferEncoding.CHUNKED);
} else if (HttpHeaders.isTransferEncodingChunked(message)) {
nextState = State.READ_CHUNK_SIZE;
} else if (HttpHeaders.getContentLength(message, -1) >= 0) {
nextState = State.READ_FIXED_LENGTH_CONTENT;
@ -554,12 +535,12 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
return nextState;
}
private HttpChunkTrailer readTrailingHeaders(ByteBuf buffer) {
private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
headerSize = 0;
String line = readHeader(buffer);
String lastHeader = null;
if (!line.isEmpty()) {
HttpChunkTrailer trailer = new DefaultHttpChunkTrailer();
LastHttpContent trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
do {
char firstChar = line.charAt(0);
if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
@ -588,7 +569,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
return trailer;
}
return HttpChunk.LAST_CHUNK;
return HttpContent.LAST_CONTENT;
}
private String readHeader(ByteBuf buffer) {
@ -631,8 +612,8 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
}
protected abstract boolean isDecodingRequest();
protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
protected abstract HttpMessage createInvalidMessage();
protected abstract HttpHeader createMessage(String[] initialLine) throws Exception;
protected abstract HttpHeader createInvalidMessage();
private static int getChunkSize(String hex) {
hex = hex.trim();

View File

@ -20,13 +20,12 @@ import static io.netty.handler.codec.http.HttpConstants.*;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.UnsupportedMessageTypeException;
import io.netty.util.CharsetUtil;
import java.util.Map;
/**
* Encodes an {@link HttpMessage} or an {@link HttpChunk} into
* Encodes an {@link HttpHeader} or an {@link HttpContent} into
* a {@link ByteBuf}.
*
* <h3>Extensibility</h3>
@ -39,68 +38,46 @@ import java.util.Map;
* implement all abstract methods properly.
* @apiviz.landmark
*/
public abstract class HttpMessageEncoder extends MessageToByteEncoder<Object> {
public abstract class HttpObjectEncoder<H extends HttpHeader> extends MessageToByteEncoder<Object> {
private static final ByteBuf LAST_CHUNK =
copiedBuffer("0\r\n\r\n", CharsetUtil.US_ASCII);
private HttpTransferEncoding lastTE;
private boolean chunked;
/**
* Creates a new instance.
*/
protected HttpMessageEncoder() {
protected HttpObjectEncoder() {
super(HttpObject.class);
}
@SuppressWarnings("unchecked")
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
if (msg instanceof HttpMessage) {
HttpMessage m = (HttpMessage) msg;
HttpTransferEncoding te = m.getTransferEncoding();
lastTE = te;
// Calling setTransferEncoding() will sanitize the headers and the content.
// For example, it will remove the cases such as 'Transfer-Encoding' and 'Content-Length'
// coexist. It also removes the content if the transferEncoding is not SINGLE.
m.setTransferEncoding(te);
if (msg instanceof HttpHeader) {
HttpHeader m = (HttpHeader) msg;
chunked = HttpHeaders.isTransferEncodingChunked(m);
// Encode the message.
out.markWriterIndex();
encodeInitialLine(out, m);
encodeInitialLine(out, (H) m);
encodeHeaders(out, m);
out.writeByte(CR);
out.writeByte(LF);
}
ByteBuf content = m.getContent();
out.writeBytes(content, content.readerIndex(), content.readableBytes());
} else if (msg instanceof HttpChunk) {
HttpChunk chunk = (HttpChunk) msg;
HttpTransferEncoding te = lastTE;
if (te == null) {
throw new IllegalArgumentException("HttpChunk must follow an HttpMessage.");
}
if (msg instanceof HttpContent) {
HttpContent chunk = (HttpContent) msg;
switch (te) {
case SINGLE:
throw new IllegalArgumentException(
"The transfer encoding of the last encoded HttpMessage is SINGLE.");
case STREAMED: {
if (!chunked) {
ByteBuf content = chunk.getContent();
out.writeBytes(content, content.readerIndex(), content.readableBytes());
break;
}
case CHUNKED:
if (chunk.isLast()) {
if (chunk instanceof HttpChunkTrailer) {
out.writeByte((byte) '0');
out.writeByte(CR);
out.writeByte(LF);
encodeTrailingHeaders(out, (HttpChunkTrailer) chunk);
out.writeByte(CR);
out.writeByte(LF);
} else {
out.writeBytes(LAST_CHUNK, LAST_CHUNK.readerIndex(), LAST_CHUNK.readableBytes());
}
} else {
if (chunk instanceof LastHttpContent) {
out.writeByte((byte) '0');
out.writeByte(CR);
out.writeByte(LF);
encodeTrailingHeaders(out, (LastHttpContent) chunk);
out.writeByte(CR);
out.writeByte(LF);
} else {
ByteBuf content = chunk.getContent();
int contentLength = content.readableBytes();
@ -112,18 +89,16 @@ public abstract class HttpMessageEncoder extends MessageToByteEncoder<Object> {
out.writeByte(LF);
}
}
} else {
throw new UnsupportedMessageTypeException(msg, HttpMessage.class, HttpChunk.class);
}
}
private static void encodeHeaders(ByteBuf buf, HttpMessage message) {
private static void encodeHeaders(ByteBuf buf, HttpHeader message) {
for (Map.Entry<String, String> h: message.getHeaders()) {
encodeHeader(buf, h.getKey(), h.getValue());
}
}
private static void encodeTrailingHeaders(ByteBuf buf, HttpChunkTrailer trailer) {
private static void encodeTrailingHeaders(ByteBuf buf, LastHttpContent trailer) {
for (Map.Entry<String, String> h: trailer.getHeaders()) {
encodeHeader(buf, h.getKey(), h.getValue());
}
@ -138,5 +113,5 @@ public abstract class HttpMessageEncoder extends MessageToByteEncoder<Object> {
buf.writeByte(LF);
}
protected abstract void encodeInitialLine(ByteBuf buf, HttpMessage message) throws Exception;
protected abstract void encodeInitialLine(ByteBuf buf, H message) throws Exception;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -15,49 +15,9 @@
*/
package io.netty.handler.codec.http;
/**
* An HTTP request.
*
* <h3>Accessing Query Parameters and Cookie</h3>
* <p>
* Unlike the Servlet API, a query string is constructed and decomposed by
* {@link QueryStringEncoder} and {@link QueryStringDecoder}. {@link Cookie}
* support is also provided separately via {@link CookieDecoder}, {@link ClientCookieEncoder},
* and {@link @ServerCookieEncoder}.
*
* @see HttpResponse
* @see ClientCookieEncoder
* @see ServerCookieEncoder
* @see CookieDecoder
* Combinate the {@link HttpRequestHeader} and {@link HttpMessage}, so the request is a <i>complete</i> HTTP
* request.
*/
public interface HttpRequest extends HttpMessage {
/**
* Returns the {@link HttpMethod} of this {@link HttpRequest}.
*
* @return The {@link HttpMethod} of this {@link HttpRequest}
*/
HttpMethod getMethod();
/**
* Sets the {@link HttpMethod} of this {@link HttpRequest}.
*
* @param method The {@link HttpMethod} to set
*/
void setMethod(HttpMethod method);
/**
* Returns the requested URI (or alternatively, path)
*
* @return The URI being requested
*/
String getUri();
/**
* Sets the URI (or alternatively, path) being requested.
*
* @param uri The URI being requested
*/
void setUri(String uri);
public interface HttpRequest extends HttpRequestHeader, HttpMessage {
}

View File

@ -21,7 +21,7 @@ import io.netty.handler.codec.TooLongFrameException;
/**
* Decodes {@link ByteBuf}s into {@link HttpRequest}s and {@link HttpChunk}s.
* Decodes {@link ByteBuf}s into {@link HttpRequestHeader}s and {@link HttpContent}s.
*
* <h3>Parameters that prevents excessive memory consumption</h3>
* <table border="1">
@ -44,15 +44,15 @@ import io.netty.handler.codec.TooLongFrameException;
* <td>The maximum length of the content or each chunk. If the content length
* exceeds this value, the transfer encoding of the decoded request will be
* converted to 'chunked' and the content will be split into multiple
* {@link HttpChunk}s. If the transfer encoding of the HTTP request is
* {@link HttpContent}s. If the transfer encoding of the HTTP request is
* 'chunked' already, each chunk will be split into smaller chunks if the
* length of the chunk exceeds this value. If you prefer not to handle
* {@link HttpChunk}s in your handler, insert {@link HttpChunkAggregator}
* {@link HttpContent}s in your handler, insert {@link HttpObjectAggregator}
* after this decoder in the {@link ChannelPipeline}.</td>
* </tr>
* </table>
*/
public class HttpRequestDecoder extends HttpMessageDecoder {
public class HttpRequestDecoder extends HttpObjectDecoder {
/**
* Creates a new instance with the default
@ -71,14 +71,14 @@ public class HttpRequestDecoder extends HttpMessageDecoder {
}
@Override
protected HttpMessage createMessage(String[] initialLine) throws Exception {
return new DefaultHttpRequest(
protected HttpHeader createMessage(String[] initialLine) throws Exception {
return new DefaultHttpRequestHeader(
HttpVersion.valueOf(initialLine[2]), HttpMethod.valueOf(initialLine[0]), initialLine[1]);
}
@Override
protected HttpMessage createInvalidMessage() {
return new DefaultHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request");
protected HttpHeader createInvalidMessage() {
return new DefaultHttpRequestHeader(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request");
}
@Override

View File

@ -20,15 +20,14 @@ import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
/**
* Encodes an {@link HttpRequest} or an {@link HttpChunk} into
* Encodes an {@link HttpRequestHeader} or an {@link HttpContent} into
* a {@link ByteBuf}.
*/
public class HttpRequestEncoder extends HttpMessageEncoder {
public class HttpRequestEncoder extends HttpObjectEncoder<HttpRequestHeader> {
private static final char SLASH = '/';
@Override
protected void encodeInitialLine(ByteBuf buf, HttpMessage message) throws Exception {
HttpRequest request = (HttpRequest) message;
protected void encodeInitialLine(ByteBuf buf, HttpRequestHeader request) throws Exception {
buf.writeBytes(request.getMethod().toString().getBytes(CharsetUtil.US_ASCII));
buf.writeByte(SP);

View File

@ -0,0 +1,63 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
/**
* An HTTP request.
*
* <h3>Accessing Query Parameters and Cookie</h3>
* <p>
* Unlike the Servlet API, a query string is constructed and decomposed by
* {@link QueryStringEncoder} and {@link QueryStringDecoder}. {@link Cookie}
* support is also provided separately via {@link CookieDecoder}, {@link ClientCookieEncoder},
* and {@link @ServerCookieEncoder}.
*
* @see HttpResponseHeader
* @see ClientCookieEncoder
* @see ServerCookieEncoder
* @see CookieDecoder
*/
public interface HttpRequestHeader extends HttpHeader {
/**
* Returns the {@link HttpMethod} of this {@link HttpRequestHeader}.
*
* @return The {@link HttpMethod} of this {@link HttpRequestHeader}
*/
HttpMethod getMethod();
/**
* Sets the {@link HttpMethod} of this {@link HttpRequestHeader}.
*
* @param method The {@link HttpMethod} to set
*/
void setMethod(HttpMethod method);
/**
* Returns the requested URI (or alternatively, path)
*
* @return The URI being requested
*/
String getUri();
/**
* Sets the URI (or alternatively, path) being requested.
*
* @param uri The URI being requested
*/
void setUri(String uri);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -15,33 +15,9 @@
*/
package io.netty.handler.codec.http;
/**
* An HTTP response.
*
* <h3>Accessing Cookies</h3>
* <p>
* Unlike the Servlet API, {@link Cookie} support is provided separately via {@link CookieDecoder},
* {@link ClientCookieEncoder}, and {@link ServerCookieEncoder}.
*
* @see HttpRequest
* @see CookieDecoder
* @see ClientCookieEncoder
* @see ServerCookieEncoder
* Combination of a {@link HttpResponseHeader} and {@link HttpMessage}.
* So it represent a <i>complete</i> http response.
*/
public interface HttpResponse extends HttpMessage {
/**
* Returns the status of this {@link HttpResponse}.
*
* @return The {@link HttpResponseStatus} of this {@link HttpResponse}
*/
HttpResponseStatus getStatus();
/**
* Sets the status of this {@link HttpResponse}
*
* @param status The {@link HttpResponseStatus} to use
*/
void setStatus(HttpResponseStatus status);
public interface HttpResponse extends HttpResponseHeader, HttpMessage {
}

View File

@ -21,8 +21,8 @@ import io.netty.handler.codec.TooLongFrameException;
/**
* Decodes {@link ByteBuf}s into {@link HttpResponse}s and
* {@link HttpChunk}s.
* Decodes {@link ByteBuf}s into {@link HttpResponseHeader}s and
* {@link HttpContent}s.
*
* <h3>Parameters that prevents excessive memory consumption</h3>
* <table border="1">
@ -45,10 +45,10 @@ import io.netty.handler.codec.TooLongFrameException;
* <td>The maximum length of the content or each chunk. If the content length
* exceeds this value, the transfer encoding of the decoded response will be
* converted to 'chunked' and the content will be split into multiple
* {@link HttpChunk}s. If the transfer encoding of the HTTP response is
* {@link HttpContent}s. If the transfer encoding of the HTTP response is
* 'chunked' already, each chunk will be split into smaller chunks if the
* length of the chunk exceeds this value. If you prefer not to handle
* {@link HttpChunk}s in your handler, insert {@link HttpChunkAggregator}
* {@link HttpContent}s in your handler, insert {@link HttpObjectAggregator}
* after this decoder in the {@link ChannelPipeline}.</td>
* </tr>
* </table>
@ -59,7 +59,7 @@ import io.netty.handler.codec.TooLongFrameException;
* request does not have any content even if there is <tt>Content-Length</tt>
* header. Because {@link HttpResponseDecoder} is not able to determine if the
* response currently being decoded is associated with a <tt>HEAD</tt> request,
* you must override {@link #isContentAlwaysEmpty(HttpMessage)} to return
* you must override {@link #isContentAlwaysEmpty(HttpHeader)} to return
* <tt>true</tt> for the response of the <tt>HEAD</tt> request.
* </p><p>
* If you are writing an HTTP client that issues a <tt>HEAD</tt> request,
@ -81,7 +81,7 @@ import io.netty.handler.codec.TooLongFrameException;
* <tt>CONNECT</tt> request.
* </p>
*/
public class HttpResponseDecoder extends HttpMessageDecoder {
public class HttpResponseDecoder extends HttpObjectDecoder {
private static final HttpResponseStatus UNKNOWN_STATUS = new HttpResponseStatus(999, "Unknown");
@ -102,15 +102,15 @@ public class HttpResponseDecoder extends HttpMessageDecoder {
}
@Override
protected HttpMessage createMessage(String[] initialLine) {
return new DefaultHttpResponse(
protected HttpHeader createMessage(String[] initialLine) {
return new DefaultHttpResponseHeader(
HttpVersion.valueOf(initialLine[0]),
new HttpResponseStatus(Integer.valueOf(initialLine[1]), initialLine[2]));
}
@Override
protected HttpMessage createInvalidMessage() {
return new DefaultHttpResponse(HttpVersion.HTTP_1_0, UNKNOWN_STATUS);
protected HttpHeader createInvalidMessage() {
return new DefaultHttpResponseHeader(HttpVersion.HTTP_1_0, UNKNOWN_STATUS);
}
@Override

View File

@ -20,14 +20,13 @@ import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
/**
* Encodes an {@link HttpResponse} or an {@link HttpChunk} into
* Encodes an {@link HttpResponseHeader} or an {@link HttpContent} into
* a {@link ByteBuf}.
*/
public class HttpResponseEncoder extends HttpMessageEncoder {
public class HttpResponseEncoder extends HttpObjectEncoder<HttpResponseHeader> {
@Override
protected void encodeInitialLine(ByteBuf buf, HttpMessage message) throws Exception {
HttpResponse response = (HttpResponse) message;
protected void encodeInitialLine(ByteBuf buf, HttpResponseHeader response) throws Exception {
buf.writeBytes(response.getProtocolVersion().toString().getBytes(CharsetUtil.US_ASCII));
buf.writeByte(SP);
buf.writeBytes(String.valueOf(response.getStatus().getCode()).getBytes(CharsetUtil.US_ASCII));

View File

@ -0,0 +1,47 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
/**
* An HTTP response.
*
* <h3>Accessing Cookies</h3>
* <p>
* Unlike the Servlet API, {@link Cookie} support is provided separately via {@link CookieDecoder},
* {@link ClientCookieEncoder}, and {@link ServerCookieEncoder}.
*
* @see HttpRequestHeader
* @see CookieDecoder
* @see ClientCookieEncoder
* @see ServerCookieEncoder
*/
public interface HttpResponseHeader extends HttpHeader {
/**
* Returns the status of this {@link HttpResponseHeader}.
*
* @return The {@link HttpResponseStatus} of this {@link HttpResponseHeader}
*/
HttpResponseStatus getStatus();
/**
* Sets the status of this {@link HttpResponseHeader}
*
* @param status The {@link HttpResponseStatus} to use
*/
void setStatus(HttpResponseStatus status);
}

View File

@ -1,80 +0,0 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
/**
* Represents how an HTTP request or an HTTP response is represented as an {@link HttpMessage}
* and zero or more {@link HttpChunk}s.
*/
public enum HttpTransferEncoding {
/**
* An HTTP message whose transfer encoding is {@code chunked} as defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">the section 3.6 of
* RFC2616</a> so that the content is split into multiple chunks. A complete HTTP message is
* composed of the following:
* <ol>
* <li>{@link HttpRequest} or {@link HttpResponse} with empty content</li>
* <li>A list of {@link HttpChunk}s whose content are not empty</li>
* <li>{@link HttpChunkTrailer}</li>
* </ol>
*/
CHUNKED(false),
/**
* An HTTP message whose transfer encoding is <strong>not</strong> {@code chunked}, but
* the length of its content is large enough so that the content is split into multiple
* chunks. A complete HTTP message is composted of the following.
* <ol>
* <li>{@link HttpRequest} or {@link HttpResponse} with empty content</li>
* <li>A list of {@link HttpChunk}s whose content are not empty</li>
* <li>{@link HttpChunkTrailer}</li>
* </ol>
* The difference from {@link #CHUNKED} is that the transfer encoding of the streamed content
* is <strong>not</strong> {@code chunked}, and thus {@link HttpMessageEncoder} will
* encode the content as-is, rather than prepending HTTP chunk headers as defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">the section 3.6 of
* RFC2616</a>.
*/
STREAMED(false),
/**
* A self-contained HTTP message which is not followed by any {@link HttpChunk}s.
* A user can set the content of the message via {@link HttpMessage#setContent(ByteBuf)}.
*/
SINGLE(true);
private final boolean single;
HttpTransferEncoding(boolean single) {
this.single = single;
}
/**
* Returns {@code true} if and only if a complete HTTP message is composed of an
* {@link HttpMessage} and one or more {@link HttpChunk}s.
*/
public boolean isMultiple() {
return !single;
}
/**
* Returns {@code true} if and only if a single {@link HttpMessage} represents a complete
* HTTP message, not followed by any {@link HttpChunk}s.
*/
public boolean isSingle() {
return single;
}
}

View File

@ -20,15 +20,9 @@ import java.util.Map;
import java.util.Set;
/**
* The last {@link HttpChunk} which has trailing headers.
* The last {@link HttpContent} which has trailing headers.
*/
public interface HttpChunkTrailer extends HttpChunk {
/**
* Always returns {@code true}.
*/
@Override
boolean isLast();
public interface LastHttpContent extends HttpContent {
/**
* Returns the trailing header value with the specified header name.

View File

@ -27,7 +27,7 @@ import java.util.List;
* the HTTP cookie version 0, 1, and 2.
* <pre>
* // Example
* {@link HttpRequest} req = ...;
* {@link HttpRequestHeader} req = ...;
* res.setHeader("Set-Cookie", {@link ServerCookieEncoder}.encode("JSESSIONID", "1234"));
* </pre>
*

View File

@ -15,7 +15,7 @@
*/
package io.netty.handler.codec.http.multipart;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestHeader;
import java.io.IOException;
import java.nio.charset.Charset;
@ -46,8 +46,8 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
/**
* Keep all HttpDatas until cleanAllHttpDatas() is called.
*/
private final ConcurrentHashMap<HttpRequest, List<HttpData>> requestFileDeleteMap =
new ConcurrentHashMap<HttpRequest, List<HttpData>>();
private final ConcurrentHashMap<HttpRequestHeader, List<HttpData>> requestFileDeleteMap =
new ConcurrentHashMap<HttpRequestHeader, List<HttpData>>();
/**
* HttpData will be in memory if less than default size (16KB).
* The type will be Mixed.
@ -79,7 +79,7 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
/**
* @return the associated list of Files for the request
*/
private List<HttpData> getList(HttpRequest request) {
private List<HttpData> getList(HttpRequestHeader request) {
List<HttpData> list = requestFileDeleteMap.get(request);
if (list == null) {
list = new ArrayList<HttpData>();
@ -89,7 +89,7 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
}
@Override
public Attribute createAttribute(HttpRequest request, String name) {
public Attribute createAttribute(HttpRequestHeader request, String name) {
if (useDisk) {
Attribute attribute = new DiskAttribute(name);
List<HttpData> fileToDelete = getList(request);
@ -106,7 +106,7 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
}
@Override
public Attribute createAttribute(HttpRequest request, String name, String value) {
public Attribute createAttribute(HttpRequestHeader request, String name, String value) {
if (useDisk) {
Attribute attribute;
try {
@ -133,7 +133,7 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
}
@Override
public FileUpload createFileUpload(HttpRequest request, String name, String filename,
public FileUpload createFileUpload(HttpRequestHeader request, String name, String filename,
String contentType, String contentTransferEncoding, Charset charset,
long size) {
if (useDisk) {
@ -155,7 +155,7 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
}
@Override
public void removeHttpDataFromClean(HttpRequest request, InterfaceHttpData data) {
public void removeHttpDataFromClean(HttpRequestHeader request, InterfaceHttpData data) {
if (data instanceof HttpData) {
List<HttpData> fileToDelete = getList(request);
fileToDelete.remove(data);
@ -163,7 +163,7 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
}
@Override
public void cleanRequestHttpDatas(HttpRequest request) {
public void cleanRequestHttpDatas(HttpRequestHeader request) {
List<HttpData> fileToDelete = requestFileDeleteMap.remove(request);
if (fileToDelete != null) {
for (HttpData data: fileToDelete) {
@ -175,7 +175,7 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
@Override
public void cleanAllHttpDatas() {
for (HttpRequest request : requestFileDeleteMap.keySet()) {
for (HttpRequestHeader request : requestFileDeleteMap.keySet()) {
List<HttpData> fileToDelete = requestFileDeleteMap.get(request);
if (fileToDelete != null) {
for (HttpData data: fileToDelete) {

View File

@ -15,7 +15,7 @@
*/
package io.netty.handler.codec.http.multipart;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestHeader;
import java.nio.charset.Charset;
@ -28,20 +28,20 @@ public interface HttpDataFactory {
* @param request associated request
* @return a new Attribute with no value
*/
Attribute createAttribute(HttpRequest request, String name);
Attribute createAttribute(HttpRequestHeader request, String name);
/**
* @param request associated request
* @return a new Attribute
*/
Attribute createAttribute(HttpRequest request, String name, String value);
Attribute createAttribute(HttpRequestHeader request, String name, String value);
/**
* @param request associated request
* @param size the size of the Uploaded file
* @return a new FileUpload
*/
FileUpload createFileUpload(HttpRequest request, String name, String filename,
FileUpload createFileUpload(HttpRequestHeader request, String name, String filename,
String contentType, String contentTransferEncoding, Charset charset,
long size);
@ -50,14 +50,14 @@ public interface HttpDataFactory {
* is still a temporary one as setup at construction)
* @param request associated request
*/
void removeHttpDataFromClean(HttpRequest request, InterfaceHttpData data);
void removeHttpDataFromClean(HttpRequestHeader request, InterfaceHttpData data);
/**
* Remove all InterfaceHttpData from virtual File storage from clean list for the request
*
* @param request associated request
*/
void cleanRequestHttpDatas(HttpRequest request);
void cleanRequestHttpDatas(HttpRequestHeader request);
/**
* Remove all InterfaceHttpData from virtual File storage from clean list for all requests

View File

@ -16,11 +16,12 @@
package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpChunk;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpConstants;
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.HttpRequestHeader;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadNoBackArrayException;
import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize;
import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.TransferEncodingMechanism;
@ -49,7 +50,7 @@ public class HttpPostRequestDecoder {
/**
* Request to decode
*/
private final HttpRequest request;
private final HttpRequestHeader request;
/**
* Default charset to use
@ -135,7 +136,7 @@ public class HttpPostRequestDecoder {
* if the default charset was wrong when decoding or other
* errors
*/
public HttpPostRequestDecoder(HttpRequest request) throws ErrorDataDecoderException,
public HttpPostRequestDecoder(HttpRequestHeader request) throws ErrorDataDecoderException,
IncompatibleDataDecoderException {
this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
}
@ -154,7 +155,7 @@ public class HttpPostRequestDecoder {
* if the default charset was wrong when decoding or other
* errors
*/
public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request) throws ErrorDataDecoderException,
public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequestHeader request) throws ErrorDataDecoderException,
IncompatibleDataDecoderException {
this(factory, request, HttpConstants.DEFAULT_CHARSET);
}
@ -175,7 +176,7 @@ public class HttpPostRequestDecoder {
* if the default charset was wrong when decoding or other
* errors
*/
public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset)
public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequestHeader request, Charset charset)
throws ErrorDataDecoderException, IncompatibleDataDecoderException {
if (factory == null) {
throw new NullPointerException("factory");
@ -202,11 +203,9 @@ public class HttpPostRequestDecoder {
if (!bodyToDecode) {
throw new IncompatibleDataDecoderException("No Body to decode");
}
if (!this.request.getTransferEncoding().isMultiple()) {
undecodedChunk = this.request.getContent();
isLastChunk = true;
parseBody();
}
undecodedChunk = buffer();
isLastChunk = true;
parseBody();
}
/**
@ -335,14 +334,14 @@ public class HttpPostRequestDecoder {
/**
* Initialized the internals from a new chunk
*
* @param chunk
* @param content
* the new received chunk
* @throws ErrorDataDecoderException
* if there is a problem with the charset decoding or other
* errors
*/
public void offer(HttpChunk chunk) throws ErrorDataDecoderException {
ByteBuf chunked = chunk.getContent();
public void offer(HttpContent content) throws ErrorDataDecoderException {
ByteBuf chunked = content.getContent();
if (undecodedChunk == null) {
undecodedChunk = chunked;
} else {
@ -351,7 +350,7 @@ public class HttpPostRequestDecoder {
// less memory usage
undecodedChunk = wrappedBuffer(undecodedChunk, chunked);
}
if (chunk.isLast()) {
if (content instanceof LastHttpContent) {
isLastChunk = true;
}
parseBody();

View File

@ -17,13 +17,12 @@ package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.MessageBuf;
import io.netty.handler.codec.http.DefaultHttpChunk;
import io.netty.handler.codec.http.HttpChunk;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpConstants;
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.HttpTransferEncoding;
import io.netty.handler.stream.ChunkedMessageInput;
import java.io.File;
@ -41,7 +40,7 @@ import static io.netty.buffer.Unpooled.*;
/**
* This encoder will help to encode Request for a FORM as POST.
*/
public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent> {
/**
* Factory used to create InterfaceHttpData
*/
@ -662,11 +661,11 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
}
}
}
request.setTransferEncoding(HttpTransferEncoding.CHUNKED);
HttpHeaders.setTransferEncodingChunked(request);
request.setContent(EMPTY_BUFFER);
} else {
// get the only one body and set it to the request
HttpChunk chunk = nextChunk();
HttpContent chunk = nextChunk();
request.setContent(chunk.getContent());
}
return request;
@ -738,7 +737,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
* @throws ErrorDataEncoderException
* if the encoding is in error
*/
private HttpChunk encodeNextChunkMultipart(int sizeleft) throws ErrorDataEncoderException {
private HttpContent encodeNextChunkMultipart(int sizeleft) throws ErrorDataEncoderException {
if (currentData == null) {
return null;
}
@ -783,7 +782,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
return null;
}
buffer = fillByteBuf();
return new DefaultHttpChunk(buffer);
return new DefaultHttpContent(buffer);
}
/**
@ -796,7 +795,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
* @throws ErrorDataEncoderException
* if the encoding is in error
*/
private HttpChunk encodeNextChunkUrlEncoded(int sizeleft) throws ErrorDataEncoderException {
private HttpContent encodeNextChunkUrlEncoded(int sizeleft) throws ErrorDataEncoderException {
if (currentData == null) {
return null;
}
@ -819,7 +818,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
}
if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) {
buffer = fillByteBuf();
return new DefaultHttpChunk(buffer);
return new DefaultHttpContent(buffer);
}
}
@ -849,7 +848,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
}
if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) {
buffer = fillByteBuf();
return new DefaultHttpChunk(buffer);
return new DefaultHttpContent(buffer);
}
return null;
}
@ -877,7 +876,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
}
buffer = fillByteBuf();
return new DefaultHttpChunk(buffer);
return new DefaultHttpContent(buffer);
}
@Override
@ -895,7 +894,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
* if the encoding is in error
*/
@Override
public boolean readChunk(MessageBuf<HttpChunk> buffer) throws ErrorDataEncoderException {
public boolean readChunk(MessageBuf<HttpContent> buffer) throws ErrorDataEncoderException {
if (isLastChunkSent) {
return false;
} else {
@ -912,10 +911,10 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
* @throws ErrorDataEncoderException
* if the encoding is in error
*/
private HttpChunk nextChunk() throws ErrorDataEncoderException {
private HttpContent nextChunk() throws ErrorDataEncoderException {
if (isLastChunk) {
isLastChunkSent = true;
return new DefaultHttpChunk(EMPTY_BUFFER);
return new DefaultHttpContent(EMPTY_BUFFER);
}
ByteBuf buffer;
int size = HttpPostBodyUtil.chunkSize;
@ -926,18 +925,18 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
if (size <= 0) {
// NextChunk from buffer
buffer = fillByteBuf();
return new DefaultHttpChunk(buffer);
return new DefaultHttpContent(buffer);
}
// size > 0
if (currentData != null) {
// continue to read data
if (isMultipart) {
HttpChunk chunk = encodeNextChunkMultipart(size);
HttpContent chunk = encodeNextChunkMultipart(size);
if (chunk != null) {
return chunk;
}
} else {
HttpChunk chunk = encodeNextChunkUrlEncoded(size);
HttpContent chunk = encodeNextChunkUrlEncoded(size);
if (chunk != null) {
// NextChunk Url from currentData
return chunk;
@ -950,11 +949,11 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
// NextChunk as last non empty from buffer
buffer = currentBuffer;
currentBuffer = null;
return new DefaultHttpChunk(buffer);
return new DefaultHttpContent(buffer);
}
while (size > 0 && iterator.hasNext()) {
currentData = iterator.next();
HttpChunk chunk;
HttpContent chunk;
if (isMultipart) {
chunk = encodeNextChunkMultipart(size);
} else {
@ -973,12 +972,12 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpChunk> {
if (currentBuffer == null) {
isLastChunkSent = true;
// LastChunk with no more data
return new DefaultHttpChunk(EMPTY_BUFFER);
return new DefaultHttpContent(EMPTY_BUFFER);
}
// Previous LastChunk with no more data
buffer = currentBuffer;
currentBuffer = null;
return new DefaultHttpChunk(buffer);
return new DefaultHttpContent(buffer);
}
@Override

View File

@ -20,11 +20,11 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.DefaultHttpRequestHeader;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestHeader;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseDecoder;
@ -124,7 +124,7 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
}
// Format request
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
HttpRequestHeader request = new DefaultHttpRequestHeader(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
request.addHeader(Names.CONNECTION, Values.UPGRADE);
request.addHeader(Names.SEC_WEBSOCKET_KEY, key);

View File

@ -23,7 +23,7 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.HttpRequest;
@ -186,8 +186,8 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
@Override
public void operationComplete(ChannelFuture future) {
ChannelPipeline p = future.channel().pipeline();
if (p.get(HttpChunkAggregator.class) != null) {
p.remove(HttpChunkAggregator.class);
if (p.get(HttpObjectAggregator.class) != null) {
p.remove(HttpObjectAggregator.class);
}
p.get(HttpRequestDecoder.class).replace("wsdecoder",
new WebSocket00FrameDecoder(getMaxFramePayloadLength()));

View File

@ -21,7 +21,7 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder;
@ -151,8 +151,8 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
@Override
public void operationComplete(ChannelFuture future) {
ChannelPipeline p = future.channel().pipeline();
if (p.get(HttpChunkAggregator.class) != null) {
p.remove(HttpChunkAggregator.class);
if (p.get(HttpObjectAggregator.class) != null) {
p.remove(HttpObjectAggregator.class);
}
p.get(HttpRequestDecoder.class).replace("wsdecoder",

View File

@ -20,12 +20,12 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.DefaultHttpResponseHeader;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.logging.InternalLogger;
@ -116,7 +116,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
logger.debug(String.format("Channel %s WS Version 8 server handshake", channel.id()));
}
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
HttpResponseHeader res = new DefaultHttpResponseHeader(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
if (key == null) {
@ -152,8 +152,8 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
@Override
public void operationComplete(ChannelFuture future) {
ChannelPipeline p = future.channel().pipeline();
if (p.get(HttpChunkAggregator.class) != null) {
p.remove(HttpChunkAggregator.class);
if (p.get(HttpObjectAggregator.class) != null) {
p.remove(HttpObjectAggregator.class);
}
p.get(HttpRequestDecoder.class).replace("wsdecoder",

View File

@ -20,12 +20,12 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.DefaultHttpResponseHeader;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.logging.InternalLogger;
@ -115,7 +115,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
logger.debug(String.format("Channel %s WS Version 13 server handshake", channel.id()));
}
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
HttpResponseHeader res = new DefaultHttpResponseHeader(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
if (key == null) {
@ -152,8 +152,8 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
@Override
public void operationComplete(ChannelFuture future) {
ChannelPipeline p = future.channel().pipeline();
if (p.get(HttpChunkAggregator.class) != null) {
p.remove(HttpChunkAggregator.class);
if (p.get(HttpObjectAggregator.class) != null) {
p.remove(HttpObjectAggregator.class);
}
p.get(HttpRequestDecoder.class).replace("wsdecoder",

View File

@ -16,10 +16,10 @@
package io.netty.handler.codec.http.websocketx;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponseHeader;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpRequestHeader;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
@ -81,7 +81,7 @@ public class WebSocketServerHandshakerFactory {
* @return A new WebSocketServerHandshaker for the requested web socket version. Null if web
* socket version is not supported.
*/
public WebSocketServerHandshaker newHandshaker(HttpRequest req) {
public WebSocketServerHandshaker newHandshaker(HttpRequestHeader req) {
String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION);
if (version != null) {
@ -113,7 +113,7 @@ public class WebSocketServerHandshakerFactory {
* Channel
*/
public static void sendUnsupportedWebSocketVersionResponse(Channel channel) {
HttpResponse res = new DefaultHttpResponse(
HttpResponseHeader res = new DefaultHttpResponseHeader(
HttpVersion.HTTP_1_1,
HttpResponseStatus.SWITCHING_PROTOCOLS);
res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED);

View File

@ -22,6 +22,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundMessageHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.AttributeKey;
@ -91,7 +92,7 @@ public class WebSocketServerProtocolHandler extends ChannelInboundMessageHandler
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause instanceof WebSocketHandshakeException) {
DefaultHttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
response.setContent(Unpooled.wrappedBuffer(cause.getMessage().getBytes()));
ctx.channel().write(response).addListener(ChannelFutureListener.CLOSE);
} else {
@ -112,7 +113,7 @@ public class WebSocketServerProtocolHandler extends ChannelInboundMessageHandler
@Override
public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!(msg instanceof WebSocketFrame)) {
DefaultHttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.FORBIDDEN);
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.FORBIDDEN);
ctx.channel().write(response);
} else {
ctx.nextInboundMessageBuffer().add(msg);

View File

@ -27,7 +27,8 @@ import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpRequestHeader;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.ssl.SslHandler;
/**
@ -74,14 +75,14 @@ public class WebSocketServerProtocolHandshakeHandler extends ChannelInboundMessa
}
}
private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequestHeader req, HttpResponseHeader res) {
ChannelFuture f = ctx.channel().write(res);
if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
private static String getWebSocketLocation(ChannelPipeline cp, HttpRequest req, String path) {
private static String getWebSocketLocation(ChannelPipeline cp, HttpRequestHeader req, String path) {
String protocol = "ws";
if (cp.get(SslHandler.class) != null) {
// SSL in use so use Secure WebSockets

View File

@ -19,13 +19,13 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.embedded.EmbeddedMessageChannel;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpMessageDecoder;
import io.netty.handler.codec.http.HttpHeader;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpObjectDecoder;
/**
* Decodes {@link ByteBuf}s into RTSP messages represented in
* {@link HttpMessage}s.
* {@link io.netty.handler.codec.http.HttpHeader}s.
* <p>
* <h3>Parameters that prevents excessive memory consumption</h3>
* <table border="1">
@ -52,7 +52,7 @@ import io.netty.handler.codec.http.HttpMessageDecoder;
* </table>
* @apiviz.landmark
*/
public abstract class RtspMessageDecoder extends HttpMessageDecoder {
public abstract class RtspObjectDecoder extends HttpObjectDecoder {
private final EmbeddedMessageChannel aggregator;
@ -61,16 +61,16 @@ public abstract class RtspMessageDecoder extends HttpMessageDecoder {
* {@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and
* {@code maxContentLength (8192)}.
*/
protected RtspMessageDecoder() {
protected RtspObjectDecoder() {
this(4096, 8192, 8192);
}
/**
* Creates a new instance with the specified parameters.
*/
protected RtspMessageDecoder(int maxInitialLineLength, int maxHeaderSize, int maxContentLength) {
protected RtspObjectDecoder(int maxInitialLineLength, int maxHeaderSize, int maxContentLength) {
super(maxInitialLineLength, maxHeaderSize, maxContentLength * 2);
aggregator = new EmbeddedMessageChannel(new HttpChunkAggregator(maxContentLength));
aggregator = new EmbeddedMessageChannel(new HttpObjectAggregator(maxContentLength));
}
@Override
@ -84,7 +84,7 @@ public abstract class RtspMessageDecoder extends HttpMessageDecoder {
}
@Override
protected boolean isContentAlwaysEmpty(HttpMessage msg) {
protected boolean isContentAlwaysEmpty(HttpHeader msg) {
// Unlike HTTP, RTSP always assumes zero-length body if Content-Length
// header is absent.
boolean empty = super.isContentAlwaysEmpty(msg);

View File

@ -19,31 +19,31 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.UnsupportedMessageTypeException;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpMessageEncoder;
import io.netty.handler.codec.http.HttpHeader;
import io.netty.handler.codec.http.HttpObjectEncoder;
/**
* Encodes an RTSP message represented in {@link HttpMessage} into
* Encodes an RTSP message represented in {@link HttpHeader} into
* a {@link ByteBuf}.
*
* @apiviz.landmark
*/
@Sharable
public abstract class RtspMessageEncoder extends HttpMessageEncoder {
public abstract class RtspObjectEncoder<H extends HttpHeader> extends HttpObjectEncoder<H> {
/**
* Creates a new instance.
*/
protected RtspMessageEncoder() {
protected RtspObjectEncoder() {
}
@Override
protected void encode(ChannelHandlerContext ctx, Object msg,
ByteBuf out) throws Exception {
// Ignore unrelated message types such as HttpChunk.
if (!(msg instanceof HttpMessage)) {
throw new UnsupportedMessageTypeException(msg, HttpMessage.class);
if (!(msg instanceof HttpHeader)) {
throw new UnsupportedMessageTypeException(msg, HttpHeader.class);
}
super.encode(ctx, msg, out);

View File

@ -17,13 +17,12 @@ package io.netty.handler.codec.rtsp;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.DefaultHttpRequestHeader;
import io.netty.handler.codec.http.HttpHeader;
/**
* Decodes {@link ByteBuf}s into RTSP requests represented in
* {@link HttpRequest}s.
* {@link io.netty.handler.codec.http.HttpRequestHeader}s.
* <p>
* <h3>Parameters that prevents excessive memory consumption</h3>
* <table border="1">
@ -48,7 +47,7 @@ import io.netty.handler.codec.http.HttpRequest;
* </tr>
* </table>
*/
public class RtspRequestDecoder extends RtspMessageDecoder {
public class RtspRequestDecoder extends RtspObjectDecoder {
/**
* Creates a new instance with the default
@ -66,14 +65,14 @@ public class RtspRequestDecoder extends RtspMessageDecoder {
}
@Override
protected HttpMessage createMessage(String[] initialLine) throws Exception {
return new DefaultHttpRequest(RtspVersions.valueOf(initialLine[2]),
protected HttpHeader createMessage(String[] initialLine) throws Exception {
return new DefaultHttpRequestHeader(RtspVersions.valueOf(initialLine[2]),
RtspMethods.valueOf(initialLine[0]), initialLine[1]);
}
@Override
protected HttpMessage createInvalidMessage() {
return new DefaultHttpRequest(RtspVersions.RTSP_1_0, RtspMethods.OPTIONS, "/bad-request");
protected HttpHeader createInvalidMessage() {
return new DefaultHttpRequestHeader(RtspVersions.RTSP_1_0, RtspMethods.OPTIONS, "/bad-request");
}
@Override

View File

@ -16,21 +16,19 @@
package io.netty.handler.codec.rtsp;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestHeader;
import io.netty.util.CharsetUtil;
/**
* Encodes an RTSP request represented in {@link HttpRequest} into
* Encodes an RTSP request represented in {@link HttpRequestHeader} into
* a {@link ByteBuf}.
*/
public class RtspRequestEncoder extends RtspMessageEncoder {
public class RtspRequestEncoder extends RtspObjectEncoder<HttpRequestHeader> {
@Override
protected void encodeInitialLine(ByteBuf buf, HttpMessage message)
protected void encodeInitialLine(ByteBuf buf, HttpRequestHeader request)
throws Exception {
HttpRequest request = (HttpRequest) message;
buf.writeBytes(request.getMethod().toString().getBytes(CharsetUtil.US_ASCII));
buf.writeByte((byte) ' ');
buf.writeBytes(request.getUri().getBytes(CharsetUtil.UTF_8));

View File

@ -17,14 +17,13 @@ package io.netty.handler.codec.rtsp;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponseHeader;
import io.netty.handler.codec.http.HttpHeader;
import io.netty.handler.codec.http.HttpResponseStatus;
/**
* Decodes {@link ByteBuf}s into RTSP responses represented in
* {@link HttpResponse}s.
* {@link io.netty.handler.codec.http.HttpResponseHeader}s.
* <p>
* <h3>Parameters that prevents excessive memory consumption</h3>
* <table border="1">
@ -49,7 +48,7 @@ import io.netty.handler.codec.http.HttpResponseStatus;
* </tr>
* </table>
*/
public class RtspResponseDecoder extends RtspMessageDecoder {
public class RtspResponseDecoder extends RtspObjectDecoder {
private static final HttpResponseStatus UNKNOWN_STATUS = new HttpResponseStatus(999, "Unknown");
@ -70,15 +69,15 @@ public class RtspResponseDecoder extends RtspMessageDecoder {
}
@Override
protected HttpMessage createMessage(String[] initialLine) throws Exception {
return new DefaultHttpResponse(
protected HttpHeader createMessage(String[] initialLine) throws Exception {
return new DefaultHttpResponseHeader(
RtspVersions.valueOf(initialLine[0]),
new HttpResponseStatus(Integer.valueOf(initialLine[1]), initialLine[2]));
}
@Override
protected HttpMessage createInvalidMessage() {
return new DefaultHttpResponse(RtspVersions.RTSP_1_0, UNKNOWN_STATUS);
protected HttpHeader createInvalidMessage() {
return new DefaultHttpResponseHeader(RtspVersions.RTSP_1_0, UNKNOWN_STATUS);
}
@Override

View File

@ -16,21 +16,20 @@
package io.netty.handler.codec.rtsp;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpHeader;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.util.CharsetUtil;
/**
* Encodes an RTSP response represented in {@link HttpResponse} into
* Encodes an RTSP response represented in {@link HttpResponseHeader} into
* a {@link ByteBuf}.
*/
public class RtspResponseEncoder extends RtspMessageEncoder {
public class RtspResponseEncoder extends RtspObjectEncoder<HttpResponseHeader> {
@Override
protected void encodeInitialLine(ByteBuf buf, HttpMessage message)
protected void encodeInitialLine(ByteBuf buf, HttpResponseHeader response)
throws Exception {
HttpResponse response = (HttpResponse) message;
buf.writeBytes(response.getProtocolVersion().toString().getBytes(CharsetUtil.US_ASCII));
buf.writeByte((byte) ' ');
buf.writeBytes(String.valueOf(response.getStatus().getCode()).getBytes(CharsetUtil.US_ASCII));

View File

@ -22,6 +22,7 @@ import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpHeader;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpMethod;
@ -173,7 +174,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<Object> {
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
Integer streamID = Integer.valueOf(spdyHeadersFrame.getStreamId());
HttpMessage httpMessage = messageMap.get(streamID);
HttpHeader httpMessage = messageMap.get(streamID);
// If message is not in map discard HEADERS frame.
// SpdySessionHandler should prevent this from happening.

View File

@ -18,25 +18,26 @@ package io.netty.handler.codec.spdy;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.UnsupportedMessageTypeException;
import io.netty.handler.codec.http.HttpChunk;
import io.netty.handler.codec.http.HttpChunkTrailer;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeader;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpRequestHeader;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.HttpObject;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpChunk}s
* Encodes {@link HttpRequestHeader}s, {@link HttpResponseHeader}s, and {@link HttpContent}s
* into {@link SpdySynStreamFrame}s and {@link SpdySynReplyFrame}s.
*
* <h3>Request Annotations</h3>
*
* SPDY specific headers must be added to {@link HttpRequest}s:
* SPDY specific headers must be added to {@link HttpRequestHeader}s:
* <table border=1>
* <tr>
* <th>Header Name</th><th>Header Value</th>
@ -57,7 +58,7 @@ import java.util.Map;
*
* <h3>Response Annotations</h3>
*
* SPDY specific headers must be added to {@link HttpResponse}s:
* SPDY specific headers must be added to {@link HttpResponseHeader}s:
* <table border=1>
* <tr>
* <th>Header Name</th><th>Header Value</th>
@ -70,7 +71,7 @@ import java.util.Map;
*
* <h3>Pushed Resource Annotations</h3>
*
* SPDY specific headers must be added to pushed {@link HttpResponse}s:
* SPDY specific headers must be added to pushed {@link HttpResponseHeader}s:
* <table border=1>
* <tr>
* <th>Header Name</th><th>Header Value</th>
@ -110,9 +111,9 @@ import java.util.Map;
*
* <h3>Chunked Content</h3>
*
* This encoder associates all {@link HttpChunk}s that it receives
* with the most recently received 'chunked' {@link HttpRequest}
* or {@link HttpResponse}.
* This encoder associates all {@link HttpContent}s that it receives
* with the most recently received 'chunked' {@link HttpRequestHeader}
* or {@link HttpResponseHeader}.
*
* <h3>Pushed Resources</h3>
*
@ -143,38 +144,32 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<Object> {
public Object encode(ChannelHandlerContext ctx, Object msg) throws Exception {
List<Object> out = new ArrayList<Object>();
if (msg instanceof HttpRequest) {
if (msg instanceof HttpRequestHeader) {
HttpRequest httpRequest = (HttpRequest) msg;
HttpRequestHeader httpRequest = (HttpRequestHeader) msg;
SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest);
int streamID = spdySynStreamFrame.getStreamId();
out.add(spdySynStreamFrame);
addContent(out, streamID, httpRequest);
}
if (msg instanceof HttpResponseHeader) {
} else if (msg instanceof HttpResponse) {
HttpResponse httpResponse = (HttpResponse) msg;
HttpResponseHeader httpResponse = (HttpResponseHeader) msg;
if (httpResponse.containsHeader(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID)) {
SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpResponse);
int streamID = spdySynStreamFrame.getStreamId();
out.add(spdySynStreamFrame);
addContent(out, streamID, httpResponse);
} else {
SpdySynReplyFrame spdySynReplyFrame = createSynReplyFrame(httpResponse);
int streamID = spdySynReplyFrame.getStreamId();
out.add(spdySynReplyFrame);
addContent(out, streamID, httpResponse);
}
}
if (msg instanceof HttpContent) {
} else if (msg instanceof HttpChunk) {
HttpChunk chunk = (HttpChunk) msg;
HttpContent chunk = (HttpContent) msg;
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamId);
spdyDataFrame.setData(chunk.getContent());
spdyDataFrame.setLast(chunk.isLast());
spdyDataFrame.setLast(chunk instanceof LastHttpContent);
if (chunk instanceof HttpChunkTrailer) {
HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
if (chunk instanceof LastHttpContent) {
LastHttpContent trailer = (LastHttpContent) chunk;
List<Map.Entry<String, String>> trailers = trailer.getHeaders();
if (trailers.isEmpty()) {
out.add(spdyDataFrame);
@ -199,23 +194,8 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<Object> {
return out.toArray();
}
private static void addContent(List<Object> out, int streamID, HttpMessage httpMessage) {
if (!httpMessage.getContent().readable()) {
return;
}
// Create SPDY Data Frame out of message content
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
spdyDataFrame.setData(httpMessage.getContent());
spdyDataFrame.setLast(true);
out.add(spdyDataFrame);
}
private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage)
private SpdySynStreamFrame createSynStreamFrame(HttpHeader httpMessage)
throws Exception {
boolean chunked = httpMessage.getTransferEncoding().isMultiple();
// Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers
int streamID = SpdyHttpHeaders.getStreamId(httpMessage);
int associatedToStreamId = SpdyHttpHeaders.getAssociatedToStreamId(httpMessage);
@ -240,13 +220,13 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<Object> {
// Unfold the first line of the message into name/value pairs
if (httpMessage instanceof HttpRequest) {
HttpRequest httpRequest = (HttpRequest) httpMessage;
HttpRequestHeader httpRequest = (HttpRequestHeader) httpMessage;
SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod());
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri());
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
}
if (httpMessage instanceof HttpResponse) {
HttpResponse httpResponse = (HttpResponse) httpMessage;
if (httpMessage instanceof HttpResponseHeader) {
HttpResponseHeader httpResponse = (HttpResponseHeader) httpMessage;
SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus());
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL);
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
@ -270,20 +250,14 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<Object> {
for (Map.Entry<String, String> entry: httpMessage.getHeaders()) {
spdySynStreamFrame.addHeader(entry.getKey(), entry.getValue());
}
if (chunked) {
currentStreamId = streamID;
spdySynStreamFrame.setLast(false);
} else {
spdySynStreamFrame.setLast(httpMessage.getContent().readableBytes() == 0);
}
currentStreamId = spdySynStreamFrame.getStreamId();
return spdySynStreamFrame;
}
private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse)
private SpdySynReplyFrame createSynReplyFrame(HttpResponseHeader httpResponse)
throws Exception {
boolean chunked = httpResponse.getTransferEncoding().isMultiple();
boolean chunked = HttpHeaders.isTransferEncodingChunked(httpResponse);
// Get the Stream-ID from the headers
int streamID = SpdyHttpHeaders.getStreamId(httpResponse);
@ -310,8 +284,6 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<Object> {
if (chunked) {
currentStreamId = streamID;
spdySynReplyFrame.setLast(false);
} else {
spdySynReplyFrame.setLast(httpResponse.getContent().readableBytes() == 0);
}
return spdySynReplyFrame;

View File

@ -15,8 +15,8 @@
*/
package io.netty.handler.codec.spdy;
import io.netty.handler.codec.http.HttpHeader;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMessage;
/**
* Provides the constants for the header names and the utility methods
@ -60,28 +60,28 @@ public final class SpdyHttpHeaders {
/**
* Removes the {@code "X-SPDY-Stream-ID"} header.
*/
public static void removeStreamId(HttpMessage message) {
public static void removeStreamId(HttpHeader message) {
message.removeHeader(Names.STREAM_ID);
}
/**
* Returns the value of the {@code "X-SPDY-Stream-ID"} header.
*/
public static int getStreamId(HttpMessage message) {
public static int getStreamId(HttpHeader message) {
return HttpHeaders.getIntHeader(message, Names.STREAM_ID);
}
/**
* Sets the {@code "X-SPDY-Stream-ID"} header.
*/
public static void setStreamId(HttpMessage message, int streamId) {
public static void setStreamId(HttpHeader message, int streamId) {
HttpHeaders.setIntHeader(message, Names.STREAM_ID, streamId);
}
/**
* Removes the {@code "X-SPDY-Associated-To-Stream-ID"} header.
*/
public static void removeAssociatedToStreamId(HttpMessage message) {
public static void removeAssociatedToStreamId(HttpHeader message) {
message.removeHeader(Names.ASSOCIATED_TO_STREAM_ID);
}
@ -91,21 +91,21 @@ public final class SpdyHttpHeaders {
* @return the header value or {@code 0} if there is no such header or
* if the header value is not a number
*/
public static int getAssociatedToStreamId(HttpMessage message) {
public static int getAssociatedToStreamId(HttpHeader message) {
return HttpHeaders.getIntHeader(message, Names.ASSOCIATED_TO_STREAM_ID, 0);
}
/**
* Sets the {@code "X-SPDY-Associated-To-Stream-ID"} header.
*/
public static void setAssociatedToStreamId(HttpMessage message, int associatedToStreamId) {
public static void setAssociatedToStreamId(HttpHeader message, int associatedToStreamId) {
HttpHeaders.setIntHeader(message, Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId);
}
/**
* Removes the {@code "X-SPDY-Priority"} header.
*/
public static void removePriority(HttpMessage message) {
public static void removePriority(HttpHeader message) {
message.removeHeader(Names.PRIORITY);
}
@ -115,56 +115,56 @@ public final class SpdyHttpHeaders {
* @return the header value or {@code 0} if there is no such header or
* if the header value is not a number
*/
public static byte getPriority(HttpMessage message) {
public static byte getPriority(HttpHeader message) {
return (byte) HttpHeaders.getIntHeader(message, Names.PRIORITY, 0);
}
/**
* Sets the {@code "X-SPDY-Priority"} header.
*/
public static void setPriority(HttpMessage message, byte priority) {
public static void setPriority(HttpHeader message, byte priority) {
HttpHeaders.setIntHeader(message, Names.PRIORITY, priority);
}
/**
* Removes the {@code "X-SPDY-URL"} header.
*/
public static void removeUrl(HttpMessage message) {
public static void removeUrl(HttpHeader message) {
message.removeHeader(Names.URL);
}
/**
* Returns the value of the {@code "X-SPDY-URL"} header.
*/
public static String getUrl(HttpMessage message) {
public static String getUrl(HttpHeader message) {
return message.getHeader(Names.URL);
}
/**
* Sets the {@code "X-SPDY-URL"} header.
*/
public static void setUrl(HttpMessage message, String url) {
public static void setUrl(HttpHeader message, String url) {
message.setHeader(Names.URL, url);
}
/**
* Removes the {@code "X-SPDY-Scheme"} header.
*/
public static void removeScheme(HttpMessage message) {
public static void removeScheme(HttpHeader message) {
message.removeHeader(Names.SCHEME);
}
/**
* Returns the value of the {@code "X-SPDY-Scheme"} header.
*/
public static String getScheme(HttpMessage message) {
public static String getScheme(HttpHeader message) {
return message.getHeader(Names.SCHEME);
}
/**
* Sets the {@code "X-SPDY-Scheme"} header.
*/
public static void setScheme(HttpMessage message, String scheme) {
public static void setScheme(HttpHeader message, String scheme) {
message.setHeader(Names.SCHEME, scheme);
}
}

View File

@ -20,25 +20,24 @@ import java.util.Queue;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpHeader;
/**
* {@link MessageToMessageCodec} that takes care of adding the right {@link SpdyHttpHeaders.Names#STREAM_ID} to the
* {@link HttpResponse} if one is not present. This makes it possible to just re-use plan handlers current used
* {@link HttpHeader} if one is not present. This makes it possible to just re-use plan handlers current used
* for HTTP.
*/
public class SpdyHttpResponseStreamIdHandler extends
MessageToMessageCodec<Object, HttpMessage> {
MessageToMessageCodec<Object, HttpHeader> {
private static final Integer NO_ID = -1;
private final Queue<Integer> ids = new LinkedList<Integer>();
public SpdyHttpResponseStreamIdHandler() {
super(new Class<?>[] { HttpMessage.class, SpdyRstStreamFrame.class }, new Class<?>[] { HttpMessage.class });
super(new Class<?>[] { HttpHeader.class, SpdyRstStreamFrame.class }, new Class<?>[] { HttpHeader.class });
}
@Override
protected Object encode(ChannelHandlerContext ctx, HttpMessage msg) throws Exception {
protected Object encode(ChannelHandlerContext ctx, HttpHeader msg) throws Exception {
Integer id = ids.poll();
if (id != null && id.intValue() != NO_ID && !msg.containsHeader(SpdyHttpHeaders.Names.STREAM_ID)) {
SpdyHttpHeaders.setStreamId(msg, id);
@ -48,12 +47,12 @@ public class SpdyHttpResponseStreamIdHandler extends
@Override
protected Object decode(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpMessage) {
boolean contains = ((HttpMessage) msg).containsHeader(SpdyHttpHeaders.Names.STREAM_ID);
if (msg instanceof HttpHeader) {
boolean contains = ((HttpHeader) msg).containsHeader(SpdyHttpHeaders.Names.STREAM_ID);
if (!contains) {
ids.add(NO_ID);
} else {
ids.add(SpdyHttpHeaders.getStreamId((HttpMessage) msg));
ids.add(SpdyHttpHeaders.getStreamId((HttpHeader) msg));
}
} else if (msg instanceof SpdyRstStreamFrame) {
ids.remove(((SpdyRstStreamFrame) msg).getStreamId());

View File

@ -22,8 +22,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundByteHandler;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.ssl.SslHandler;
@ -139,19 +138,19 @@ public abstract class SpdyOrHttpChooser extends ChannelHandlerAdapter implements
ChannelPipeline pipeline = ctx.pipeline();
pipeline.addLast("httpRquestDecoder", new HttpRequestDecoder());
pipeline.addLast("httpResponseEncoder", new HttpResponseEncoder());
pipeline.addLast("httpChunkAggregator", new HttpChunkAggregator(maxHttpContentLength));
pipeline.addLast("httpChunkAggregator", new HttpObjectAggregator(maxHttpContentLength));
pipeline.addLast("httpRquestHandler", createHttpRequestHandlerForHttp());
}
/**
* Create the {@link ChannelInboundHandler} that is responsible for handling the {@link HttpRequest}'s
* Create the {@link ChannelInboundHandler} that is responsible for handling the http requests
* when the {@link SelectedProtocol} was {@link SelectedProtocol#HttpVersion1_0} or
* {@link SelectedProtocol#HttpVersion1_1}
*/
protected abstract ChannelInboundHandler createHttpRequestHandlerForHttp();
/**
* Create the {@link ChannelInboundHandler} that is responsible for handling the {@link HttpRequest}'s
* Create the {@link ChannelInboundHandler} that is responsible for handling the http responses
* when the {@link SelectedProtocol} was {@link SelectedProtocol#SpdyVersion2} or
* {@link SelectedProtocol#SpdyVersion3}.
*

View File

@ -18,11 +18,11 @@ package io.netty.handler.codec.http;
import org.junit.Assert;
import org.junit.Test;
public class DefaultHttpMessageTest {
public class DefaultHttpRequestTest {
@Test
public void testHeaderRemoval() {
HttpMessage m = new DefaultHttpRequest(
HttpHeader m = new DefaultHttpRequestHeader(
HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
// Insert sample keys.

View File

@ -33,7 +33,7 @@ public class HttpInvalidMessageTest {
public void testRequestWithBadInitialLine() throws Exception {
EmbeddedByteChannel ch = new EmbeddedByteChannel(new HttpRequestDecoder());
ch.writeInbound(Unpooled.copiedBuffer("GET / HTTP/1.0 with extra\r\n", CharsetUtil.UTF_8));
HttpRequest req = (HttpRequest) ch.readInbound();
HttpRequestHeader req = (HttpRequestHeader) ch.readInbound();
DecoderResult dr = req.getDecoderResult();
Assert.assertFalse(dr.isSuccess());
Assert.assertFalse(dr.isPartialFailure());
@ -47,7 +47,7 @@ public class HttpInvalidMessageTest {
ch.writeInbound(Unpooled.copiedBuffer("Good_Name: Good Value\r\n", CharsetUtil.UTF_8));
ch.writeInbound(Unpooled.copiedBuffer("Bad=Name: Bad Value\r\n", CharsetUtil.UTF_8));
ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.UTF_8));
HttpRequest req = (HttpRequest) ch.readInbound();
HttpRequestHeader req = (HttpRequestHeader) ch.readInbound();
DecoderResult dr = req.getDecoderResult();
Assert.assertFalse(dr.isSuccess());
Assert.assertTrue(dr.isPartialFailure());
@ -60,7 +60,7 @@ public class HttpInvalidMessageTest {
public void testResponseWithBadInitialLine() throws Exception {
EmbeddedByteChannel ch = new EmbeddedByteChannel(new HttpResponseDecoder());
ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.0 BAD_CODE Bad Server\r\n", CharsetUtil.UTF_8));
HttpResponse res = (HttpResponse) ch.readInbound();
HttpResponseHeader res = (HttpResponseHeader) ch.readInbound();
DecoderResult dr = res.getDecoderResult();
Assert.assertFalse(dr.isSuccess());
Assert.assertFalse(dr.isPartialFailure());
@ -74,7 +74,7 @@ public class HttpInvalidMessageTest {
ch.writeInbound(Unpooled.copiedBuffer("Good_Name: Good Value\r\n", CharsetUtil.UTF_8));
ch.writeInbound(Unpooled.copiedBuffer("Bad=Name: Bad Value\r\n", CharsetUtil.UTF_8));
ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.UTF_8));
HttpResponse res = (HttpResponse) ch.readInbound();
HttpResponseHeader res = (HttpResponseHeader) ch.readInbound();
DecoderResult dr = res.getDecoderResult();
Assert.assertFalse(dr.isSuccess());
Assert.assertTrue(dr.isPartialFailure());
@ -90,10 +90,10 @@ public class HttpInvalidMessageTest {
ch.writeInbound(Unpooled.copiedBuffer("Transfer-Encoding: chunked\r\n\r\n", CharsetUtil.UTF_8));
ch.writeInbound(Unpooled.copiedBuffer("BAD_LENGTH\r\n", CharsetUtil.UTF_8));
HttpRequest req = (HttpRequest) ch.readInbound();
HttpRequestHeader req = (HttpRequestHeader) ch.readInbound();
Assert.assertTrue(req.getDecoderResult().isSuccess());
HttpChunk chunk = (HttpChunk) ch.readInbound();
HttpContent chunk = (HttpContent) ch.readInbound();
DecoderResult dr = chunk.getDecoderResult();
Assert.assertFalse(dr.isSuccess());
Assert.assertFalse(dr.isPartialFailure());

View File

@ -29,19 +29,19 @@ import java.util.List;
import org.easymock.EasyMock;
import org.junit.Test;
public class HttpChunkAggregatorTest {
public class HttpObjectAggregatorTest {
@Test
public void testAggregate() {
HttpChunkAggregator aggr = new HttpChunkAggregator(1024 * 1024);
HttpObjectAggregator aggr = new HttpObjectAggregator(1024 * 1024);
EmbeddedMessageChannel embedder = new EmbeddedMessageChannel(aggr);
HttpMessage message = new DefaultHttpMessage(HttpVersion.HTTP_1_1);
HttpRequestHeader message = new DefaultHttpRequestHeader(HttpVersion.HTTP_1_1,
HttpMethod.GET, "http://localhost");
HttpHeaders.setHeader(message, "X-Test", true);
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
HttpChunk chunk1 = new DefaultHttpChunk(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
HttpChunk chunk2 = new DefaultHttpChunk(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
HttpChunk chunk3 = new DefaultHttpChunk(Unpooled.EMPTY_BUFFER);
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
HttpContent chunk3 = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
assertFalse(embedder.writeInbound(message));
assertFalse(embedder.writeInbound(chunk1));
assertFalse(embedder.writeInbound(chunk2));
@ -49,7 +49,7 @@ public class HttpChunkAggregatorTest {
// this should trigger a messageReceived event so return true
assertTrue(embedder.writeInbound(chunk3));
assertTrue(embedder.finish());
HttpMessage aggratedMessage = (HttpMessage) embedder.readInbound();
DefaultHttpRequest aggratedMessage = (DefaultHttpRequest) embedder.readInbound();
assertNotNull(aggratedMessage);
assertEquals(chunk1.getContent().readableBytes() + chunk2.getContent().readableBytes(), HttpHeaders.getContentLength(aggratedMessage));
@ -59,7 +59,7 @@ public class HttpChunkAggregatorTest {
}
private static void checkContentBuffer(HttpMessage aggregatedMessage) {
private static void checkContentBuffer(DefaultHttpRequest aggregatedMessage) {
CompositeByteBuf buffer = (CompositeByteBuf) aggregatedMessage.getContent();
assertEquals(2, buffer.numComponents());
List<ByteBuf> buffers = buffer.decompose(0, buffer.capacity());
@ -72,14 +72,15 @@ public class HttpChunkAggregatorTest {
@Test
public void testAggregateWithTrailer() {
HttpChunkAggregator aggr = new HttpChunkAggregator(1024 * 1024);
HttpObjectAggregator aggr = new HttpObjectAggregator(1024 * 1024);
EmbeddedMessageChannel embedder = new EmbeddedMessageChannel(aggr);
HttpMessage message = new DefaultHttpMessage(HttpVersion.HTTP_1_1);
HttpRequestHeader message = new DefaultHttpRequestHeader(HttpVersion.HTTP_1_1,
HttpMethod.GET, "http://localhost");
HttpHeaders.setHeader(message, "X-Test", true);
message.setTransferEncoding(HttpTransferEncoding.CHUNKED);
HttpChunk chunk1 = new DefaultHttpChunk(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
HttpChunk chunk2 = new DefaultHttpChunk(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
HttpChunkTrailer trailer = new DefaultHttpChunkTrailer();
HttpHeaders.setTransferEncodingChunked(message);
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
LastHttpContent trailer = new DefaultLastHttpContent();
trailer.setHeader("X-Trailer", true);
assertFalse(embedder.writeInbound(message));
@ -89,7 +90,7 @@ public class HttpChunkAggregatorTest {
// this should trigger a messageReceived event so return true
assertTrue(embedder.writeInbound(trailer));
assertTrue(embedder.finish());
HttpMessage aggratedMessage = (HttpMessage) embedder.readInbound();
DefaultHttpRequest aggratedMessage = (DefaultHttpRequest) embedder.readInbound();
assertNotNull(aggratedMessage);
assertEquals(chunk1.getContent().readableBytes() + chunk2.getContent().readableBytes(), HttpHeaders.getContentLength(aggratedMessage));
@ -104,12 +105,12 @@ public class HttpChunkAggregatorTest {
@Test(expected = TooLongFrameException.class)
public void testTooLongFrameException() {
HttpChunkAggregator aggr = new HttpChunkAggregator(4);
HttpObjectAggregator aggr = new HttpObjectAggregator(4);
EmbeddedMessageChannel embedder = new EmbeddedMessageChannel(aggr);
HttpMessage message = new DefaultHttpMessage(HttpVersion.HTTP_1_1);
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
HttpChunk chunk1 = new DefaultHttpChunk(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
HttpChunk chunk2 = new DefaultHttpChunk(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
HttpRequestHeader message = new DefaultHttpRequestHeader(HttpVersion.HTTP_1_1,
HttpMethod.GET, "http://localhost");
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
assertFalse(embedder.writeInbound(message));
assertFalse(embedder.writeInbound(chunk1));
embedder.writeInbound(chunk2);
@ -119,18 +120,18 @@ public class HttpChunkAggregatorTest {
@Test(expected = IllegalArgumentException.class)
public void testInvalidConstructorUsage() {
new HttpChunkAggregator(0);
new HttpObjectAggregator(0);
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidMaxCumulationBufferComponents() {
HttpChunkAggregator aggr= new HttpChunkAggregator(Integer.MAX_VALUE);
HttpObjectAggregator aggr= new HttpObjectAggregator(Integer.MAX_VALUE);
aggr.setMaxCumulationBufferComponents(1);
}
@Test(expected = IllegalStateException.class)
public void testSetMaxCumulationBufferComponentsAfterInit() throws Exception {
HttpChunkAggregator aggr = new HttpChunkAggregator(Integer.MAX_VALUE);
HttpObjectAggregator aggr = new HttpObjectAggregator(Integer.MAX_VALUE);
ChannelHandlerContext ctx = EasyMock.createMock(ChannelHandlerContext.class);
EasyMock.replay(ctx);
aggr.beforeAdd(ctx);

View File

@ -31,7 +31,7 @@ public class HttpRequestEncoderTest {
public void testUriWithoutPath() throws Exception {
HttpRequestEncoder encoder = new HttpRequestEncoder();
ByteBuf buffer = Unpooled.buffer(64);
encoder.encodeInitialLine(buffer, new DefaultHttpRequest(HttpVersion.HTTP_1_1,
encoder.encodeInitialLine(buffer, new DefaultHttpRequestHeader(HttpVersion.HTTP_1_1,
HttpMethod.GET, "http://localhost"));
String req = buffer.toString(Charset.forName("US-ASCII"));
assertEquals("GET http://localhost/ HTTP/1.1\r\n", req);
@ -42,7 +42,7 @@ public class HttpRequestEncoderTest {
public void testUriWithPath() throws Exception {
HttpRequestEncoder encoder = new HttpRequestEncoder();
ByteBuf buffer = Unpooled.buffer(64);
encoder.encodeInitialLine(buffer, new DefaultHttpRequest(HttpVersion.HTTP_1_1,
encoder.encodeInitialLine(buffer, new DefaultHttpRequestHeader(HttpVersion.HTTP_1_1,
HttpMethod.GET, "http://localhost/"));
String req = buffer.toString(Charset.forName("US-ASCII"));
assertEquals("GET http://localhost/ HTTP/1.1\r\n", req);

View File

@ -44,19 +44,19 @@ public class HttpServerCodecTest {
decoderEmbedder.writeInbound(prepareDataChunk(offeredContentLength));
decoderEmbedder.finish();
HttpMessage httpMessage = (HttpMessage) decoderEmbedder.readInbound();
Assert.assertSame(HttpTransferEncoding.STREAMED, httpMessage.getTransferEncoding());
HttpHeader httpMessage = (HttpHeader) decoderEmbedder.readInbound();
//Assert.assertSame(HttpTransferEncoding.STREAMED, httpMessage.getTransferEncoding());
boolean empty = true;
int totalBytesPolled = 0;
for (;;) {
HttpChunk httpChunk = (HttpChunk) decoderEmbedder.readInbound();
HttpContent httpChunk = (HttpContent) decoderEmbedder.readInbound();
if (httpChunk == null) {
break;
}
empty = false;
totalBytesPolled += httpChunk.getContent().readableBytes();
Assert.assertFalse(httpChunk.isLast());
Assert.assertFalse(httpChunk instanceof LastHttpContent);
}
Assert.assertFalse(empty);
Assert.assertEquals(offeredContentLength, totalBytesPolled);

View File

@ -17,9 +17,11 @@ package io.netty.handler.codec.http.websocketx;
import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestHeader;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.HttpHeaders.Names;
@ -117,7 +119,7 @@ public class WebSocketRequestBuilder {
return req;
}
public static HttpRequest sucessful() {
public static HttpRequestHeader sucessful() {
return new WebSocketRequestBuilder().httpVersion(HTTP_1_1)
.method(HttpMethod.GET)
.uri("/test")

View File

@ -21,14 +21,15 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedByteChannel;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.CharsetUtil;
import org.junit.Assert;
@ -39,7 +40,7 @@ public class WebSocketServerHandshaker00Test {
@Test
public void testPerformOpeningHandshake() {
EmbeddedByteChannel ch = new EmbeddedByteChannel(
new HttpChunkAggregator(42), new HttpRequestDecoder(), new HttpResponseEncoder());
new HttpObjectAggregator(42), new HttpRequestDecoder(), new HttpResponseEncoder());
HttpRequest req = new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/chat");
req.setHeader(Names.HOST, "server.example.com");
@ -60,10 +61,12 @@ public class WebSocketServerHandshaker00Test {
EmbeddedByteChannel ch2 = new EmbeddedByteChannel(new HttpResponseDecoder());
ch2.writeInbound(resBuf);
HttpResponse res = (HttpResponse) ch2.readInbound();
HttpResponseHeader res = (HttpResponseHeader) ch2.readInbound();
Assert.assertEquals("ws://example.com/chat", res.getHeader(Names.SEC_WEBSOCKET_LOCATION));
Assert.assertEquals("chat", res.getHeader(Names.SEC_WEBSOCKET_PROTOCOL));
Assert.assertEquals("8jKS'y:G*Co,Wxa-", res.getContent().toString(CharsetUtil.US_ASCII));
LastHttpContent content = (LastHttpContent) ch2.readInbound();
Assert.assertEquals("8jKS'y:G*Co,Wxa-", content.getContent().toString(CharsetUtil.US_ASCII));
}
}

View File

@ -20,12 +20,12 @@ import static io.netty.handler.codec.http.HttpVersion.*;
import io.netty.buffer.ByteBuf;
import io.netty.channel.embedded.EmbeddedByteChannel;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
@ -37,7 +37,7 @@ public class WebSocketServerHandshaker08Test {
@Test
public void testPerformOpeningHandshake() {
EmbeddedByteChannel ch = new EmbeddedByteChannel(
new HttpChunkAggregator(42), new HttpRequestDecoder(), new HttpResponseEncoder());
new HttpObjectAggregator(42), new HttpRequestDecoder(), new HttpResponseEncoder());
HttpRequest req = new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/chat");
req.setHeader(Names.HOST, "server.example.com");
@ -55,7 +55,7 @@ public class WebSocketServerHandshaker08Test {
EmbeddedByteChannel ch2 = new EmbeddedByteChannel(new HttpResponseDecoder());
ch2.writeInbound(resBuf);
HttpResponse res = (HttpResponse) ch2.readInbound();
HttpResponseHeader res = (HttpResponseHeader) ch2.readInbound();
Assert.assertEquals(
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getHeader(Names.SEC_WEBSOCKET_ACCEPT));

View File

@ -20,12 +20,12 @@ import static io.netty.handler.codec.http.HttpVersion.*;
import io.netty.buffer.ByteBuf;
import io.netty.channel.embedded.EmbeddedByteChannel;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
@ -37,7 +37,7 @@ public class WebSocketServerHandshaker13Test {
@Test
public void testPerformOpeningHandshake() {
EmbeddedByteChannel ch = new EmbeddedByteChannel(
new HttpChunkAggregator(42), new HttpRequestDecoder(), new HttpResponseEncoder());
new HttpObjectAggregator(42), new HttpRequestDecoder(), new HttpResponseEncoder());
HttpRequest req = new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/chat");
req.setHeader(Names.HOST, "server.example.com");
@ -55,7 +55,7 @@ public class WebSocketServerHandshaker13Test {
EmbeddedByteChannel ch2 = new EmbeddedByteChannel(new HttpResponseDecoder());
ch2.writeInbound(resBuf);
HttpResponse res = (HttpResponse) ch2.readInbound();
HttpResponseHeader res = (HttpResponseHeader) ch2.readInbound();
Assert.assertEquals(
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getHeader(Names.SEC_WEBSOCKET_ACCEPT));

View File

@ -22,11 +22,13 @@ import io.netty.channel.ChannelInboundMessageHandlerAdapter;
import io.netty.channel.ChannelOutboundMessageHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.embedded.EmbeddedMessageChannel;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.DefaultHttpRequestHeader;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestHeader;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.HttpResponseEncoder;
import org.junit.Test;
@ -44,7 +46,7 @@ public class WebSocketServerProtocolHandlerTest {
writeUpgradeRequest(ch);
assertEquals(SWITCHING_PROTOCOLS, ((HttpResponse) ch.outboundMessageBuffer().poll()).getStatus());
assertEquals(SWITCHING_PROTOCOLS, ((HttpResponseHeader) ch.outboundMessageBuffer().poll()).getStatus());
assertNotNull(WebSocketServerProtocolHandler.getHandshaker(handshakerCtx));
}
@ -53,10 +55,10 @@ public class WebSocketServerProtocolHandlerTest {
EmbeddedMessageChannel ch = createChannel(new MockOutboundHandler());
writeUpgradeRequest(ch);
assertEquals(SWITCHING_PROTOCOLS, ((HttpResponse) ch.outboundMessageBuffer().poll()).getStatus());
assertEquals(SWITCHING_PROTOCOLS, ((HttpResponseHeader) ch.outboundMessageBuffer().poll()).getStatus());
ch.writeInbound(new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/test"));
assertEquals(FORBIDDEN, ((HttpResponse) ch.outboundMessageBuffer().poll()).getStatus());
ch.writeInbound(new DefaultHttpRequestHeader(HTTP_1_1, HttpMethod.GET, "/test"));
assertEquals(FORBIDDEN, ((HttpResponseHeader) ch.outboundMessageBuffer().poll()).getStatus());
}
@Test
@ -81,7 +83,7 @@ public class WebSocketServerProtocolHandlerTest {
@Test
public void testHttpUpgradeRequestMissingWSKeyHeader() {
EmbeddedMessageChannel ch = createChannel();
HttpRequest httpRequest = new WebSocketRequestBuilder().httpVersion(HTTP_1_1)
HttpRequestHeader httpRequest = new WebSocketRequestBuilder().httpVersion(HTTP_1_1)
.method(HttpMethod.GET)
.uri("/test")
.key(null)

View File

@ -18,7 +18,7 @@ package io.netty.example.http.file;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
@ -35,7 +35,7 @@ public class HttpStaticFileServerInitializer extends ChannelInitializer<SocketCh
//pipeline.addLast("ssl", new SslHandler(engine));
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());

View File

@ -21,10 +21,10 @@ import io.netty.channel.socket.nio.NioEventLoopGroup;
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.DefaultHttpRequest;
import io.netty.handler.codec.http.DefaultHttpRequestHeader;
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.HttpRequestHeader;
import io.netty.handler.codec.http.HttpVersion;
import java.net.InetSocketAddress;
@ -73,7 +73,7 @@ public class HttpSnoopClient {
Channel ch = b.connect().sync().channel();
// Prepare the HTTP request.
HttpRequest request = new DefaultHttpRequest(
HttpRequestHeader request = new DefaultHttpRequestHeader(
HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath());
request.setHeader(HttpHeaders.Names.HOST, host);
request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);

View File

@ -15,21 +15,20 @@
*/
package io.netty.example.http.snoop;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundMessageHandlerAdapter;
import io.netty.handler.codec.http.HttpChunk;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.CharsetUtil;
public class HttpSnoopClientHandler extends ChannelInboundMessageHandlerAdapter<Object> {
private boolean readingChunks;
@Override
public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!readingChunks) {
HttpResponse response = (HttpResponse) msg;
if (msg instanceof HttpResponseHeader) {
HttpResponseHeader response = (HttpResponseHeader) msg;
System.out.println("STATUS: " + response.getStatus());
System.out.println("VERSION: " + response.getProtocolVersion());
@ -44,25 +43,20 @@ public class HttpSnoopClientHandler extends ChannelInboundMessageHandlerAdapter<
System.out.println();
}
if (response.getTransferEncoding().isMultiple()) {
readingChunks = true;
if (HttpHeaders.isTransferEncodingChunked(response)) {
System.out.println("CHUNKED CONTENT {");
} else {
ByteBuf content = response.getContent();
if (content.readable()) {
System.out.println("CONTENT {");
System.out.println(content.toString(CharsetUtil.UTF_8));
System.out.println("} END OF CONTENT");
}
System.out.println("CONTENT {");
}
} else {
HttpChunk chunk = (HttpChunk) msg;
if (chunk.isLast()) {
readingChunks = false;
System.out.println("} END OF CHUNKED CONTENT");
} else {
System.out.print(chunk.getContent().toString(CharsetUtil.UTF_8));
System.out.flush();
}
if (msg instanceof HttpContent) {
HttpContent content = (HttpContent) msg;
System.out.print(content.getContent().toString(CharsetUtil.UTF_8));
System.out.flush();
if (content instanceof LastHttpContent) {
System.out.println("} END OF CONTENT");
}
}
}

View File

@ -25,12 +25,14 @@ import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.CookieDecoder;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpChunk;
import io.netty.handler.codec.http.HttpChunkTrailer;
import io.netty.handler.codec.http.DefaultHttpResponseHeader;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestHeader;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.ServerCookieEncoder;
import io.netty.util.CharsetUtil;
@ -47,15 +49,15 @@ import static io.netty.handler.codec.http.HttpVersion.*;
public class HttpSnoopServerHandler extends ChannelInboundMessageHandlerAdapter<Object> {
private HttpRequest request;
private HttpRequestHeader request;
private boolean readingChunks;
/** Buffer that stores the response content */
private final StringBuilder buf = new StringBuilder();
@Override
public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!readingChunks) {
HttpRequest request = this.request = (HttpRequest) msg;
if (msg instanceof HttpRequestHeader) {
HttpRequestHeader request = this.request = (HttpRequestHeader) msg;
if (is100ContinueExpected(request)) {
send100Continue(ctx);
@ -92,25 +94,36 @@ public class HttpSnoopServerHandler extends ChannelInboundMessageHandlerAdapter<
buf.append("\r\n");
}
if (request.getTransferEncoding().isMultiple()) {
if (isTransferEncodingChunked(request)) {
readingChunks = true;
} else {
ByteBuf content = request.getContent();
if (content.readable()) {
buf.append("CONTENT: ");
buf.append(content.toString(CharsetUtil.UTF_8));
buf.append("\r\n");
}
appendDecoderResult(buf, request);
writeResponse(ctx, request);
}
} else {
HttpChunk chunk = (HttpChunk) msg;
if (chunk.isLast()) {
}
if (msg instanceof HttpContent) {
HttpContent httpContent = (HttpContent) msg;
ByteBuf content = httpContent.getContent();
if (content.readable()) {
buf.append("CONTENT: ");
buf.append(content.toString(CharsetUtil.UTF_8));
buf.append("\r\n");
}
appendDecoderResult(buf, request);
writeResponse(ctx, request);
if (msg instanceof LastHttpContent) {
if (readingChunks) {
buf.append("CHUNK: ");
} else {
buf.append("CONTENT: ");
}
buf.append(content.toString(CharsetUtil.UTF_8)).append("\r\n");
readingChunks = false;
buf.append("END OF CONTENT\r\n");
HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
LastHttpContent trailer = (LastHttpContent) msg;
if (!trailer.getHeaderNames().isEmpty()) {
buf.append("\r\n");
for (String name: trailer.getHeaderNames()) {
@ -122,12 +135,14 @@ public class HttpSnoopServerHandler extends ChannelInboundMessageHandlerAdapter<
buf.append("\r\n");
}
appendDecoderResult(buf, chunk);
writeResponse(ctx, chunk);
appendDecoderResult(buf, trailer);
writeResponse(ctx, trailer);
} else {
buf.append("CHUNK: ");
buf.append(chunk.getContent().toString(CharsetUtil.UTF_8)).append("\r\n");
appendDecoderResult(buf, chunk);
if (readingChunks) {
buf.append("CHUNK: ");
}
buf.append(content.toString(CharsetUtil.UTF_8)).append("\r\n");
appendDecoderResult(buf, httpContent);
}
}
}
@ -192,7 +207,7 @@ public class HttpSnoopServerHandler extends ChannelInboundMessageHandlerAdapter<
}
private static void send100Continue(ChannelHandlerContext ctx) {
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, CONTINUE);
HttpResponseHeader response = new DefaultHttpResponseHeader(HTTP_1_1, CONTINUE);
ctx.write(response);
}

View File

@ -15,11 +15,12 @@
*/
package io.netty.example.http.upload;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundMessageHandlerAdapter;
import io.netty.handler.codec.http.HttpChunk;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseHeader;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
import io.netty.util.CharsetUtil;
@ -35,8 +36,8 @@ public class HttpUploadClientHandler extends ChannelInboundMessageHandlerAdapter
@Override
public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!readingChunks) {
HttpResponse response = (HttpResponse) msg;
if (msg instanceof HttpResponseHeader) {
HttpResponseHeader response = (HttpResponseHeader) msg;
logger.info("STATUS: " + response.getStatus());
logger.info("VERSION: " + response.getProtocolVersion());
@ -49,22 +50,24 @@ public class HttpUploadClientHandler extends ChannelInboundMessageHandlerAdapter
}
}
if (response.getStatus().getCode() == 200 && response.getTransferEncoding().isMultiple()) {
if (response.getStatus().getCode() == 200 && HttpHeaders.isTransferEncodingChunked(response)) {
readingChunks = true;
logger.info("CHUNKED CONTENT {");
} else {
ByteBuf content = response.getContent();
if (content.readable()) {
logger.info("CONTENT {");
logger.info(content.toString(CharsetUtil.UTF_8));
logger.info("CONTENT {");
}
}
if (msg instanceof HttpContent) {
HttpContent chunk = (HttpContent) msg;
logger.info(chunk.getContent().toString(CharsetUtil.UTF_8));
if (chunk instanceof LastHttpContent) {
if (readingChunks) {
logger.info("} END OF CHUNKED CONTENT");
} else {
logger.info("} END OF CONTENT");
}
}
} else {
HttpChunk chunk = (HttpChunk) msg;
if (chunk.isLast()) {
readingChunks = false;
logger.info("} END OF CHUNKED CONTENT");
} else {
logger.info(chunk.getContent().toString(CharsetUtil.UTF_8));
}

View File

@ -24,9 +24,9 @@ import io.netty.channel.ChannelInboundMessageHandlerAdapter;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.CookieDecoder;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpChunk;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestHeader;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
@ -64,7 +64,7 @@ public class HttpUploadServerHandler extends ChannelInboundMessageHandlerAdapter
private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpUploadServerHandler.class);
private HttpRequest request;
private HttpRequestHeader request;
private boolean readingChunks;
@ -95,14 +95,14 @@ public class HttpUploadServerHandler extends ChannelInboundMessageHandlerAdapter
@Override
public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!readingChunks) {
if (msg instanceof HttpRequestHeader) {
// clean previous FileUpload if Any
if (decoder != null) {
decoder.cleanFiles();
decoder = null;
}
HttpRequest request = this.request = (HttpRequest) msg;
HttpRequestHeader request = this.request = (HttpRequestHeader) msg;
URI uri = new URI(request.getUri());
if (!uri.getPath().startsWith("/form")) {
// Write Menu
@ -165,9 +165,10 @@ public class HttpUploadServerHandler extends ChannelInboundMessageHandlerAdapter
return;
}
responseContent.append("Is Chunked: " + request.getTransferEncoding().isMultiple() + "\r\n");
readingChunks = HttpHeaders.isTransferEncodingChunked(request);
responseContent.append("Is Chunked: " + readingChunks + "\r\n");
responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n");
if (request.getTransferEncoding().isMultiple()) {
if (readingChunks) {
// Chunk version
responseContent.append("Chunks: ");
readingChunks = true;
@ -177,9 +178,10 @@ public class HttpUploadServerHandler extends ChannelInboundMessageHandlerAdapter
responseContent.append("\r\n\r\nEND OF NOT CHUNKED CONTENT\r\n");
writeResponse(ctx.channel());
}
} else {
}
if (msg instanceof HttpContent) {
// New chunk is received
HttpChunk chunk = (HttpChunk) msg;
HttpContent chunk = (HttpContent) msg;
try {
decoder.offer(chunk);
} catch (ErrorDataDecoderException e1) {
@ -194,7 +196,7 @@ public class HttpUploadServerHandler extends ChannelInboundMessageHandlerAdapter
// Factory)
readHttpDataChunkByChunk();
// example of reading only if at the end
if (chunk.isLast()) {
if (chunk instanceof HttpContent) {
readHttpDataAllReceive(ctx.channel());
writeResponse(ctx.channel());
readingChunks = false;

View File

@ -18,7 +18,7 @@ package io.netty.example.http.websocketx.autobahn;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
@ -27,7 +27,7 @@ public class AutobahnServerInitializer extends ChannelInitializer<SocketChannel>
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("handler", new AutobahnServerHandler());
}

View File

@ -21,7 +21,7 @@ import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
@ -69,7 +69,7 @@ public class WebSocketServer {
public void initChannel(final SocketChannel ch) throws Exception {
ch.pipeline().addLast(
new HttpRequestDecoder(),
new HttpChunkAggregator(65536),
new HttpObjectAggregator(65536),
new HttpResponseEncoder(),
new WebSocketServerProtocolHandler("/websocket"),
new CustomTextFrameHandler());

View File

@ -18,7 +18,7 @@ package io.netty.example.http.websocketx.server;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
@ -29,7 +29,7 @@ public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("handler", new WebSocketServerHandler());
}

View File

@ -18,7 +18,7 @@ package io.netty.example.http.websocketx.sslserver;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.ssl.SslHandler;
@ -37,7 +37,7 @@ public class WebSocketSslServerInitializer extends ChannelInitializer<SocketChan
pipeline.addLast("ssl", new SslHandler(engine));
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("handler", new WebSocketSslServerHandler());
}