1
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 import io.netty.util.ReferenceCountUtil;
25
26 import java.util.ArrayDeque;
27 import java.util.List;
28 import java.util.Queue;
29 import java.util.concurrent.atomic.AtomicLong;
30
31 import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS;
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
51 public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResponseDecoder, HttpRequestEncoder>
52 implements HttpClientUpgradeHandler.SourceCodec {
53 public static final boolean DEFAULT_FAIL_ON_MISSING_RESPONSE = false;
54 public static final boolean DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST = false;
55
56
57 private final Queue<HttpMethod> queue = new ArrayDeque<HttpMethod>();
58 private final boolean parseHttpAfterConnectRequest;
59
60
61 private boolean done;
62
63 private final AtomicLong requestResponseCounter = new AtomicLong();
64 private final boolean failOnMissingResponse;
65
66
71 public HttpClientCodec() {
72 this(DEFAULT_MAX_INITIAL_LINE_LENGTH, DEFAULT_MAX_HEADER_SIZE, DEFAULT_MAX_CHUNK_SIZE,
73 DEFAULT_FAIL_ON_MISSING_RESPONSE);
74 }
75
76
79 public HttpClientCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
80 this(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_FAIL_ON_MISSING_RESPONSE);
81 }
82
83
86 public HttpClientCodec(
87 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse) {
88 this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, DEFAULT_VALIDATE_HEADERS);
89 }
90
91
94 public HttpClientCodec(
95 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
96 boolean validateHeaders) {
97 this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, validateHeaders,
98 DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST);
99 }
100
101
104 public HttpClientCodec(
105 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
106 boolean validateHeaders, boolean parseHttpAfterConnectRequest) {
107 init(new Decoder(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders), new Encoder());
108 this.failOnMissingResponse = failOnMissingResponse;
109 this.parseHttpAfterConnectRequest = parseHttpAfterConnectRequest;
110 }
111
112
115 public HttpClientCodec(
116 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
117 boolean validateHeaders, int initialBufferSize) {
118 this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, validateHeaders,
119 initialBufferSize, DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST);
120 }
121
122
125 public HttpClientCodec(
126 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
127 boolean validateHeaders, int initialBufferSize, boolean parseHttpAfterConnectRequest) {
128 this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, validateHeaders,
129 initialBufferSize, parseHttpAfterConnectRequest, DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS);
130 }
131
132
135 public HttpClientCodec(
136 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse,
137 boolean validateHeaders, int initialBufferSize, boolean parseHttpAfterConnectRequest,
138 boolean allowDuplicateContentLengths) {
139 init(new Decoder(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize,
140 allowDuplicateContentLengths),
141 new Encoder());
142 this.parseHttpAfterConnectRequest = parseHttpAfterConnectRequest;
143 this.failOnMissingResponse = failOnMissingResponse;
144 }
145
146
149 @Override
150 public void prepareUpgradeFrom(ChannelHandlerContext ctx) {
151 ((Encoder) outboundHandler()).upgraded = true;
152 }
153
154
158 @Override
159 public void upgradeFrom(ChannelHandlerContext ctx) {
160 final ChannelPipeline p = ctx.pipeline();
161 p.remove(this);
162 }
163
164 public void setSingleDecode(boolean singleDecode) {
165 inboundHandler().setSingleDecode(singleDecode);
166 }
167
168 public boolean isSingleDecode() {
169 return inboundHandler().isSingleDecode();
170 }
171
172 private final class Encoder extends HttpRequestEncoder {
173
174 boolean upgraded;
175
176 @Override
177 protected void encode(
178 ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
179
180 if (upgraded) {
181 out.add(ReferenceCountUtil.retain(msg));
182 return;
183 }
184
185 if (msg instanceof HttpRequest) {
186 queue.offer(((HttpRequest) msg).method());
187 }
188
189 super.encode(ctx, msg, out);
190
191 if (failOnMissingResponse && !done) {
192
193 if (msg instanceof LastHttpContent) {
194
195 requestResponseCounter.incrementAndGet();
196 }
197 }
198 }
199 }
200
201 private final class Decoder extends HttpResponseDecoder {
202 Decoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders) {
203 super(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders);
204 }
205
206 Decoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
207 int initialBufferSize, boolean allowDuplicateContentLengths) {
208 super(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize,
209 allowDuplicateContentLengths);
210 }
211
212 @Override
213 protected void decode(
214 ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
215 if (done) {
216 int readable = actualReadableBytes();
217 if (readable == 0) {
218
219
220 return;
221 }
222 out.add(buffer.readBytes(readable));
223 } else {
224 int oldSize = out.size();
225 super.decode(ctx, buffer, out);
226 if (failOnMissingResponse) {
227 int size = out.size();
228 for (int i = oldSize; i < size; i++) {
229 decrement(out.get(i));
230 }
231 }
232 }
233 }
234
235 private void decrement(Object msg) {
236 if (msg == null) {
237 return;
238 }
239
240
241 if (msg instanceof LastHttpContent) {
242 requestResponseCounter.decrementAndGet();
243 }
244 }
245
246 @Override
247 protected boolean isContentAlwaysEmpty(HttpMessage msg) {
248
249
250
251
252
253 HttpMethod method = queue.poll();
254
255 final int statusCode = ((HttpResponse) msg).status().code();
256 if (statusCode >= 100 && statusCode < 200) {
257
258
259 return super.isContentAlwaysEmpty(msg);
260 }
261
262
263
264 if (method != null) {
265 char firstChar = method.name().charAt(0);
266 switch (firstChar) {
267 case 'H':
268
269
270
271
272 if (HttpMethod.HEAD.equals(method)) {
273 return true;
274
275
276
277
278
279
280
281
282
283
284
285
286
287 }
288 break;
289 case 'C':
290
291 if (statusCode == 200) {
292 if (HttpMethod.CONNECT.equals(method)) {
293
294
295 if (!parseHttpAfterConnectRequest) {
296 done = true;
297 queue.clear();
298 }
299 return true;
300 }
301 }
302 break;
303 }
304 }
305 return super.isContentAlwaysEmpty(msg);
306 }
307
308 @Override
309 public void channelInactive(ChannelHandlerContext ctx)
310 throws Exception {
311 super.channelInactive(ctx);
312
313 if (failOnMissingResponse) {
314 long missingResponses = requestResponseCounter.get();
315 if (missingResponses > 0) {
316 ctx.fireExceptionCaught(new PrematureChannelClosureException(
317 "channel gone inactive with " + missingResponses +
318 " missing response(s)"));
319 }
320 }
321 }
322 }
323 }
324