View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package org.jboss.netty.handler.codec.http;
17  
18  import org.jboss.netty.buffer.ChannelBuffer;
19  import org.jboss.netty.buffer.ChannelBuffers;
20  import org.jboss.netty.channel.ChannelHandlerContext;
21  import org.jboss.netty.channel.ChannelStateEvent;
22  import org.jboss.netty.channel.Channels;
23  import org.jboss.netty.channel.LifeCycleAwareChannelHandler;
24  import org.jboss.netty.channel.MessageEvent;
25  import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
26  import org.jboss.netty.handler.codec.embedder.DecoderEmbedder;
27  
28  /**
29   * Decodes the content of the received {@link HttpRequest} and {@link HttpChunk}.
30   * The original content is replaced with the new content decoded by the
31   * {@link DecoderEmbedder}, which is created by {@link #newContentDecoder(String)}.
32   * Once decoding is finished, the value of the <tt>'Content-Encoding'</tt>
33   * header is set to the target content encoding, as returned by {@link #getTargetContentEncoding(String)}.
34   * Also, the <tt>'Content-Length'</tt> header is updated to the length of the
35   * decoded content.  If the content encoding of the original is not supported
36   * by the decoder, {@link #newContentDecoder(String)} should return {@code null}
37   * so that no decoding occurs (i.e. pass-through).
38   * <p>
39   * Please note that this is an abstract class.  You have to extend this class
40   * and implement {@link #newContentDecoder(String)} properly to make this class
41   * functional.  For example, refer to the source code of {@link HttpContentDecompressor}.
42   * <p>
43   * This handler must be placed after {@link HttpMessageDecoder} in the pipeline
44   * so that this handler can intercept HTTP requests after {@link HttpMessageDecoder}
45   * converts {@link ChannelBuffer}s into HTTP requests.
46   */
47  public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler
48                                           implements LifeCycleAwareChannelHandler {
49  
50      private DecoderEmbedder<ChannelBuffer> decoder;
51  
52      /**
53       * Creates a new instance.
54       */
55      protected HttpContentDecoder() {
56      }
57  
58      @Override
59      public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
60          Object msg = e.getMessage();
61          if (msg instanceof HttpResponse && ((HttpResponse) msg).getStatus().getCode() == 100) {
62              // 100-continue response must be passed through.
63              ctx.sendUpstream(e);
64          } else if (msg instanceof HttpMessage) {
65              HttpMessage m = (HttpMessage) msg;
66  
67              // Clean-up the previous decoder if not cleaned up correctly.
68              finishDecode();
69  
70              // Determine the content encoding.
71              String contentEncoding = m.headers().get(HttpHeaders.Names.CONTENT_ENCODING);
72              if (contentEncoding != null) {
73                  contentEncoding = contentEncoding.trim();
74              } else {
75                  contentEncoding = HttpHeaders.Values.IDENTITY;
76              }
77  
78              boolean hasContent = m.isChunked() || m.getContent().readable();
79              if (hasContent && (decoder = newContentDecoder(contentEncoding)) != null) {
80                  // Decode the content and remove or replace the existing headers
81                  // so that the message looks like a decoded message.
82                  String targetContentEncoding = getTargetContentEncoding(contentEncoding);
83                  if (HttpHeaders.Values.IDENTITY.equals(targetContentEncoding)) {
84                      // Do NOT set the 'Content-Encoding' header if the target encoding is 'identity'
85                      // as per: http://tools.ietf.org/html/rfc2616#section-14.11
86                      m.headers().remove(HttpHeaders.Names.CONTENT_ENCODING);
87                  } else {
88                      m.headers().set(HttpHeaders.Names.CONTENT_ENCODING, targetContentEncoding);
89                  }
90  
91                  if (!m.isChunked()) {
92                      ChannelBuffer content = m.getContent();
93                      // Decode the content
94                      content = ChannelBuffers.wrappedBuffer(
95                              decode(content), finishDecode());
96  
97                      // Replace the content.
98                      m.setContent(content);
99                      if (m.headers().contains(HttpHeaders.Names.CONTENT_LENGTH)) {
100                         m.headers().set(
101                                 HttpHeaders.Names.CONTENT_LENGTH,
102                                 Integer.toString(content.readableBytes()));
103                     }
104                 }
105             }
106 
107             // Because HttpMessage is a mutable object, we can simply forward the received event.
108             ctx.sendUpstream(e);
109         } else if (msg instanceof HttpChunk) {
110             HttpChunk c = (HttpChunk) msg;
111             ChannelBuffer content = c.getContent();
112 
113             // Decode the chunk if necessary.
114             if (decoder != null) {
115                 if (!c.isLast()) {
116                     content = decode(content);
117                     if (content.readable()) {
118                         c.setContent(content);
119                         ctx.sendUpstream(e);
120                     }
121                 } else {
122                     ChannelBuffer lastProduct = finishDecode();
123 
124                     // Generate an additional chunk if the decoder produced
125                     // the last product on closure,
126                     if (lastProduct.readable()) {
127                         Channels.fireMessageReceived(
128                                 ctx, new DefaultHttpChunk(lastProduct), e.getRemoteAddress());
129                     }
130 
131                     // Emit the last chunk.
132                     ctx.sendUpstream(e);
133                 }
134             } else {
135                 ctx.sendUpstream(e);
136             }
137         } else {
138             ctx.sendUpstream(e);
139         }
140     }
141 
142     @Override
143     public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
144         // Clean-up the previous decoder if not cleaned up correctly.
145         finishDecode();
146 
147         super.channelClosed(ctx, e);
148     }
149 
150     /**
151      * Returns a new {@link DecoderEmbedder} that decodes the HTTP message
152      * content encoded in the specified <tt>contentEncoding</tt>.
153      *
154      * @param contentEncoding the value of the {@code "Content-Encoding"} header
155      * @return a new {@link DecoderEmbedder} if the specified encoding is supported.
156      *         {@code null} otherwise (alternatively, you can throw an exception
157      *         to block unknown encoding).
158      */
159     protected abstract DecoderEmbedder<ChannelBuffer> newContentDecoder(String contentEncoding) throws Exception;
160 
161     /**
162      * Returns the expected content encoding of the decoded content.
163      * This method returns {@code "identity"} by default, which is the case for
164      * most decoders.
165      *
166      * @param contentEncoding the value of the {@code "Content-Encoding"} header
167      * @return the expected content encoding of the new content
168      */
169     protected String getTargetContentEncoding(String contentEncoding) throws Exception {
170         return HttpHeaders.Values.IDENTITY;
171     }
172 
173     private ChannelBuffer decode(ChannelBuffer buf) {
174         decoder.offer(buf);
175         return ChannelBuffers.wrappedBuffer(decoder.pollAll(new ChannelBuffer[decoder.size()]));
176     }
177 
178     private ChannelBuffer finishDecode() {
179         if (decoder == null) {
180             return ChannelBuffers.EMPTY_BUFFER;
181         }
182 
183         ChannelBuffer result;
184         if (decoder.finish()) {
185             result = ChannelBuffers.wrappedBuffer(decoder.pollAll(new ChannelBuffer[decoder.size()]));
186         } else {
187             result = ChannelBuffers.EMPTY_BUFFER;
188         }
189         decoder = null;
190         return result;
191     }
192 
193     public void beforeAdd(ChannelHandlerContext ctx) throws Exception {
194         // NOOP
195     }
196 
197     public void afterAdd(ChannelHandlerContext ctx) throws Exception {
198         // NOOP
199     }
200 
201     public void beforeRemove(ChannelHandlerContext ctx) throws Exception {
202         // NOOP
203     }
204 
205     public void afterRemove(ChannelHandlerContext ctx) throws Exception {
206         finishDecode();
207     }
208 }