View Javadoc

1   /*
2    * Copyright 2009 Red Hat, Inc.
3    *
4    * Red Hat licenses this file to you under the Apache License, version 2.0
5    * (the "License"); you may not use this file except in compliance with the
6    * 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.Channels;
22  import org.jboss.netty.channel.MessageEvent;
23  import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
24  import org.jboss.netty.handler.codec.embedder.DecoderEmbedder;
25  
26  /**
27   * Decodes the content of the received {@link HttpRequest} and {@link HttpChunk}.
28   * The original content is replaced with the new content decoded by the
29   * {@link DecoderEmbedder}, which is created by {@link #newContentDecoder(String)}.
30   * Once decoding is finished, the value of the <tt>'Content-Encoding'</tt>
31   * header is set to the target content encoding, as returned by {@link #getTargetContentEncoding(String)}.
32   * Also, the <tt>'Content-Length'</tt> header is updated to the length of the
33   * decoded content.  If the content encoding of the original is not supported
34   * by the decoder, {@link #newContentDecoder(String)} should return {@code null}
35   * so that no decoding occurs (i.e. pass-through).
36   * <p>
37   * Please note that this is an abstract class.  You have to extend this class
38   * and implement {@link #newContentDecoder(String)} properly to make this class
39   * functional.  For example, refer to the source code of {@link HttpContentDecompressor}.
40   * <p>
41   * This handler must be placed after {@link HttpMessageDecoder} in the pipeline
42   * so that this handler can intercept HTTP requests after {@link HttpMessageDecoder}
43   * converts {@link ChannelBuffer}s into HTTP requests.
44   *
45   * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
46   * @author <a href="http://gleamynode.net/">Trustin Lee</a>
47   * @version $Rev: 2368 $, $Date: 2010-10-18 17:19:03 +0900 (Mon, 18 Oct 2010) $
48   */
49  public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler {
50  
51      private DecoderEmbedder<ChannelBuffer> decoder;
52  
53      /**
54       * Creates a new instance.
55       */
56      protected HttpContentDecoder() {
57          super();
58      }
59  
60      @Override
61      public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
62          Object msg = e.getMessage();
63          if (msg instanceof HttpResponse && ((HttpResponse) msg).getStatus().getCode() == 100) {
64              // 100-continue response must be passed through.
65              ctx.sendUpstream(e);
66          } else if (msg instanceof HttpMessage) {
67              HttpMessage m = (HttpMessage) msg;
68  
69              decoder = null;
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     /**
139      * Returns a new {@link DecoderEmbedder} that decodes the HTTP message
140      * content encoded in the specified <tt>contentEncoding</tt>.
141      *
142      * @param contentEncoding the value of the {@code "Content-Encoding"} header
143      * @return a new {@link DecoderEmbedder} if the specified encoding is supported.
144      *         {@code null} otherwise (alternatively, you can throw an exception
145      *         to block unknown encoding).
146      */
147     protected abstract DecoderEmbedder<ChannelBuffer> newContentDecoder(String contentEncoding) throws Exception;
148 
149     /**
150      * Returns the expected content encoding of the decoded content.
151      * This method returns {@code "identity"} by default, which is the case for
152      * most decoders.
153      *
154      * @param contentEncoding the value of the {@code "Content-Encoding"} header
155      * @return the expected content encoding of the new content
156      */
157     protected String getTargetContentEncoding(String contentEncoding) throws Exception {
158         return HttpHeaders.Values.IDENTITY;
159     }
160 
161     private ChannelBuffer decode(ChannelBuffer buf) {
162         decoder.offer(buf);
163         return ChannelBuffers.wrappedBuffer(decoder.pollAll(new ChannelBuffer[decoder.size()]));
164     }
165 
166     private ChannelBuffer finishDecode() {
167         ChannelBuffer result;
168         if (decoder.finish()) {
169             result = ChannelBuffers.wrappedBuffer(decoder.pollAll(new ChannelBuffer[decoder.size()]));
170         } else {
171             result = ChannelBuffers.EMPTY_BUFFER;
172         }
173         decoder = null;
174         return result;
175     }
176 }