1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
37
38
39
40
41
42
43
44
45
46
47
48
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
70
71
72
73 private float headersEncodedSizeAccumulator = 256;
74
75
76
77
78
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 }
88
89 @SuppressWarnings({ "unchecked", "CastConflictsWithInstanceof" })
90 H m = (H) msg;
91
92 buf = ctx.alloc().buffer((int) headersEncodedSizeAccumulator);
93
94 encodeInitialLine(buf, m);
95 state = isContentAlwaysEmpty(m) ? ST_CONTENT_ALWAYS_EMPTY :
96 HttpUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
97
98 sanitizeHeadersBeforeEncode(m, state == ST_CONTENT_ALWAYS_EMPTY);
99
100 encodeHeaders(m.headers(), buf);
101 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
102
103 headersEncodedSizeAccumulator = HEADERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
104 HEADERS_WEIGHT_HISTORICAL * headersEncodedSizeAccumulator;
105 }
106
107
108
109
110
111
112 if (msg instanceof ByteBuf) {
113 final ByteBuf potentialEmptyBuf = (ByteBuf) msg;
114 if (!potentialEmptyBuf.isReadable()) {
115 out.add(potentialEmptyBuf.retain());
116 return;
117 }
118 }
119
120 if (msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion) {
121 switch (state) {
122 case ST_INIT:
123 throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
124 case ST_CONTENT_NON_CHUNK:
125 final long contentLength = contentLength(msg);
126 if (contentLength > 0) {
127 if (buf != null && buf.writableBytes() >= contentLength && msg instanceof HttpContent) {
128
129 buf.writeBytes(((HttpContent) msg).content());
130 out.add(buf);
131 } else {
132 if (buf != null) {
133 out.add(buf);
134 }
135 out.add(encodeAndRetain(msg));
136 }
137
138 if (msg instanceof LastHttpContent) {
139 state = ST_INIT;
140 }
141
142 break;
143 }
144
145
146 case ST_CONTENT_ALWAYS_EMPTY:
147
148 if (buf != null) {
149
150 out.add(buf);
151 } else {
152
153
154
155
156
157
158
159 out.add(Unpooled.EMPTY_BUFFER);
160 }
161
162 break;
163 case ST_CONTENT_CHUNK:
164 if (buf != null) {
165
166 out.add(buf);
167 }
168 encodeChunkedContent(ctx, msg, contentLength(msg), out);
169
170 break;
171 default:
172 throw new Error();
173 }
174
175 if (msg instanceof LastHttpContent) {
176 state = ST_INIT;
177 }
178 } else if (buf != null) {
179 out.add(buf);
180 }
181 }
182
183
184
185
186 protected void encodeHeaders(HttpHeaders headers, ByteBuf buf) {
187 Iterator<Entry<CharSequence, CharSequence>> iter = headers.iteratorCharSequence();
188 while (iter.hasNext()) {
189 Entry<CharSequence, CharSequence> header = iter.next();
190 HttpHeadersEncoder.encoderHeader(header.getKey(), header.getValue(), buf);
191 }
192 }
193
194 private void encodeChunkedContent(ChannelHandlerContext ctx, Object msg, long contentLength, List<Object> out) {
195 if (contentLength > 0) {
196 String lengthHex = Long.toHexString(contentLength);
197 ByteBuf buf = ctx.alloc().buffer(lengthHex.length() + 2);
198 buf.writeCharSequence(lengthHex, CharsetUtil.US_ASCII);
199 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
200 out.add(buf);
201 out.add(encodeAndRetain(msg));
202 out.add(CRLF_BUF.duplicate());
203 }
204
205 if (msg instanceof LastHttpContent) {
206 HttpHeaders headers = ((LastHttpContent) msg).trailingHeaders();
207 if (headers.isEmpty()) {
208 out.add(ZERO_CRLF_CRLF_BUF.duplicate());
209 } else {
210 ByteBuf buf = ctx.alloc().buffer((int) trailersEncodedSizeAccumulator);
211 ByteBufUtil.writeMediumBE(buf, ZERO_CRLF_MEDIUM);
212 encodeHeaders(headers, buf);
213 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
214 trailersEncodedSizeAccumulator = TRAILERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
215 TRAILERS_WEIGHT_HISTORICAL * trailersEncodedSizeAccumulator;
216 out.add(buf);
217 }
218 } else if (contentLength == 0) {
219
220
221 out.add(encodeAndRetain(msg));
222 }
223 }
224
225
226
227
228 protected void sanitizeHeadersBeforeEncode(@SuppressWarnings("unused") H msg, boolean isAlwaysEmpty) {
229
230 }
231
232
233
234
235
236
237
238
239 protected boolean isContentAlwaysEmpty(@SuppressWarnings("unused") H msg) {
240 return false;
241 }
242
243 @Override
244 public boolean acceptOutboundMessage(Object msg) throws Exception {
245 return msg instanceof HttpObject || msg instanceof ByteBuf || msg instanceof FileRegion;
246 }
247
248 private static Object encodeAndRetain(Object msg) {
249 if (msg instanceof ByteBuf) {
250 return ((ByteBuf) msg).retain();
251 }
252 if (msg instanceof HttpContent) {
253 return ((HttpContent) msg).content().retain();
254 }
255 if (msg instanceof FileRegion) {
256 return ((FileRegion) msg).retain();
257 }
258 throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
259 }
260
261 private static long contentLength(Object msg) {
262 if (msg instanceof HttpContent) {
263 return ((HttpContent) msg).content().readableBytes();
264 }
265 if (msg instanceof ByteBuf) {
266 return ((ByteBuf) msg).readableBytes();
267 }
268 if (msg instanceof FileRegion) {
269 return ((FileRegion) msg).count();
270 }
271 throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
272 }
273
274
275
276
277
278
279
280 private static int padSizeForAccumulation(int readableBytes) {
281 return (readableBytes << 2) / 3;
282 }
283
284 @Deprecated
285 protected static void encodeAscii(String s, ByteBuf buf) {
286 buf.writeCharSequence(s, CharsetUtil.US_ASCII);
287 }
288
289 protected abstract void encodeInitialLine(ByteBuf buf, H message) throws Exception;
290 }