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 }