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.ByteBufHolder;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.embedded.EmbeddedChannel;
22 import io.netty.handler.codec.DecoderResult;
23 import io.netty.handler.codec.MessageToMessageCodec;
24 import io.netty.util.ReferenceCountUtil;
25 import io.netty.util.internal.ObjectUtil;
26 import io.netty.util.internal.StringUtil;
27
28 import java.util.ArrayDeque;
29 import java.util.List;
30 import java.util.Queue;
31
32 import static io.netty.handler.codec.http.HttpHeaderNames.*;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpRequest, HttpObject> {
57
58 private enum State {
59 PASS_THROUGH,
60 AWAIT_HEADERS,
61 AWAIT_CONTENT
62 }
63
64 private static final CharSequence ZERO_LENGTH_HEAD = "HEAD";
65 private static final CharSequence ZERO_LENGTH_CONNECT = "CONNECT";
66 private static final int CONTINUE_CODE = HttpResponseStatus.CONTINUE.code();
67
68 private final Queue<CharSequence> acceptEncodingQueue = new ArrayDeque<CharSequence>();
69 private EmbeddedChannel encoder;
70 private State state = State.AWAIT_HEADERS;
71
72 @Override
73 public boolean acceptOutboundMessage(Object msg) throws Exception {
74 return msg instanceof HttpContent || msg instanceof HttpResponse;
75 }
76
77 @Override
78 protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List<Object> out) throws Exception {
79 CharSequence acceptEncoding;
80 List<String> acceptEncodingHeaders = msg.headers().getAll(ACCEPT_ENCODING);
81 switch (acceptEncodingHeaders.size()) {
82 case 0:
83 acceptEncoding = HttpContentDecoder.IDENTITY;
84 break;
85 case 1:
86 acceptEncoding = acceptEncodingHeaders.get(0);
87 break;
88 default:
89
90 acceptEncoding = StringUtil.join(",", acceptEncodingHeaders);
91 break;
92 }
93
94 HttpMethod method = msg.method();
95 if (HttpMethod.HEAD.equals(method)) {
96 acceptEncoding = ZERO_LENGTH_HEAD;
97 } else if (HttpMethod.CONNECT.equals(method)) {
98 acceptEncoding = ZERO_LENGTH_CONNECT;
99 }
100
101 acceptEncodingQueue.add(acceptEncoding);
102 out.add(ReferenceCountUtil.retain(msg));
103 }
104
105 @Override
106 protected void encode(ChannelHandlerContext ctx, HttpObject msg, List<Object> out) throws Exception {
107 final boolean isFull = msg instanceof HttpResponse && msg instanceof LastHttpContent;
108 switch (state) {
109 case AWAIT_HEADERS: {
110 ensureHeaders(msg);
111 assert encoder == null;
112
113 final HttpResponse res = (HttpResponse) msg;
114 final int code = res.status().code();
115 final CharSequence acceptEncoding;
116 if (code == CONTINUE_CODE) {
117
118
119 acceptEncoding = null;
120 } else {
121
122 acceptEncoding = acceptEncodingQueue.poll();
123 if (acceptEncoding == null) {
124 throw new IllegalStateException("cannot send more responses than requests");
125 }
126 }
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141 if (isPassthru(res.protocolVersion(), code, acceptEncoding)) {
142 if (isFull) {
143 out.add(ReferenceCountUtil.retain(res));
144 } else {
145 out.add(ReferenceCountUtil.retain(res));
146
147 state = State.PASS_THROUGH;
148 }
149 break;
150 }
151
152 if (isFull) {
153
154 if (!((ByteBufHolder) res).content().isReadable()) {
155 out.add(ReferenceCountUtil.retain(res));
156 break;
157 }
158 }
159
160
161 final Result result = beginEncode(res, acceptEncoding.toString());
162
163
164 if (result == null) {
165 if (isFull) {
166 out.add(ReferenceCountUtil.retain(res));
167 } else {
168 out.add(ReferenceCountUtil.retain(res));
169
170 state = State.PASS_THROUGH;
171 }
172 break;
173 }
174
175 encoder = result.contentEncoder();
176
177
178
179 res.headers().set(HttpHeaderNames.CONTENT_ENCODING, result.targetContentEncoding());
180
181
182 if (isFull) {
183
184 HttpResponse newRes = new DefaultHttpResponse(res.protocolVersion(), res.status());
185 newRes.headers().set(res.headers());
186 out.add(newRes);
187
188 ensureContent(res);
189 encodeFullResponse(newRes, (HttpContent) res, out);
190 break;
191 } else {
192
193 res.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
194 res.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
195
196 out.add(ReferenceCountUtil.retain(res));
197 state = State.AWAIT_CONTENT;
198 if (!(msg instanceof HttpContent)) {
199
200
201 break;
202 }
203
204 }
205 }
206 case AWAIT_CONTENT: {
207 ensureContent(msg);
208 if (encodeContent((HttpContent) msg, out)) {
209 state = State.AWAIT_HEADERS;
210 }
211 break;
212 }
213 case PASS_THROUGH: {
214 ensureContent(msg);
215 out.add(ReferenceCountUtil.retain(msg));
216
217 if (msg instanceof LastHttpContent) {
218 state = State.AWAIT_HEADERS;
219 }
220 break;
221 }
222 }
223 }
224
225 private void encodeFullResponse(HttpResponse newRes, HttpContent content, List<Object> out) {
226 int existingMessages = out.size();
227 encodeContent(content, out);
228
229 if (HttpUtil.isContentLengthSet(newRes)) {
230
231 int messageSize = 0;
232 for (int i = existingMessages; i < out.size(); i++) {
233 Object item = out.get(i);
234 if (item instanceof HttpContent) {
235 messageSize += ((HttpContent) item).content().readableBytes();
236 }
237 }
238 HttpUtil.setContentLength(newRes, messageSize);
239 } else {
240 newRes.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
241 }
242 }
243
244 private static boolean isPassthru(HttpVersion version, int code, CharSequence httpMethod) {
245 return code < 200 || code == 204 || code == 304 ||
246 (httpMethod == ZERO_LENGTH_HEAD || (httpMethod == ZERO_LENGTH_CONNECT && code == 200)) ||
247 version == HttpVersion.HTTP_1_0;
248 }
249
250 private static void ensureHeaders(HttpObject msg) {
251 if (!(msg instanceof HttpResponse)) {
252 throw new IllegalStateException(
253 "unexpected message type: " +
254 msg.getClass().getName() + " (expected: " + HttpResponse.class.getSimpleName() + ')');
255 }
256 }
257
258 private static void ensureContent(HttpObject msg) {
259 if (!(msg instanceof HttpContent)) {
260 throw new IllegalStateException(
261 "unexpected message type: " +
262 msg.getClass().getName() + " (expected: " + HttpContent.class.getSimpleName() + ')');
263 }
264 }
265
266 private boolean encodeContent(HttpContent c, List<Object> out) {
267 ByteBuf content = c.content();
268
269 encode(content, out);
270
271 if (c instanceof LastHttpContent) {
272 finishEncode(out);
273 LastHttpContent last = (LastHttpContent) c;
274
275
276
277 HttpHeaders headers = last.trailingHeaders();
278 if (headers.isEmpty()) {
279 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
280 } else {
281 out.add(new ComposedLastHttpContent(headers, DecoderResult.SUCCESS));
282 }
283 return true;
284 }
285 return false;
286 }
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302 protected abstract Result beginEncode(HttpResponse httpResponse, String acceptEncoding) throws Exception;
303
304 @Override
305 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
306 cleanupSafely(ctx);
307 super.handlerRemoved(ctx);
308 }
309
310 @Override
311 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
312 cleanupSafely(ctx);
313 super.channelInactive(ctx);
314 }
315
316 private void cleanup() {
317 if (encoder != null) {
318
319 encoder.finishAndReleaseAll();
320 encoder = null;
321 }
322 }
323
324 private void cleanupSafely(ChannelHandlerContext ctx) {
325 try {
326 cleanup();
327 } catch (Throwable cause) {
328
329
330 ctx.fireExceptionCaught(cause);
331 }
332 }
333
334 private void encode(ByteBuf in, List<Object> out) {
335
336 encoder.writeOutbound(in.retain());
337 fetchEncoderOutput(out);
338 }
339
340 private void finishEncode(List<Object> out) {
341 if (encoder.finish()) {
342 fetchEncoderOutput(out);
343 }
344 encoder = null;
345 }
346
347 private void fetchEncoderOutput(List<Object> out) {
348 for (;;) {
349 ByteBuf buf = encoder.readOutbound();
350 if (buf == null) {
351 break;
352 }
353 if (!buf.isReadable()) {
354 buf.release();
355 continue;
356 }
357 out.add(new DefaultHttpContent(buf));
358 }
359 }
360
361 public static final class Result {
362 private final String targetContentEncoding;
363 private final EmbeddedChannel contentEncoder;
364
365 public Result(String targetContentEncoding, EmbeddedChannel contentEncoder) {
366 this.targetContentEncoding = ObjectUtil.checkNotNull(targetContentEncoding, "targetContentEncoding");
367 this.contentEncoder = ObjectUtil.checkNotNull(contentEncoder, "contentEncoder");
368 }
369
370 public String targetContentEncoding() {
371 return targetContentEncoding;
372 }
373
374 public EmbeddedChannel contentEncoder() {
375 return contentEncoder;
376 }
377 }
378 }