(new ZlibEncoder(wrapper, compressionLevel));
+ }
+
+ @Override
+ protected String getTargetContentEncoding(String acceptEncoding) throws Exception {
+ ZlibWrapper wrapper = determineWrapper(acceptEncoding);
+ if (wrapper == null) {
+ return null;
+ }
+
+ switch (wrapper) {
+ case GZIP:
+ return "gzip";
+ case ZLIB:
+ return "deflate";
+ default:
+ throw new Error();
+ }
+ }
+
+ private ZlibWrapper determineWrapper(String acceptEncoding) {
+ // FIXME: Use the Q value.
+ if (acceptEncoding.indexOf("gzip") >= 0) {
+ return ZlibWrapper.GZIP;
+ }
+ if (acceptEncoding.indexOf("deflate") >= 0) {
+ return ZlibWrapper.ZLIB;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/HttpContentDecoder.java b/src/main/java/org/jboss/netty/handler/codec/http/HttpContentDecoder.java
index 71b08f273c..eac7265914 100644
--- a/src/main/java/org/jboss/netty/handler/codec/http/HttpContentDecoder.java
+++ b/src/main/java/org/jboss/netty/handler/codec/http/HttpContentDecoder.java
@@ -28,15 +28,15 @@ import org.jboss.netty.handler.codec.embedder.DecoderEmbedder;
* Decodes the content of the received {@link HttpMessage} and {@link HttpChunk}.
* The original content ({@link HttpMessage#getContent()} or {@link HttpChunk#getContent()})
* is replaced with the new content decoded by the {@link DecoderEmbedder},
- * which is created by {@link #newDecoder(String)}. Once decoding is finished,
+ * which is created by {@link #newContentDecoder(String)}. Once decoding is finished,
* the value of the 'Content-Encoding' header is set to 'identity'
* and the 'Content-Length' header is updated to the length of the
* decoded content. If the content encoding of the original is not supported
- * by the decoder, {@link #newDecoder(String)} returns {@code null} and no
+ * by the decoder, {@link #newContentDecoder(String)} returns {@code null} and no
* decoding occurs (i.e. pass-through).
*
* Please note that this is an abstract class. You have to extend this class
- * and implement {@link #newDecoder(String)} properly to make this class
+ * and implement {@link #newContentDecoder(String)} properly to make this class
* functional. For example, refer to the source code of {@link HttpContentDecompressor}.
*
* @author The Netty Project (netty-dev@lists.jboss.org)
@@ -46,7 +46,6 @@ import org.jboss.netty.handler.codec.embedder.DecoderEmbedder;
@ChannelPipelineCoverage("one")
public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler {
- private volatile HttpMessage previous;
private volatile DecoderEmbedder decoder;
/**
@@ -63,11 +62,6 @@ public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler {
HttpMessage m = (HttpMessage) msg;
decoder = null;
- if (m.isChunked()) {
- previous = m;
- } else {
- previous = null;
- }
// Determine the content encoding.
String contentEncoding = m.getHeader(HttpHeaders.Names.CONTENT_ENCODING);
@@ -77,29 +71,25 @@ public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler {
contentEncoding = HttpHeaders.Values.IDENTITY;
}
- if ((decoder = newDecoder(contentEncoding)) != null) {
+ if ((decoder = newContentDecoder(contentEncoding)) != null) {
// Decode the content and remove or replace the existing headers
// so that the message looks like a decoded message.
m.setHeader(
HttpHeaders.Names.CONTENT_ENCODING,
- getTargetEncoding(contentEncoding));
+ getTargetContentEncoding(contentEncoding));
if (!m.isChunked()) {
ChannelBuffer content = m.getContent();
- if (content.readable()) {
- // Decode the content
- content = ChannelBuffers.wrappedBuffer(
- decode(content), finishDecode());
+ // Decode the content
+ content = ChannelBuffers.wrappedBuffer(
+ decode(content), finishDecode());
- // Replace the content if necessary.
- if (content != null) {
- m.setContent(content);
- if (m.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) {
- m.setHeader(
- HttpHeaders.Names.CONTENT_LENGTH,
- Integer.toString(content.readableBytes()));
- }
- }
+ // Replace the content.
+ m.setContent(content);
+ if (m.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) {
+ m.setHeader(
+ HttpHeaders.Names.CONTENT_LENGTH,
+ Integer.toString(content.readableBytes()));
}
}
}
@@ -107,8 +97,6 @@ public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler {
// Because HttpMessage is a mutable object, we can simply forward the received event.
ctx.sendUpstream(e);
} else if (msg instanceof HttpChunk) {
- assert previous != null;
-
HttpChunk c = (HttpChunk) msg;
ChannelBuffer content = c.getContent();
@@ -124,8 +112,6 @@ public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler {
}
} else {
ChannelBuffer lastProduct = finishDecode();
- previous = null;
- decoder = null;
// Generate an additional chunk if the decoder produced
// the last product on closure,
@@ -154,7 +140,7 @@ public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler {
* {@code null} otherwise (alternatively, you can throw an exception
* to block unknown encoding).
*/
- protected abstract DecoderEmbedder newDecoder(String contentEncoding) throws Exception;
+ protected abstract DecoderEmbedder newContentDecoder(String contentEncoding) throws Exception;
/**
* Returns the expected content encoding of the decoded content.
@@ -164,7 +150,7 @@ public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler {
* @param contentEncoding the content encoding of the original content
* @return the expected content encoding of the new content
*/
- protected String getTargetEncoding(String contentEncoding) throws Exception {
+ protected String getTargetContentEncoding(String contentEncoding) throws Exception {
return HttpHeaders.Values.IDENTITY;
}
@@ -174,10 +160,13 @@ public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler {
}
private ChannelBuffer finishDecode() {
+ ChannelBuffer result;
if (decoder.finish()) {
- return ChannelBuffers.wrappedBuffer(decoder.pollAll(new ChannelBuffer[decoder.size()]));
+ result = ChannelBuffers.wrappedBuffer(decoder.pollAll(new ChannelBuffer[decoder.size()]));
} else {
- return ChannelBuffers.EMPTY_BUFFER;
+ result = ChannelBuffers.EMPTY_BUFFER;
}
+ decoder = null;
+ return result;
}
}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/HttpContentDecompressor.java b/src/main/java/org/jboss/netty/handler/codec/http/HttpContentDecompressor.java
index 8c5b3cc43c..2bbe6f2a1d 100644
--- a/src/main/java/org/jboss/netty/handler/codec/http/HttpContentDecompressor.java
+++ b/src/main/java/org/jboss/netty/handler/codec/http/HttpContentDecompressor.java
@@ -43,7 +43,7 @@ import org.jboss.netty.handler.codec.embedder.DecoderEmbedder;
@ChannelPipelineCoverage("one")
public class HttpContentDecompressor extends HttpContentDecoder {
@Override
- protected DecoderEmbedder newDecoder(String contentEncoding) throws Exception {
+ protected DecoderEmbedder newContentDecoder(String contentEncoding) throws Exception {
if ("gzip".equalsIgnoreCase(contentEncoding) || "x-gzip".equalsIgnoreCase(contentEncoding)) {
return new DecoderEmbedder(new ZlibDecoder(ZlibWrapper.GZIP));
} else if ("deflate".equalsIgnoreCase(contentEncoding) || "x-deflate".equalsIgnoreCase(contentEncoding)) {
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/HttpContentEncoder.java b/src/main/java/org/jboss/netty/handler/codec/http/HttpContentEncoder.java
new file mode 100644
index 0000000000..0a446cda97
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/HttpContentEncoder.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2009 Red Hat, Inc.
+ *
+ * Red Hat 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 org.jboss.netty.handler.codec.http;
+
+import java.util.Queue;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelHandler;
+import org.jboss.netty.handler.codec.embedder.DecoderEmbedder;
+import org.jboss.netty.handler.codec.embedder.EncoderEmbedder;
+import org.jboss.netty.util.internal.LinkedTransferQueue;
+
+/**
+ *
+ * @author The Netty Project (netty-dev@lists.jboss.org)
+ * @author Trustin Lee (tlee@redhat.com)
+ * @version $Rev$, $Date$
+ */
+public abstract class HttpContentEncoder extends SimpleChannelHandler {
+
+ private final Queue acceptEncodingQueue = new LinkedTransferQueue();
+ private volatile EncoderEmbedder encoder;
+
+ /**
+ * Creates a new instance.
+ */
+ protected HttpContentEncoder() {
+ super();
+ }
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
+ throws Exception {
+ Object msg = e.getMessage();
+ if (!(msg instanceof HttpMessage)) {
+ ctx.sendUpstream(e);
+ return;
+ }
+
+ HttpMessage m = (HttpMessage) msg;
+ String acceptedEncoding = m.getHeader(HttpHeaders.Names.ACCEPT_ENCODING);
+ if (acceptedEncoding == null) {
+ acceptedEncoding = HttpHeaders.Values.IDENTITY;
+ }
+ acceptEncodingQueue.offer(acceptedEncoding);
+
+ ctx.sendUpstream(e);
+ }
+
+ @Override
+ public void writeRequested(ChannelHandlerContext ctx, MessageEvent e)
+ throws Exception {
+
+ Object msg = e.getMessage();
+ if (msg instanceof HttpMessage) {
+ HttpMessage m = (HttpMessage) msg;
+
+ encoder = null;
+
+ // Determine the content encoding.
+ String acceptEncoding = acceptEncodingQueue.poll();
+ if (acceptEncoding == null) {
+ throw new IllegalStateException("cannot send more responses than requests");
+ }
+
+ if ((encoder = newContentEncoder(acceptEncoding)) != null) {
+ // 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,
+ getTargetContentEncoding(acceptEncoding));
+
+ if (!m.isChunked()) {
+ ChannelBuffer content = m.getContent();
+ // Encode the content.
+ content = ChannelBuffers.wrappedBuffer(
+ encode(content), finishEncode());
+
+ // Replace the content.
+ m.setContent(content);
+ if (m.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) {
+ m.setHeader(
+ HttpHeaders.Names.CONTENT_LENGTH,
+ Integer.toString(content.readableBytes()));
+ }
+ }
+ }
+
+ // Because HttpMessage is a mutable object, we can simply forward the write request.
+ ctx.sendDownstream(e);
+ } else if (msg instanceof HttpChunk) {
+ HttpChunk c = (HttpChunk) msg;
+ ChannelBuffer content = c.getContent();
+
+ // Encode the chunk if necessary.
+ if (encoder != null) {
+ if (!c.isLast()) {
+ content = encode(content);
+ if (content.readable()) {
+ // Note that HttpChunk is immutable unlike HttpMessage.
+ // XXX API inconsistency? I can live with it though.
+ Channels.write(
+ ctx, e.getFuture(), new DefaultHttpChunk(content), e.getRemoteAddress());
+ }
+ } else {
+ ChannelBuffer lastProduct = finishEncode();
+
+ // Generate an additional chunk if the decoder produced
+ // the last product on closure,
+ if (lastProduct.readable()) {
+ Channels.write(
+ ctx, Channels.succeededFuture(e.getChannel()), new DefaultHttpChunk(lastProduct), e.getRemoteAddress());
+ }
+
+ // Emit the last chunk.
+ ctx.sendDownstream(e);
+ }
+ } else {
+ ctx.sendDownstream(e);
+ }
+ } else {
+ ctx.sendDownstream(e);
+ }
+ }
+
+ /**
+ * Returns a new {@link EncoderEmbedder} that encodes the HTTP message
+ * content.
+ *
+ * @param acceptEncoding
+ * the value of the {@code "Accept-Encoding"} header.
+ *
+ * @return a new {@link DecoderEmbedder} if the specified encoding is supported.
+ * {@code null} otherwise (alternatively, you can throw an exception
+ * to block unknown encoding).
+ */
+ protected abstract EncoderEmbedder newContentEncoder(String acceptEncoding) throws Exception;
+
+ /**
+ * Returns the expected content encoding of the encoded content.
+ *
+ * @param contentEncoding the content encoding of the original content
+ * @return the expected content encoding of the new content
+ */
+ protected abstract String getTargetContentEncoding(String acceptEncoding) throws Exception;
+
+ private ChannelBuffer encode(ChannelBuffer buf) {
+ encoder.offer(buf);
+ return ChannelBuffers.wrappedBuffer(encoder.pollAll(new ChannelBuffer[encoder.size()]));
+ }
+
+ private ChannelBuffer finishEncode() {
+ ChannelBuffer result;
+ if (encoder.finish()) {
+ result = ChannelBuffers.wrappedBuffer(encoder.pollAll(new ChannelBuffer[encoder.size()]));
+ } else {
+ result = ChannelBuffers.EMPTY_BUFFER;
+ }
+ encoder = null;
+ return result;
+ }
+}