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 }