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