View Javadoc

1   /*
2    * Copyright 2009 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 static org.jboss.netty.buffer.ChannelBuffers.*;
19  import static org.jboss.netty.handler.codec.http2.HttpCodecUtil.*;
20  
21  import java.io.UnsupportedEncodingException;
22  import java.util.Map;
23  
24  import org.jboss.netty.buffer.ChannelBuffer;
25  import org.jboss.netty.buffer.ChannelBuffers;
26  import org.jboss.netty.channel.Channel;
27  import org.jboss.netty.channel.ChannelHandlerContext;
28  import org.jboss.netty.handler.codec.http2.HttpHeaders.Names;
29  import org.jboss.netty.handler.codec.http2.HttpHeaders.Values;
30  import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
31  import org.jboss.netty.util.CharsetUtil;
32  
33  /**
34   * Encodes an {@link HttpMessage} or an {@link HttpChunk} into
35   * a {@link ChannelBuffer}.
36   *
37   * <h3>Extensibility</h3>
38   *
39   * Please note that this encoder is designed to be extended to implement
40   * a protocol derived from HTTP, such as
41   * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
42   * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
43   * To implement the encoder of such a derived protocol, extend this class and
44   * implement all abstract methods properly.
45   *
46   * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
47   * @author Andy Taylor (andy.taylor@jboss.org)
48   * @author <a href="http://gleamynode.net/">Trustin Lee</a>
49   * @version $Rev: 1107 $, $Date: 2012-04-15 19:00:57 +0200 (dim., 15 avr. 2012) $
50   *
51   * @apiviz.landmark
52   */
53  public abstract class HttpMessageEncoder extends OneToOneEncoder {
54  
55      private static final ChannelBuffer LAST_CHUNK =
56          copiedBuffer("0\r\n\r\n", CharsetUtil.US_ASCII);
57  
58      private volatile boolean chunked;
59  
60      /**
61       * Creates a new instance.
62       */
63      protected HttpMessageEncoder() {
64          super();
65      }
66  
67      @Override
68      protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
69          if (msg instanceof HttpMessage) {
70              HttpMessage m = (HttpMessage) msg;
71              boolean chunked;
72              if (m.isChunked()) {
73                  // check if the Transfer-Encoding is set to chunked already.
74                  // if not add the header to the message
75                  if (!HttpCodecUtil.isTransferEncodingChunked(m)) {
76                      m.addHeader(Names.TRANSFER_ENCODING, Values.CHUNKED);
77                  }
78                  chunked = this.chunked = true;
79              } else {
80                  chunked = this.chunked = HttpCodecUtil.isTransferEncodingChunked(m);
81              }
82              ChannelBuffer header = ChannelBuffers.dynamicBuffer(
83                      channel.getConfig().getBufferFactory());
84              encodeInitialLine(header, m);
85              encodeHeaders(header, m);
86              header.writeByte(CR);
87              header.writeByte(LF);
88  
89              ChannelBuffer content = m.getContent();
90              if (!content.readable()) {
91                  return header; // no content
92              } else if (chunked) {
93                  throw new IllegalArgumentException(
94                          "HttpMessage.content must be empty " +
95                          "if Transfer-Encoding is chunked.");
96              } else {
97                  return wrappedBuffer(header, content);
98              }
99          }
100 
101         if (msg instanceof HttpChunk) {
102             HttpChunk chunk = (HttpChunk) msg;
103             if (chunked) {
104                 if (chunk.isLast()) {
105                     chunked = false;
106                     if (chunk instanceof HttpChunkTrailer) {
107                         ChannelBuffer trailer = ChannelBuffers.dynamicBuffer(
108                                 channel.getConfig().getBufferFactory());
109                         trailer.writeByte((byte) '0');
110                         trailer.writeByte(CR);
111                         trailer.writeByte(LF);
112                         encodeTrailingHeaders(trailer, (HttpChunkTrailer) chunk);
113                         trailer.writeByte(CR);
114                         trailer.writeByte(LF);
115                         return trailer;
116                     } else {
117                         return LAST_CHUNK.duplicate();
118                     }
119                 } else {
120                     ChannelBuffer content = chunk.getContent();
121                     int contentLength = content.readableBytes();
122 
123                     return wrappedBuffer(
124                             copiedBuffer(
125                                     Integer.toHexString(contentLength),
126                                     CharsetUtil.US_ASCII),
127                             wrappedBuffer(CRLF),
128                             content.slice(content.readerIndex(), contentLength),
129                             wrappedBuffer(CRLF));
130                 }
131             } else {
132                 if (chunk.isLast()) {
133                     return null;
134                 } else {
135                     return chunk.getContent();
136                 }
137             }
138 
139         }
140 
141         // Unknown message type.
142         return msg;
143     }
144 
145     private void encodeHeaders(ChannelBuffer buf, HttpMessage message) {
146         try {
147             for (Map.Entry<String, String> h: message.getHeaders()) {
148                 encodeHeader(buf, h.getKey(), h.getValue());
149             }
150         } catch (UnsupportedEncodingException e) {
151             throw (Error) new Error().initCause(e);
152         }
153     }
154 
155     private void encodeTrailingHeaders(ChannelBuffer buf, HttpChunkTrailer trailer) {
156         try {
157             for (Map.Entry<String, String> h: trailer.getHeaders()) {
158                 encodeHeader(buf, h.getKey(), h.getValue());
159             }
160         } catch (UnsupportedEncodingException e) {
161             throw (Error) new Error().initCause(e);
162         }
163     }
164 
165     private void encodeHeader(ChannelBuffer buf, String header, String value)
166             throws UnsupportedEncodingException {
167         buf.writeBytes(header.getBytes("ASCII"));
168         buf.writeByte(COLON);
169         buf.writeByte(SP);
170         buf.writeBytes(value.getBytes("ASCII"));
171         buf.writeByte(CR);
172         buf.writeByte(LF);
173     }
174 
175     protected abstract void encodeInitialLine(ChannelBuffer buf, HttpMessage message) throws Exception;
176 }