[#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:
parent
506474f569
commit
b7de868003
@ -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>
|
||||
*
|
||||
|
@ -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<{@link Cookie}> cookies = new {@link CookieDecoder}().decode(value);
|
||||
* </pre>
|
||||
|
@ -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());
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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(')');
|
@ -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;
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
@ -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.
|
@ -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;
|
||||
|
@ -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>.
|
||||
|
@ -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}.
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
@ -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;
|
||||
|
@ -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 {
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
@ -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;
|
||||
}
|
@ -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 {
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
@ -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 {
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.
|
@ -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>
|
||||
*
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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()));
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
@ -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);
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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}.
|
||||
*
|
||||
|
@ -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.
|
@ -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());
|
||||
|
@ -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);
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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")
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user