View Javadoc

1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the 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 static org.jboss.netty.buffer.ChannelBuffers.*;
19  import static org.jboss.netty.handler.codec.http.HttpConstants.*;
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.http.HttpHeaders.Names;
29  import org.jboss.netty.handler.codec.http.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   * @apiviz.landmark
46   */
47  public abstract class HttpMessageEncoder extends OneToOneEncoder {
48  
49      private static final byte[] CRLF = new byte[] { CR, LF };
50      private static final ChannelBuffer LAST_CHUNK =
51          copiedBuffer("0\r\n\r\n", CharsetUtil.US_ASCII);
52  
53      private volatile boolean transferEncodingChunked;
54  
55      /**
56       * Creates a new instance.
57       */
58      protected HttpMessageEncoder() {
59          super();
60      }
61  
62      @Override
63      protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
64          if (msg instanceof HttpMessage) {
65              HttpMessage m = (HttpMessage) msg;
66              boolean contentMustBeEmpty;
67              if (m.isChunked()) {
68                  // if Content-Length is set then the message can't be HTTP chunked
69                  if (HttpCodecUtil.isContentLengthSet(m)) {
70                      contentMustBeEmpty = false;
71                      transferEncodingChunked = false;
72                      HttpCodecUtil.removeTransferEncodingChunked(m);
73                  } else {
74                      // check if the Transfer-Encoding is set to chunked already.
75                      // if not add the header to the message
76                      if (!HttpCodecUtil.isTransferEncodingChunked(m)) {
77                          m.addHeader(Names.TRANSFER_ENCODING, Values.CHUNKED);
78                      }
79                      contentMustBeEmpty = true;
80                      transferEncodingChunked = true;
81                  }
82              } else {
83                  transferEncodingChunked = contentMustBeEmpty = HttpCodecUtil.isTransferEncodingChunked(m);
84              }
85  
86              ChannelBuffer header = ChannelBuffers.dynamicBuffer(
87                      channel.getConfig().getBufferFactory());
88              encodeInitialLine(header, m);
89              encodeHeaders(header, m);
90              header.writeByte(CR);
91              header.writeByte(LF);
92  
93              ChannelBuffer content = m.getContent();
94              if (!content.readable()) {
95                  return header; // no content
96              } else if (contentMustBeEmpty) {
97                  throw new IllegalArgumentException(
98                          "HttpMessage.content must be empty " +
99                          "if Transfer-Encoding is chunked.");
100             } else {
101                 return wrappedBuffer(header, content);
102             }
103         }
104 
105         if (msg instanceof HttpChunk) {
106             HttpChunk chunk = (HttpChunk) msg;
107             if (transferEncodingChunked) {
108                 if (chunk.isLast()) {
109                     transferEncodingChunked = false;
110                     if (chunk instanceof HttpChunkTrailer) {
111                         ChannelBuffer trailer = ChannelBuffers.dynamicBuffer(
112                                 channel.getConfig().getBufferFactory());
113                         trailer.writeByte((byte) '0');
114                         trailer.writeByte(CR);
115                         trailer.writeByte(LF);
116                         encodeTrailingHeaders(trailer, (HttpChunkTrailer) chunk);
117                         trailer.writeByte(CR);
118                         trailer.writeByte(LF);
119                         return trailer;
120                     } else {
121                         return LAST_CHUNK.duplicate();
122                     }
123                 } else {
124                     ChannelBuffer content = chunk.getContent();
125                     int contentLength = content.readableBytes();
126 
127                     return wrappedBuffer(
128                             copiedBuffer(
129                                     Integer.toHexString(contentLength),
130                                     CharsetUtil.US_ASCII),
131                             wrappedBuffer(CRLF),
132                             content.slice(content.readerIndex(), contentLength),
133                             wrappedBuffer(CRLF));
134                 }
135             } else {
136                 return chunk.getContent();
137             }
138 
139         }
140 
141         // Unknown message type.
142         return msg;
143     }
144 
145     private static 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 static 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 static 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 }