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.headers().add(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         // Unknown message type.
139         return msg;
140     }
141 
142     private static void encodeHeaders(ChannelBuffer buf, HttpMessage message) {
143         try {
144             for (Map.Entry<String, String> h: message.headers()) {
145                 encodeHeader(buf, h.getKey(), h.getValue());
146             }
147         } catch (UnsupportedEncodingException e) {
148             throw (Error) new Error().initCause(e);
149         }
150     }
151 
152     private static void encodeTrailingHeaders(ChannelBuffer buf, HttpChunkTrailer trailer) {
153         try {
154             for (Map.Entry<String, String> h: trailer.trailingHeaders()) {
155                 encodeHeader(buf, h.getKey(), h.getValue());
156             }
157         } catch (UnsupportedEncodingException e) {
158             throw (Error) new Error().initCause(e);
159         }
160     }
161 
162     private static void encodeHeader(ChannelBuffer buf, String header, String value)
163             throws UnsupportedEncodingException {
164         encodeAscii(header, buf);
165         buf.writeByte(COLON);
166         buf.writeByte(SP);
167         encodeAscii(value, buf);
168         buf.writeByte(CR);
169         buf.writeByte(LF);
170     }
171 
172     protected static void encodeAscii(String s, ChannelBuffer buf) {
173         for (int i = 0; i < s.length(); i++) {
174             buf.writeByte(c2b(s.charAt(i)));
175         }
176     }
177 
178     private static byte c2b(char c) {
179         if (c > 255) {
180             return '?';
181         }
182         return (byte) c;
183     }
184 
185     protected abstract void encodeInitialLine(ChannelBuffer buf, HttpMessage message) throws Exception;
186 }