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