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