View Javadoc

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.http2;
17  
18  import java.util.Queue;
19  import java.util.concurrent.atomic.AtomicLong;
20  
21  import org.jboss.netty.buffer.ChannelBuffer;
22  import org.jboss.netty.channel.Channel;
23  import org.jboss.netty.channel.ChannelDownstreamHandler;
24  import org.jboss.netty.channel.ChannelEvent;
25  import org.jboss.netty.channel.ChannelHandlerContext;
26  import org.jboss.netty.channel.ChannelStateEvent;
27  import org.jboss.netty.channel.ChannelUpstreamHandler;
28  import org.jboss.netty.handler.codec.PrematureChannelClosureException;
29  import org.jboss.netty.util.internal.QueueFactory;
30  
31  /**
32   * A combination of {@link HttpRequestEncoder} and {@link HttpResponseDecoder}
33   * which enables easier client side HTTP implementation. {@link HttpClientCodec}
34   * provides additional state management for <tt>HEAD</tt> and <tt>CONNECT</tt>
35   * requests, which {@link HttpResponseDecoder} lacks.  Please refer to
36   * {@link HttpResponseDecoder} to learn what additional state management needs
37   * to be done for <tt>HEAD</tt> and <tt>CONNECT</tt> and why
38   * {@link HttpResponseDecoder} can not handle it by itself.
39   *
40   * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
41   * @author <a href="http://gleamynode.net/">Trustin Lee</a>
42   * @version $Rev: 1108 $, $Date: 2012-04-21 12:51:25 +0200 (sam., 21 avr. 2012) $
43   *
44   * @see HttpServerCodec
45   *
46   * @apiviz.has org.jboss.netty.handler.codec.http2.HttpResponseDecoder
47   * @apiviz.has org.jboss.netty.handler.codec.http2.HttpRequestEncoder
48   */
49  public class HttpClientCodec implements ChannelUpstreamHandler,
50          ChannelDownstreamHandler {
51  
52      /** A queue that is used for correlating a request and a response. */
53      final Queue<HttpMethod> queue = QueueFactory.createQueue(HttpMethod.class);
54  
55      /** If true, decoding stops (i.e. pass-through) */
56      volatile boolean done;
57  
58      private final HttpRequestEncoder encoder = new Encoder();
59  
60      private final HttpResponseDecoder decoder;
61  
62      private final AtomicLong requestResponseCounter = new AtomicLong(0);
63  
64      private final boolean failOnMissingResponse;
65  
66      /**
67      * Creates a new instance with the default decoder options
68      * ({@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and
69      * {@code maxChunkSize (8192)}).
70      *
71      */
72      public HttpClientCodec() {
73          this(4096, 8192, 8192, false);
74      }
75  
76      /**
77      * Creates a new instance with the specified decoder options.
78      */
79      public HttpClientCodec(int maxInitialLineLength, int maxHeaderSize,
80              int maxChunkSize) {
81          this(maxInitialLineLength, maxHeaderSize, maxChunkSize, false);
82      }
83  
84      /**
85      * Creates a new instance with the specified decoder options.
86      */
87      public HttpClientCodec(int maxInitialLineLength, int maxHeaderSize,
88              int maxChunkSize, boolean failOnMissingResponse) {
89          decoder = new Decoder(maxInitialLineLength, maxHeaderSize, maxChunkSize);
90          this.failOnMissingResponse = failOnMissingResponse;
91      }
92  
93      @Override
94      public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)
95              throws Exception {
96          decoder.handleUpstream(ctx, e);
97      }
98  
99      @Override
100     public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)
101             throws Exception {
102         encoder.handleDownstream(ctx, e);
103     }
104 
105     private final class Encoder extends HttpRequestEncoder {
106 
107         Encoder() {
108         }
109 
110         @Override
111         protected Object encode(ChannelHandlerContext ctx, Channel channel,
112                 Object msg) throws Exception {
113             if (msg instanceof HttpRequest && !done) {
114                 queue.offer(((HttpRequest) msg).getMethod());
115             }
116 
117             Object obj = super.encode(ctx, channel, msg);
118 
119             if (failOnMissingResponse) {
120                 // check if the request is chunked if so do not increment
121                 if (msg instanceof HttpRequest &&
122                         !((HttpRequest) msg).isChunked()) {
123                     requestResponseCounter.incrementAndGet();
124                 } else if (msg instanceof HttpChunk &&
125                         ((HttpChunk) msg).isLast()) {
126                     // increment as its the last chunk
127                     requestResponseCounter.incrementAndGet();
128                 }
129             }
130 
131             return obj;
132 
133         }
134     }
135 
136     private final class Decoder extends HttpResponseDecoder {
137 
138         Decoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
139             super(maxInitialLineLength, maxHeaderSize, maxChunkSize);
140         }
141 
142         @Override
143         protected Object decode(ChannelHandlerContext ctx, Channel channel,
144                 ChannelBuffer buffer, State state) throws Exception {
145             if (done) {
146                 return buffer.readBytes(actualReadableBytes());
147             } else {
148                 Object msg = super.decode(ctx, channel, buffer, state);
149                 if (failOnMissingResponse) {
150                     decrement(msg);
151                 }
152                 return msg;
153             }
154         }
155 
156         private void decrement(Object msg) {
157             if (msg == null) {
158                 return;
159             }
160 
161             // check if its a HttpMessage and its not chunked
162             if (msg instanceof HttpMessage && !((HttpMessage) msg).isChunked()) {
163                 requestResponseCounter.decrementAndGet();
164             } else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) {
165                 requestResponseCounter.decrementAndGet();
166             } else if (msg instanceof Object[]) {
167                 // we just decrement it here as we only use this if the end of the chunk is reached
168                 // It would be more safe to check all the objects in the array but would also be slower
169                 requestResponseCounter.decrementAndGet();
170             }
171         }
172 
173         @Override
174         protected boolean isContentAlwaysEmpty(HttpMessage msg) {
175             final int statusCode = ((HttpResponse) msg).getStatus().getCode();
176             if (statusCode == 100) {
177                 // 100-continue response should be excluded from paired comparison.
178                 return true;
179             }
180 
181             // Get the method of the HTTP request that corresponds to the
182             // current response.
183             HttpMethod method = queue.poll();
184 
185             char firstChar = method.getName().charAt(0);
186             switch (firstChar) {
187             case 'H':
188                 // According to 4.3, RFC2616:
189                 // All responses to the HEAD request method MUST NOT include a
190                 // message-body, even though the presence of entity-header fields
191                 // might lead one to believe they do.
192                 if (HttpMethod.HEAD.equals(method)) {
193                     return true;
194 
195                     // The following code was inserted to work around the servers
196                     // that behave incorrectly. It has been commented out
197                     // because it does not work with well behaving servers.
198                     // Please note, even if the 'Transfer-Encoding: chunked'
199                     // header exists in the HEAD response, the response should
200                     // have absolutely no content.
201                     //
202                     //// Interesting edge case:
203                     //// Some poorly implemented servers will send a zero-byte
204                     //// chunk if Transfer-Encoding of the response is 'chunked'.
205                     ////
206                     //// return !msg.isChunked();
207                 }
208                 break;
209             case 'C':
210                 // Successful CONNECT request results in a response with empty body.
211                 if (statusCode == 200) {
212                     if (HttpMethod.CONNECT.equals(method)) {
213                         // Proxy connection established - Not HTTP anymore.
214                         done = true;
215                         queue.clear();
216                         return true;
217                     }
218                 }
219                 break;
220             }
221 
222             return super.isContentAlwaysEmpty(msg);
223         }
224 
225         @Override
226         public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
227                 throws Exception {
228             super.channelClosed(ctx, e);
229 
230             if (failOnMissingResponse) {
231                 long missingResponses = requestResponseCounter.get();
232                 if (missingResponses > 0) {
233                     throw new PrematureChannelClosureException(
234                             "Channel closed but still missing " +
235                                     missingResponses + " response(s)");
236                 }
237             }
238         }
239 
240     }
241 }