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