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          super();
57      }
58  
59      @Override
60      public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
61          Object msg = e.getMessage();
62          if (msg instanceof HttpResponse && ((HttpResponse) msg).getStatus().getCode() == 100) {
63              // 100-continue response must be passed through.
64              ctx.sendUpstream(e);
65          } else if (msg instanceof HttpMessage) {
66              HttpMessage m = (HttpMessage) msg;
67  
68              // Clean-up the previous decoder if not cleaned up correctly.
69              finishDecode();
70  
71              // Determine the content encoding.
72              String contentEncoding = m.getHeader(HttpHeaders.Names.CONTENT_ENCODING);
73              if (contentEncoding != null) {
74                  contentEncoding = contentEncoding.trim();
75              } else {
76                  contentEncoding = HttpHeaders.Values.IDENTITY;
77              }
78  
79              boolean hasContent = m.isChunked() || m.getContent().readable();
80              if (hasContent && (decoder = newContentDecoder(contentEncoding)) != null) {
81                  // Decode the content and remove or replace the existing headers
82                  // so that the message looks like a decoded message.
83                  m.setHeader(
84                          HttpHeaders.Names.CONTENT_ENCODING,
85                          getTargetContentEncoding(contentEncoding));
86  
87                  if (!m.isChunked()) {
88                      ChannelBuffer content = m.getContent();
89                      // Decode the content
90                      content = ChannelBuffers.wrappedBuffer(
91                              decode(content), finishDecode());
92  
93                      // Replace the content.
94                      m.setContent(content);
95                      if (m.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) {
96                          m.setHeader(
97                                  HttpHeaders.Names.CONTENT_LENGTH,
98                                  Integer.toString(content.readableBytes()));
99                      }
100                 }
101             }
102 
103             // Because HttpMessage is a mutable object, we can simply forward the received event.
104             ctx.sendUpstream(e);
105         } else if (msg instanceof HttpChunk) {
106             HttpChunk c = (HttpChunk) msg;
107             ChannelBuffer content = c.getContent();
108 
109             // Decode the chunk if necessary.
110             if (decoder != null) {
111                 if (!c.isLast()) {
112                     content = decode(content);
113                     if (content.readable()) {
114                         c.setContent(content);
115                         ctx.sendUpstream(e);
116                     }
117                 } else {
118                     ChannelBuffer lastProduct = finishDecode();
119 
120                     // Generate an additional chunk if the decoder produced
121                     // the last product on closure,
122                     if (lastProduct.readable()) {
123                         Channels.fireMessageReceived(
124                                 ctx, new DefaultHttpChunk(lastProduct), e.getRemoteAddress());
125                     }
126 
127                     // Emit the last chunk.
128                     ctx.sendUpstream(e);
129                 }
130             } else {
131                 ctx.sendUpstream(e);
132             }
133         } else {
134             ctx.sendUpstream(e);
135         }
136     }
137 
138     @Override
139     public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
140         // Clean-up the previous decoder if not cleaned up correctly.
141         finishDecode();
142 
143         super.channelClosed(ctx, e);
144     }
145 
146     /**
147      * Returns a new {@link DecoderEmbedder} that decodes the HTTP message
148      * content encoded in the specified <tt>contentEncoding</tt>.
149      *
150      * @param contentEncoding the value of the {@code "Content-Encoding"} header
151      * @return a new {@link DecoderEmbedder} if the specified encoding is supported.
152      *         {@code null} otherwise (alternatively, you can throw an exception
153      *         to block unknown encoding).
154      */
155     protected abstract DecoderEmbedder<ChannelBuffer> newContentDecoder(String contentEncoding) throws Exception;
156 
157     /**
158      * Returns the expected content encoding of the decoded content.
159      * This method returns {@code "identity"} by default, which is the case for
160      * most decoders.
161      *
162      * @param contentEncoding the value of the {@code "Content-Encoding"} header
163      * @return the expected content encoding of the new content
164      */
165     protected String getTargetContentEncoding(String contentEncoding) throws Exception {
166         return HttpHeaders.Values.IDENTITY;
167     }
168 
169     private ChannelBuffer decode(ChannelBuffer buf) {
170         decoder.offer(buf);
171         return ChannelBuffers.wrappedBuffer(decoder.pollAll(new ChannelBuffer[decoder.size()]));
172     }
173 
174     private ChannelBuffer finishDecode() {
175         if (decoder == null) {
176             return ChannelBuffers.EMPTY_BUFFER;
177         }
178 
179         ChannelBuffer result;
180         if (decoder.finish()) {
181             result = ChannelBuffers.wrappedBuffer(decoder.pollAll(new ChannelBuffer[decoder.size()]));
182         } else {
183             result = ChannelBuffers.EMPTY_BUFFER;
184         }
185         decoder = null;
186         return result;
187     }
188 
189     public void beforeAdd(ChannelHandlerContext ctx) throws Exception {
190         // NOOP
191     }
192 
193     public void afterAdd(ChannelHandlerContext ctx) throws Exception {
194         // NOOP
195     }
196 
197     public void beforeRemove(ChannelHandlerContext ctx) throws Exception {
198         // NOOP
199     }
200 
201     public void afterRemove(ChannelHandlerContext ctx) throws Exception {
202         finishDecode();
203     }
204 }