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.Channel;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.ChannelPipeline;
22 import io.netty.channel.CombinedChannelDuplexHandler;
23 import io.netty.handler.codec.PrematureChannelClosureException;
24
25 import java.util.ArrayDeque;
26 import java.util.List;
27 import java.util.Queue;
28 import java.util.concurrent.atomic.AtomicLong;
29
30 import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS;
31 import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_ALLOW_PARTIAL_CHUNKS;
32 import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_CHUNK_SIZE;
33 import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_HEADER_SIZE;
34 import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_INITIAL_LINE_LENGTH;
35 import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_VALIDATE_HEADERS;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResponseDecoder, HttpRequestEncoder>
64 implements HttpClientUpgradeHandler.SourceCodec {
65 public static final boolean DEFAULT_FAIL_ON_MISSING_RESPONSE = false;
66 public static final boolean DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST = false;
67
68
69 private final Queue<HttpMethod> queue = new ArrayDeque<HttpMethod>();
70 private final boolean parseHttpAfterConnectRequest;
71
72
73 private boolean done;
74
75 private final AtomicLong requestResponseCounter = new AtomicLong();
76 private final boolean failOnMissingResponse;
77
78
79
80
81
82
83 public HttpClientCodec() {
84 this(new HttpDecoderConfig(),
85 DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST,
86 DEFAULT_FAIL_ON_MISSING_RESPONSE);
87 }
88
89
90
91
92 public HttpClientCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
93 this(new HttpDecoderConfig()
94 .setMaxInitialLineLength(maxInitialLineLength)
95 .setMaxHeaderSize(maxHeaderSize)
96 .setMaxChunkSize(maxChunkSize),
97 DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST,
98 DEFAULT_FAIL_ON_MISSING_RESPONSE);
99 }
100
101
102
103
104 public HttpClientCodec(
105 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse) {
106 this(new HttpDecoderConfig()
107 .setMaxInitialLineLength(maxInitialLineLength)
108 .setMaxHeaderSize(maxHeaderSize)
109 .setMaxChunkSize(maxChunkSize),
110 DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST,
111 failOnMissingResponse);
112 }
113
114
115
116
117
118
119
120 @Deprecated
121 public HttpClientCodec(
122 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
123 boolean validateHeaders) {
124 this(new HttpDecoderConfig()
125 .setMaxInitialLineLength(maxInitialLineLength)
126 .setMaxHeaderSize(maxHeaderSize)
127 .setMaxChunkSize(maxChunkSize)
128 .setValidateHeaders(validateHeaders),
129 DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST,
130 failOnMissingResponse);
131 }
132
133
134
135
136
137
138
139 @Deprecated
140 public HttpClientCodec(
141 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
142 boolean validateHeaders, boolean parseHttpAfterConnectRequest) {
143 this(new HttpDecoderConfig()
144 .setMaxInitialLineLength(maxInitialLineLength)
145 .setMaxHeaderSize(maxHeaderSize)
146 .setMaxChunkSize(maxChunkSize)
147 .setValidateHeaders(validateHeaders),
148 parseHttpAfterConnectRequest,
149 failOnMissingResponse);
150 }
151
152
153
154
155
156
157
158 @Deprecated
159 public HttpClientCodec(
160 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
161 boolean validateHeaders, int initialBufferSize) {
162 this(new HttpDecoderConfig()
163 .setMaxInitialLineLength(maxInitialLineLength)
164 .setMaxHeaderSize(maxHeaderSize)
165 .setMaxChunkSize(maxChunkSize)
166 .setValidateHeaders(validateHeaders)
167 .setInitialBufferSize(initialBufferSize),
168 DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST,
169 failOnMissingResponse);
170 }
171
172
173
174
175
176
177
178 @Deprecated
179 public HttpClientCodec(
180 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
181 boolean validateHeaders, int initialBufferSize, boolean parseHttpAfterConnectRequest) {
182 this(new HttpDecoderConfig()
183 .setMaxInitialLineLength(maxInitialLineLength)
184 .setMaxHeaderSize(maxHeaderSize)
185 .setMaxChunkSize(maxChunkSize)
186 .setValidateHeaders(validateHeaders)
187 .setInitialBufferSize(initialBufferSize),
188 parseHttpAfterConnectRequest,
189 failOnMissingResponse);
190 }
191
192
193
194
195
196
197 @Deprecated
198 public HttpClientCodec(
199 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
200 boolean validateHeaders, int initialBufferSize, boolean parseHttpAfterConnectRequest,
201 boolean allowDuplicateContentLengths) {
202 this(new HttpDecoderConfig()
203 .setMaxInitialLineLength(maxInitialLineLength)
204 .setMaxHeaderSize(maxHeaderSize)
205 .setMaxChunkSize(maxChunkSize)
206 .setValidateHeaders(validateHeaders)
207 .setInitialBufferSize(initialBufferSize)
208 .setAllowDuplicateContentLengths(allowDuplicateContentLengths),
209 parseHttpAfterConnectRequest,
210 failOnMissingResponse);
211 }
212
213
214
215
216
217
218
219 @Deprecated
220 public HttpClientCodec(
221 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
222 boolean validateHeaders, int initialBufferSize, boolean parseHttpAfterConnectRequest,
223 boolean allowDuplicateContentLengths, boolean allowPartialChunks) {
224 this(new HttpDecoderConfig()
225 .setMaxInitialLineLength(maxInitialLineLength)
226 .setMaxHeaderSize(maxHeaderSize)
227 .setMaxChunkSize(maxChunkSize)
228 .setValidateHeaders(validateHeaders)
229 .setInitialBufferSize(initialBufferSize)
230 .setAllowDuplicateContentLengths(allowDuplicateContentLengths)
231 .setAllowPartialChunks(allowPartialChunks),
232 parseHttpAfterConnectRequest,
233 failOnMissingResponse);
234 }
235
236
237
238
239 public HttpClientCodec(
240 HttpDecoderConfig config, boolean parseHttpAfterConnectRequest, boolean failOnMissingResponse) {
241 init(new Decoder(config), new Encoder());
242 this.parseHttpAfterConnectRequest = parseHttpAfterConnectRequest;
243 this.failOnMissingResponse = failOnMissingResponse;
244 }
245
246
247
248
249 @Override
250 public void prepareUpgradeFrom(ChannelHandlerContext ctx) {
251 ((Encoder) outboundHandler()).upgraded = true;
252 }
253
254
255
256
257
258 @Override
259 public void upgradeFrom(ChannelHandlerContext ctx) {
260 final ChannelPipeline p = ctx.pipeline();
261 p.remove(this);
262 }
263
264 public void setSingleDecode(boolean singleDecode) {
265 inboundHandler().setSingleDecode(singleDecode);
266 }
267
268 public boolean isSingleDecode() {
269 return inboundHandler().isSingleDecode();
270 }
271
272 private final class Encoder extends HttpRequestEncoder {
273
274 boolean upgraded;
275
276 @Override
277 protected void encode(
278 ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
279
280 if (upgraded) {
281
282 out.add(msg);
283 return;
284 }
285
286 if (msg instanceof HttpRequest) {
287 queue.offer(((HttpRequest) msg).method());
288 }
289
290 super.encode(ctx, msg, out);
291
292 if (failOnMissingResponse && !done) {
293
294 if (msg instanceof LastHttpContent) {
295
296 requestResponseCounter.incrementAndGet();
297 }
298 }
299 }
300 }
301
302 private final class Decoder extends HttpResponseDecoder {
303 Decoder(HttpDecoderConfig config) {
304 super(config);
305 }
306
307 @Override
308 protected void decode(
309 ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
310 if (done) {
311 int readable = actualReadableBytes();
312 if (readable == 0) {
313
314
315 return;
316 }
317 out.add(buffer.readBytes(readable));
318 } else {
319 int oldSize = out.size();
320 super.decode(ctx, buffer, out);
321 if (failOnMissingResponse) {
322 int size = out.size();
323 for (int i = oldSize; i < size; i++) {
324 decrement(out.get(i));
325 }
326 }
327 }
328 }
329
330 private void decrement(Object msg) {
331 if (msg == null) {
332 return;
333 }
334
335
336 if (msg instanceof LastHttpContent) {
337 requestResponseCounter.decrementAndGet();
338 }
339 }
340
341 @Override
342 protected boolean isContentAlwaysEmpty(HttpMessage msg) {
343
344
345
346
347
348 HttpMethod method = queue.poll();
349
350 final HttpResponseStatus status = ((HttpResponse) msg).status();
351 final HttpStatusClass statusClass = status.codeClass();
352 final int statusCode = status.code();
353 if (statusClass == HttpStatusClass.INFORMATIONAL) {
354
355
356 return super.isContentAlwaysEmpty(msg);
357 }
358
359
360
361 if (method != null) {
362 char firstChar = method.name().charAt(0);
363 switch (firstChar) {
364 case 'H':
365
366
367
368
369 if (HttpMethod.HEAD.equals(method)) {
370 return true;
371
372
373
374
375
376
377
378
379
380
381
382
383
384 }
385 break;
386 case 'C':
387
388 if (statusCode == 200) {
389 if (HttpMethod.CONNECT.equals(method)) {
390
391
392 if (!parseHttpAfterConnectRequest) {
393 done = true;
394 queue.clear();
395 }
396 return true;
397 }
398 }
399 break;
400 default:
401 break;
402 }
403 }
404 return super.isContentAlwaysEmpty(msg);
405 }
406
407 @Override
408 public void channelInactive(ChannelHandlerContext ctx)
409 throws Exception {
410 super.channelInactive(ctx);
411
412 if (failOnMissingResponse) {
413 long missingResponses = requestResponseCounter.get();
414 if (missingResponses > 0) {
415 ctx.fireExceptionCaught(new PrematureChannelClosureException(
416 "channel gone inactive with " + missingResponses +
417 " missing response(s)"));
418 }
419 }
420 }
421 }
422 }