1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.channel.embedded.EmbeddedChannel;
21 import io.netty.handler.codec.CodecException;
22 import io.netty.handler.codec.DecoderResult;
23 import io.netty.handler.codec.MessageToMessageDecoder;
24 import io.netty.util.ReferenceCountUtil;
25
26 import java.util.List;
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 public abstract class HttpContentDecoder extends MessageToMessageDecoder<HttpObject> {
48
49 static final String IDENTITY = HttpHeaderValues.IDENTITY.toString();
50
51 protected ChannelHandlerContext ctx;
52 private EmbeddedChannel decoder;
53 private boolean continueResponse;
54 private boolean needRead = true;
55
56 public HttpContentDecoder() {
57 super(HttpObject.class);
58 }
59
60 @Override
61 protected void decode(ChannelHandlerContext ctx, HttpObject msg, List<Object> out) throws Exception {
62 try {
63 if (msg instanceof HttpResponse && ((HttpResponse) msg).status().code() == 100) {
64
65 if (!(msg instanceof LastHttpContent)) {
66 continueResponse = true;
67 }
68
69 out.add(ReferenceCountUtil.retain(msg));
70 return;
71 }
72
73 if (continueResponse) {
74 if (msg instanceof LastHttpContent) {
75 continueResponse = false;
76 }
77
78 out.add(ReferenceCountUtil.retain(msg));
79 return;
80 }
81
82 if (msg instanceof HttpMessage) {
83 cleanup();
84 final HttpMessage message = (HttpMessage) msg;
85 final HttpHeaders headers = message.headers();
86
87
88 String contentEncoding = headers.get(HttpHeaderNames.CONTENT_ENCODING);
89 if (contentEncoding != null) {
90 contentEncoding = contentEncoding.trim();
91 } else {
92 String transferEncoding = headers.get(HttpHeaderNames.TRANSFER_ENCODING);
93 if (transferEncoding != null) {
94 int idx = transferEncoding.indexOf(',');
95 if (idx != -1) {
96 contentEncoding = transferEncoding.substring(0, idx).trim();
97 } else {
98 contentEncoding = transferEncoding.trim();
99 }
100 } else {
101 contentEncoding = IDENTITY;
102 }
103 }
104 decoder = newContentDecoder(contentEncoding);
105
106 if (decoder == null) {
107 if (message instanceof HttpContent) {
108 ((HttpContent) message).retain();
109 }
110 out.add(message);
111 return;
112 }
113
114
115
116
117
118 if (headers.contains(HttpHeaderNames.CONTENT_LENGTH)) {
119 headers.remove(HttpHeaderNames.CONTENT_LENGTH);
120 headers.set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
121 }
122
123
124
125
126 CharSequence targetContentEncoding = getTargetContentEncoding(contentEncoding);
127 if (HttpHeaderValues.IDENTITY.contentEquals(targetContentEncoding)) {
128
129
130 headers.remove(HttpHeaderNames.CONTENT_ENCODING);
131 } else {
132 headers.set(HttpHeaderNames.CONTENT_ENCODING, targetContentEncoding);
133 }
134
135 if (message instanceof HttpContent) {
136
137
138
139
140 HttpMessage copy;
141 if (message instanceof HttpRequest) {
142 HttpRequest r = (HttpRequest) message;
143 copy = new DefaultHttpRequest(r.protocolVersion(), r.method(), r.uri());
144 } else if (message instanceof HttpResponse) {
145 HttpResponse r = (HttpResponse) message;
146 copy = new DefaultHttpResponse(r.protocolVersion(), r.status());
147 } else {
148 throw new CodecException("Object of class " + message.getClass().getName() +
149 " is not an HttpRequest or HttpResponse");
150 }
151 copy.headers().set(message.headers());
152 copy.setDecoderResult(message.decoderResult());
153 out.add(copy);
154 } else {
155 out.add(message);
156 }
157 }
158
159 if (msg instanceof HttpContent) {
160 final HttpContent c = (HttpContent) msg;
161 if (decoder == null) {
162 out.add(c.retain());
163 } else {
164 decodeContent(c, out);
165 }
166 }
167 } finally {
168 needRead = out.isEmpty();
169 }
170 }
171
172 private void decodeContent(HttpContent c, List<Object> out) {
173 ByteBuf content = c.content();
174
175 decode(content, out);
176
177 if (c instanceof LastHttpContent) {
178 finishDecode(out);
179
180 LastHttpContent last = (LastHttpContent) c;
181
182
183 HttpHeaders headers = last.trailingHeaders();
184 if (headers.isEmpty()) {
185 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
186 } else {
187 out.add(new ComposedLastHttpContent(headers, DecoderResult.SUCCESS));
188 }
189 }
190 }
191
192 @Override
193 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
194 boolean needRead = this.needRead;
195 this.needRead = true;
196
197 try {
198 ctx.fireChannelReadComplete();
199 } finally {
200 if (needRead && !ctx.channel().config().isAutoRead()) {
201 ctx.read();
202 }
203 }
204 }
205
206
207
208
209
210
211
212
213
214
215 protected abstract EmbeddedChannel newContentDecoder(String contentEncoding) throws Exception;
216
217
218
219
220
221
222
223
224
225 protected String getTargetContentEncoding(
226 @SuppressWarnings("UnusedParameters") String contentEncoding) throws Exception {
227 return IDENTITY;
228 }
229
230 @Override
231 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
232 cleanupSafely(ctx);
233 super.handlerRemoved(ctx);
234 }
235
236 @Override
237 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
238 cleanupSafely(ctx);
239 super.channelInactive(ctx);
240 }
241
242 @Override
243 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
244 this.ctx = ctx;
245 super.handlerAdded(ctx);
246 }
247
248 private void cleanup() {
249 if (decoder != null) {
250
251 decoder.finishAndReleaseAll();
252 decoder = null;
253 }
254 }
255
256 private void cleanupSafely(ChannelHandlerContext ctx) {
257 try {
258 cleanup();
259 } catch (Throwable cause) {
260
261
262 ctx.fireExceptionCaught(cause);
263 }
264 }
265
266 private void decode(ByteBuf in, List<Object> out) {
267
268 decoder.writeInbound(in.retain());
269 fetchDecoderOutput(out);
270 }
271
272 private void finishDecode(List<Object> out) {
273 if (decoder.finish()) {
274 fetchDecoderOutput(out);
275 }
276 decoder = null;
277 }
278
279 private void fetchDecoderOutput(List<Object> out) {
280 for (;;) {
281 ByteBuf buf = decoder.readInbound();
282 if (buf == null) {
283 break;
284 }
285 if (!buf.isReadable()) {
286 buf.release();
287 continue;
288 }
289 out.add(new DefaultHttpContent(buf));
290 }
291 }
292 }