1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
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  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
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  
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                  
67                  if (HttpCodecUtil.isContentLengthSet(m)) {
68                      contentMustBeEmpty = false;
69                      transferEncodingChunked = false;
70                      HttpCodecUtil.removeTransferEncodingChunked(m);
71                  } else {
72                      
73                      
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; 
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         
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 }