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.http2;
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: 1107 $, $Date: 2012-04-15 19:00:57 +0200 (dim., 15 avr. 2012) $
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 }