1
16 package io.netty.handler.codec.http;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.ByteBufUtil;
20 import io.netty.buffer.Unpooled;
21 import io.netty.channel.ChannelHandlerContext;
22 import io.netty.channel.FileRegion;
23 import io.netty.handler.codec.MessageToMessageEncoder;
24 import io.netty.util.CharsetUtil;
25 import io.netty.util.internal.StringUtil;
26
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map.Entry;
30
31 import static io.netty.buffer.Unpooled.directBuffer;
32 import static io.netty.buffer.Unpooled.unreleasableBuffer;
33 import static io.netty.handler.codec.http.HttpConstants.CR;
34 import static io.netty.handler.codec.http.HttpConstants.LF;
35
36
49 public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageToMessageEncoder<Object> {
50 static final int CRLF_SHORT = (CR << 8) | LF;
51 private static final int ZERO_CRLF_MEDIUM = ('0' << 16) | CRLF_SHORT;
52 private static final byte[] ZERO_CRLF_CRLF = { '0', CR, LF, CR, LF };
53 private static final ByteBuf CRLF_BUF = unreleasableBuffer(directBuffer(2).writeByte(CR).writeByte(LF));
54 private static final ByteBuf ZERO_CRLF_CRLF_BUF = unreleasableBuffer(directBuffer(ZERO_CRLF_CRLF.length)
55 .writeBytes(ZERO_CRLF_CRLF));
56 private static final float HEADERS_WEIGHT_NEW = 1 / 5f;
57 private static final float HEADERS_WEIGHT_HISTORICAL = 1 - HEADERS_WEIGHT_NEW;
58 private static final float TRAILERS_WEIGHT_NEW = HEADERS_WEIGHT_NEW;
59 private static final float TRAILERS_WEIGHT_HISTORICAL = HEADERS_WEIGHT_HISTORICAL;
60
61 private static final int ST_INIT = 0;
62 private static final int ST_CONTENT_NON_CHUNK = 1;
63 private static final int ST_CONTENT_CHUNK = 2;
64 private static final int ST_CONTENT_ALWAYS_EMPTY = 3;
65
66 @SuppressWarnings("RedundantFieldInitialization")
67 private int state = ST_INIT;
68
69
73 private float headersEncodedSizeAccumulator = 256;
74
75
79 private float trailersEncodedSizeAccumulator = 256;
80
81 @Override
82 protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
83 ByteBuf buf = null;
84 if (msg instanceof HttpMessage) {
85 if (state != ST_INIT) {
86 throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg)
87 + ", state: " + state);
88 }
89
90 @SuppressWarnings({ "unchecked", "CastConflictsWithInstanceof" })
91 H m = (H) msg;
92
93 buf = ctx.alloc().buffer((int) headersEncodedSizeAccumulator);
94
95 encodeInitialLine(buf, m);
96 state = isContentAlwaysEmpty(m) ? ST_CONTENT_ALWAYS_EMPTY :
97 HttpUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
98
99 sanitizeHeadersBeforeEncode(m, state == ST_CONTENT_ALWAYS_EMPTY);
100
101 encodeHeaders(m.headers(), buf);
102 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
103
104 headersEncodedSizeAccumulator = HEADERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
105 HEADERS_WEIGHT_HISTORICAL * headersEncodedSizeAccumulator;
106 }
107
108
109
110
111
112
113 if (msg instanceof ByteBuf) {
114 final ByteBuf potentialEmptyBuf = (ByteBuf) msg;
115 if (!potentialEmptyBuf.isReadable()) {
116 out.add(potentialEmptyBuf.retain());
117 return;
118 }
119 }
120
121 if (msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion) {
122 switch (state) {
123 case ST_INIT:
124 throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
125 case ST_CONTENT_NON_CHUNK:
126 final long contentLength = contentLength(msg);
127 if (contentLength > 0) {
128 if (buf != null && buf.writableBytes() >= contentLength && msg instanceof HttpContent) {
129
130 buf.writeBytes(((HttpContent) msg).content());
131 out.add(buf);
132 } else {
133 if (buf != null) {
134 out.add(buf);
135 }
136 out.add(encodeAndRetain(msg));
137 }
138
139 if (msg instanceof LastHttpContent) {
140 state = ST_INIT;
141 }
142
143 break;
144 }
145
146
147 case ST_CONTENT_ALWAYS_EMPTY:
148
149 if (buf != null) {
150
151 out.add(buf);
152 } else {
153
154
155
156
157
158
159
160 out.add(Unpooled.EMPTY_BUFFER);
161 }
162
163 break;
164 case ST_CONTENT_CHUNK:
165 if (buf != null) {
166
167 out.add(buf);
168 }
169 encodeChunkedContent(ctx, msg, contentLength(msg), out);
170
171 break;
172 default:
173 throw new Error();
174 }
175
176 if (msg instanceof LastHttpContent) {
177 state = ST_INIT;
178 }
179 } else if (buf != null) {
180 out.add(buf);
181 }
182 }
183
184
187 protected void encodeHeaders(HttpHeaders headers, ByteBuf buf) {
188 Iterator<Entry<CharSequence, CharSequence>> iter = headers.iteratorCharSequence();
189 while (iter.hasNext()) {
190 Entry<CharSequence, CharSequence> header = iter.next();
191 HttpHeadersEncoder.encoderHeader(header.getKey(), header.getValue(), buf);
192 }
193 }
194
195 private void encodeChunkedContent(ChannelHandlerContext ctx, Object msg, long contentLength, List<Object> out) {
196 if (contentLength > 0) {
197 String lengthHex = Long.toHexString(contentLength);
198 ByteBuf buf = ctx.alloc().buffer(lengthHex.length() + 2);
199 buf.writeCharSequence(lengthHex, CharsetUtil.US_ASCII);
200 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
201 out.add(buf);
202 out.add(encodeAndRetain(msg));
203 out.add(CRLF_BUF.duplicate());
204 }
205
206 if (msg instanceof LastHttpContent) {
207 HttpHeaders headers = ((LastHttpContent) msg).trailingHeaders();
208 if (headers.isEmpty()) {
209 out.add(ZERO_CRLF_CRLF_BUF.duplicate());
210 } else {
211 ByteBuf buf = ctx.alloc().buffer((int) trailersEncodedSizeAccumulator);
212 ByteBufUtil.writeMediumBE(buf, ZERO_CRLF_MEDIUM);
213 encodeHeaders(headers, buf);
214 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
215 trailersEncodedSizeAccumulator = TRAILERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
216 TRAILERS_WEIGHT_HISTORICAL * trailersEncodedSizeAccumulator;
217 out.add(buf);
218 }
219 } else if (contentLength == 0) {
220
221
222 out.add(encodeAndRetain(msg));
223 }
224 }
225
226
229 protected void sanitizeHeadersBeforeEncode(@SuppressWarnings("unused") H msg, boolean isAlwaysEmpty) {
230
231 }
232
233
240 protected boolean isContentAlwaysEmpty(@SuppressWarnings("unused") H msg) {
241 return false;
242 }
243
244 @Override
245 public boolean acceptOutboundMessage(Object msg) throws Exception {
246 return msg instanceof HttpObject || msg instanceof ByteBuf || msg instanceof FileRegion;
247 }
248
249 private static Object encodeAndRetain(Object msg) {
250 if (msg instanceof ByteBuf) {
251 return ((ByteBuf) msg).retain();
252 }
253 if (msg instanceof HttpContent) {
254 return ((HttpContent) msg).content().retain();
255 }
256 if (msg instanceof FileRegion) {
257 return ((FileRegion) msg).retain();
258 }
259 throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
260 }
261
262 private static long contentLength(Object msg) {
263 if (msg instanceof HttpContent) {
264 return ((HttpContent) msg).content().readableBytes();
265 }
266 if (msg instanceof ByteBuf) {
267 return ((ByteBuf) msg).readableBytes();
268 }
269 if (msg instanceof FileRegion) {
270 return ((FileRegion) msg).count();
271 }
272 throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
273 }
274
275
281 private static int padSizeForAccumulation(int readableBytes) {
282 return (readableBytes << 2) / 3;
283 }
284
285 @Deprecated
286 protected static void encodeAscii(String s, ByteBuf buf) {
287 buf.writeCharSequence(s, CharsetUtil.US_ASCII);
288 }
289
290 protected abstract void encodeInitialLine(ByteBuf buf, H message) throws Exception;
291 }
292