1 /*
2 * Copyright 2010 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 java.util.Queue;
19
20 import org.jboss.netty.buffer.ChannelBuffer;
21 import org.jboss.netty.channel.Channel;
22 import org.jboss.netty.channel.ChannelDownstreamHandler;
23 import org.jboss.netty.channel.ChannelEvent;
24 import org.jboss.netty.channel.ChannelHandlerContext;
25 import org.jboss.netty.channel.ChannelUpstreamHandler;
26 import org.jboss.netty.util.internal.LinkedTransferQueue;
27
28 /**
29 * A combination of {@link HttpRequestEncoder} and {@link HttpResponseDecoder}
30 * which enables easier client side HTTP implementation. {@link HttpClientCodec}
31 * provides additional state management for <tt>HEAD</tt> and <tt>CONNECT</tt>
32 * requests, which {@link HttpResponseDecoder} lacks. Please refer to
33 * {@link HttpResponseDecoder} to learn what additional state management needs
34 * to be done for <tt>HEAD</tt> and <tt>CONNECT</tt> and why
35 * {@link HttpResponseDecoder} can not handle it by itself.
36 *
37 * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
38 * @author <a href="http://gleamynode.net/">Trustin Lee</a>
39 * @version $Rev: 2368 $, $Date: 2010-10-18 17:19:03 +0900 (Mon, 18 Oct 2010) $
40 *
41 * @see HttpServerCodec
42 *
43 * @apiviz.has org.jboss.netty.handler.codec.http.HttpResponseDecoder
44 * @apiviz.has org.jboss.netty.handler.codec.http.HttpRequestEncoder
45 */
46 public class HttpClientCodec implements ChannelUpstreamHandler,
47 ChannelDownstreamHandler {
48
49 /** A queue that is used for correlating a request and a response. */
50 final Queue<HttpMethod> queue = new LinkedTransferQueue<HttpMethod>();
51
52 /** If true, decoding stops (i.e. pass-through) */
53 volatile boolean done;
54
55 private final HttpRequestEncoder encoder = new Encoder();
56 private final HttpResponseDecoder decoder;
57
58 /**
59 * Creates a new instance with the default decoder options
60 * ({@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and
61 * {@code maxChunkSize (8192)}).
62 */
63 public HttpClientCodec() {
64 this(4096, 8192, 8192);
65 }
66
67 /**
68 * Creates a new instance with the specified decoder options.
69 */
70 public HttpClientCodec(
71 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
72 decoder = new Decoder(maxInitialLineLength, maxHeaderSize, maxChunkSize);
73 }
74
75 public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)
76 throws Exception {
77 decoder.handleUpstream(ctx, e);
78 }
79
80 public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)
81 throws Exception {
82 encoder.handleDownstream(ctx, e);
83 }
84
85 private final class Encoder extends HttpRequestEncoder {
86
87 Encoder() {
88 super();
89 }
90
91 @Override
92 protected Object encode(ChannelHandlerContext ctx, Channel channel,
93 Object msg) throws Exception {
94 if (msg instanceof HttpRequest && !done) {
95 queue.offer(((HttpRequest) msg).getMethod());
96 }
97 return super.encode(ctx, channel, msg);
98 }
99 }
100
101 private final class Decoder extends HttpResponseDecoder {
102
103 Decoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
104 super(maxInitialLineLength, maxHeaderSize, maxChunkSize);
105 }
106
107 @Override
108 protected Object decode(ChannelHandlerContext ctx, Channel channel,
109 ChannelBuffer buffer, State state) throws Exception {
110 if (done) {
111 return buffer.readBytes(actualReadableBytes());
112 } else {
113 return super.decode(ctx, channel, buffer, state);
114 }
115 }
116
117 @Override
118 protected boolean isContentAlwaysEmpty(HttpMessage msg) {
119 final int statusCode = ((HttpResponse) msg).getStatus().getCode();
120 if (statusCode == 100) {
121 // 100-continue response should be excluded from paired comparison.
122 return true;
123 }
124
125 // Get the method of the HTTP request that corresponds to the
126 // current response.
127 HttpMethod method = queue.poll();
128
129 char firstChar = method.getName().charAt(0);
130 switch (firstChar) {
131 case 'H':
132 // According to 4.3, RFC2616:
133 // All responses to the HEAD request method MUST NOT include a
134 // message-body, even though the presence of entity-header fields
135 // might lead one to believe they do.
136 if (HttpMethod.HEAD.equals(method)) {
137 return true;
138
139 // The following code was inserted to work around the servers
140 // that behave incorrectly. It has been commented out
141 // because it does not work with well behaving servers.
142 // Please note, even if the 'Transfer-Encoding: chunked'
143 // header exists in the HEAD response, the response should
144 // have absolutely no content.
145 //
146 //// Interesting edge case:
147 //// Some poorly implemented servers will send a zero-byte
148 //// chunk if Transfer-Encoding of the response is 'chunked'.
149 ////
150 //// return !msg.isChunked();
151 }
152 break;
153 case 'C':
154 // Successful CONNECT request results in a response with empty body.
155 if (statusCode == 200) {
156 if (HttpMethod.CONNECT.equals(method)) {
157 // Proxy connection established - Not HTTP anymore.
158 done = true;
159 queue.clear();
160 return true;
161 }
162 }
163 break;
164 }
165
166 return super.isContentAlwaysEmpty(msg);
167 }
168 }
169 }