View Javadoc
1   /*
2    * Copyright 2013 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.spdy;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufAllocator;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.handler.codec.MessageToMessageDecoder;
22  import io.netty.handler.codec.TooLongFrameException;
23  import io.netty.handler.codec.http.DefaultFullHttpRequest;
24  import io.netty.handler.codec.http.DefaultFullHttpResponse;
25  import io.netty.handler.codec.http.FullHttpMessage;
26  import io.netty.handler.codec.http.FullHttpRequest;
27  import io.netty.handler.codec.http.FullHttpResponse;
28  import io.netty.handler.codec.http.HttpHeaderNames;
29  import io.netty.handler.codec.http.DefaultHttpHeadersFactory;
30  import io.netty.handler.codec.http.HttpHeadersFactory;
31  import io.netty.handler.codec.http.HttpUtil;
32  import io.netty.handler.codec.http.HttpMethod;
33  import io.netty.handler.codec.http.HttpResponseStatus;
34  import io.netty.handler.codec.http.HttpVersion;
35  import io.netty.handler.codec.http.TooLongHttpHeaderException;
36  import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
37  import io.netty.util.ReferenceCountUtil;
38  import io.netty.util.collection.IntObjectHashMap;
39  import io.netty.util.collection.IntObjectMap;
40  import io.netty.util.internal.ObjectUtil;
41  
42  import java.util.HashMap;
43  import java.util.List;
44  import java.util.Map;
45  
46  import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
47  import static io.netty.util.internal.ObjectUtil.checkPositive;
48  
49  /**
50   * Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s,
51   * and {@link SpdyDataFrame}s into {@link FullHttpRequest}s and {@link FullHttpResponse}s.
52   */
53  public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
54  
55      // Same default as SpdyFrameCodec's maxHeaderSize.
56      private static final int DEFAULT_MAX_HEADER_SIZE = 16384;
57      private final int spdyVersion;
58      private final int maxHeaderSize;
59      private final int maxContentLength;
60      private final Map<Integer, FullHttpMessage> messageMap;
61      private final IntObjectMap<Long> headerSizeMap = new IntObjectHashMap<Long>();
62      private final HttpHeadersFactory headersFactory;
63      private final HttpHeadersFactory trailersFactory;
64  
65      /**
66       * Creates a new instance.
67       *
68       * @param version the protocol version
69       * @param maxContentLength the maximum length of the message content.
70       *        If the length of the message content exceeds this value,
71       *        a {@link TooLongFrameException} will be raised.
72       */
73      public SpdyHttpDecoder(SpdyVersion version, int maxContentLength) {
74          this(version, maxContentLength, new HashMap<Integer, FullHttpMessage>(),
75                  DefaultHttpHeadersFactory.headersFactory(), DefaultHttpHeadersFactory.trailersFactory());
76      }
77  
78      /**
79       * Creates a new instance.
80       *
81       * @param version the protocol version
82       * @param maxContentLength the maximum length of the message content.
83       *        If the length of the message content exceeds this value,
84       *        a {@link TooLongFrameException} will be raised.
85       * @param validateHeaders {@code true} if http headers should be validated
86       * @deprecated Use the {@link #SpdyHttpDecoder(SpdyVersion, int, Map, HttpHeadersFactory, HttpHeadersFactory)}
87       * constructor instead.
88       */
89      @Deprecated
90      public SpdyHttpDecoder(SpdyVersion version, int maxContentLength, boolean validateHeaders) {
91          this(version, maxContentLength, new HashMap<Integer, FullHttpMessage>(), validateHeaders);
92      }
93  
94      /**
95       * Creates a new instance with the specified parameters.
96       *
97       * @param version the protocol version
98       * @param maxContentLength the maximum length of the message content.
99       *        If the length of the message content exceeds this value,
100      *        a {@link TooLongFrameException} will be raised.
101      * @param messageMap the {@link Map} used to hold partially received messages.
102      */
103     protected SpdyHttpDecoder(SpdyVersion version, int maxContentLength, Map<Integer, FullHttpMessage> messageMap) {
104         this(version, maxContentLength, messageMap,
105                 DefaultHttpHeadersFactory.headersFactory(), DefaultHttpHeadersFactory.trailersFactory());
106     }
107 
108     /**
109      * Creates a new instance with the specified parameters.
110      *
111      * @param version the protocol version
112      * @param maxContentLength the maximum length of the message content.
113      *        If the length of the message content exceeds this value,
114      *        a {@link TooLongFrameException} will be raised.
115      * @param messageMap the {@link Map} used to hold partially received messages.
116      * @param validateHeaders {@code true} if http headers should be validated
117      * @deprecated Use the {@link #SpdyHttpDecoder(SpdyVersion, int, Map, HttpHeadersFactory, HttpHeadersFactory)}
118      * constructor instead.
119      */
120     @Deprecated
121     protected SpdyHttpDecoder(SpdyVersion version, int maxContentLength, Map<Integer,
122             FullHttpMessage> messageMap, boolean validateHeaders) {
123         this(version, maxContentLength, messageMap,
124                 DefaultHttpHeadersFactory.headersFactory().withValidation(validateHeaders),
125                 DefaultHttpHeadersFactory.trailersFactory().withValidation(validateHeaders));
126     }
127 
128     /**
129      * Creates a new instance with the specified parameters.
130      *
131      * @param version the protocol version
132      * @param maxContentLength the maximum length of the message content.
133      *        If the length of the message content exceeds this value,
134      *        a {@link TooLongFrameException} will be raised.
135      * @param messageMap the {@link Map} used to hold partially received messages.
136      * @param headersFactory The factory used for creating HTTP headers
137      * @param trailersFactory The factory used for creating HTTP trailers.
138      */
139     protected SpdyHttpDecoder(SpdyVersion version, int maxContentLength, Map<Integer,
140             FullHttpMessage> messageMap, HttpHeadersFactory headersFactory, HttpHeadersFactory trailersFactory) {
141         this(version, DEFAULT_MAX_HEADER_SIZE, maxContentLength, messageMap, headersFactory, trailersFactory);
142     }
143 
144     /**
145      * Creates a new instance with the specified parameters.
146      *
147      * @param version the protocol version
148      * @param maxHeaderSize the maximum length of all headers.  If the sum of the length of each
149      *        header exceeds this value, a {@link TooLongHttpHeaderException} will be raised.
150      * @param maxContentLength the maximum length of the message content.
151      *        If the length of the message content exceeds this value,
152      *        a {@link TooLongFrameException} will be raised.
153      * @param messageMap the {@link Map} used to hold partially received messages.
154      * @param headersFactory The factory used for creating HTTP headers
155      * @param trailersFactory The factory used for creating HTTP trailers.
156      */
157     protected SpdyHttpDecoder(SpdyVersion version, int maxHeaderSize, int maxContentLength, Map<Integer,
158         FullHttpMessage> messageMap, HttpHeadersFactory headersFactory, HttpHeadersFactory trailersFactory) {
159         super(SpdyFrame.class);
160         spdyVersion = ObjectUtil.checkNotNull(version, "version").version();
161         this.maxHeaderSize = checkPositive(maxHeaderSize, "maxHeaderSize");
162         this.maxContentLength = checkPositive(maxContentLength, "maxContentLength");
163         this.messageMap = messageMap;
164         this.headersFactory = headersFactory;
165         this.trailersFactory = trailersFactory;
166     }
167 
168     @Override
169     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
170         // Release any outstanding messages from the map
171         for (Map.Entry<Integer, FullHttpMessage> entry : messageMap.entrySet()) {
172             ReferenceCountUtil.safeRelease(entry.getValue());
173         }
174         messageMap.clear();
175         headerSizeMap.clear();
176         super.handlerRemoved(ctx);
177     }
178 
179     protected FullHttpMessage putMessage(int streamId, FullHttpMessage message) {
180         return messageMap.put(streamId, message);
181     }
182 
183     protected FullHttpMessage getMessage(int streamId) {
184         return messageMap.get(streamId);
185     }
186 
187     protected FullHttpMessage removeMessage(int streamId) {
188         headerSizeMap.remove(streamId);
189         return messageMap.remove(streamId);
190     }
191 
192     @Override
193     protected void decode(ChannelHandlerContext ctx, SpdyFrame msg, List<Object> out)
194             throws Exception {
195         if (msg instanceof SpdySynStreamFrame) {
196 
197             // HTTP requests/responses are mapped one-to-one to SPDY streams.
198             SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
199             int streamId = spdySynStreamFrame.streamId();
200 
201             if (SpdyCodecUtil.isServerId(streamId)) {
202                 // SYN_STREAM frames initiated by the server are pushed resources
203                 int associatedToStreamId = spdySynStreamFrame.associatedStreamId();
204 
205                 // If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0
206                 // it must reply with a RST_STREAM with error code INVALID_STREAM.
207                 if (associatedToStreamId == 0) {
208                     SpdyRstStreamFrame spdyRstStreamFrame =
209                         new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INVALID_STREAM);
210                     ctx.writeAndFlush(spdyRstStreamFrame);
211                     return;
212                 }
213 
214                 // If a client receives a SYN_STREAM with isLast set,
215                 // reply with a RST_STREAM with error code PROTOCOL_ERROR
216                 // (we only support pushed resources divided into two header blocks).
217                 if (spdySynStreamFrame.isLast()) {
218                     SpdyRstStreamFrame spdyRstStreamFrame =
219                         new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
220                     ctx.writeAndFlush(spdyRstStreamFrame);
221                     return;
222                 }
223 
224                 // If a client receives a response with a truncated header block,
225                 // reply with a RST_STREAM with error code INTERNAL_ERROR.
226                 if (spdySynStreamFrame.isTruncated()) {
227                     SpdyRstStreamFrame spdyRstStreamFrame =
228                     new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INTERNAL_ERROR);
229                     ctx.writeAndFlush(spdyRstStreamFrame);
230                     return;
231                 }
232 
233                 FullHttpRequest httpRequestWithEntity = null;
234                 try {
235                     httpRequestWithEntity = createHttpRequest(spdySynStreamFrame, ctx.alloc());
236 
237                     // Set the Stream-ID, Associated-To-Stream-ID, and Priority as headers
238                     httpRequestWithEntity.headers().setInt(Names.STREAM_ID, streamId);
239                     httpRequestWithEntity.headers().setInt(Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId);
240                     httpRequestWithEntity.headers().setInt(Names.PRIORITY, spdySynStreamFrame.priority());
241 
242                     out.add(httpRequestWithEntity);
243                     httpRequestWithEntity = null;
244                 } catch (Throwable ignored) {
245                     if (httpRequestWithEntity != null) {
246                         httpRequestWithEntity.release();
247                     }
248                     SpdyRstStreamFrame spdyRstStreamFrame =
249                         new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
250                     ctx.writeAndFlush(spdyRstStreamFrame);
251                 }
252             } else {
253                 // SYN_STREAM frames initiated by the client are HTTP requests
254 
255                 // If a client sends a request with a truncated header block, the server must
256                 // reply with an HTTP 431 REQUEST HEADER FIELDS TOO LARGE reply.
257                 if (spdySynStreamFrame.isTruncated()) {
258                     SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
259                     spdySynReplyFrame.setLast(true);
260                     SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
261                     frameHeaders.setInt(STATUS, HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE.code());
262                     frameHeaders.setObject(VERSION, HttpVersion.HTTP_1_0);
263                     ctx.writeAndFlush(spdySynReplyFrame);
264                     return;
265                 }
266 
267                 FullHttpRequest httpRequestWithEntity = null;
268                 try {
269                     httpRequestWithEntity = createHttpRequest(spdySynStreamFrame, ctx.alloc());
270 
271                     // Set the Stream-ID as a header
272                     httpRequestWithEntity.headers().setInt(Names.STREAM_ID, streamId);
273 
274                     if (spdySynStreamFrame.isLast()) {
275                         out.add(httpRequestWithEntity);
276                         httpRequestWithEntity = null;
277                     } else {
278                         // Request body will follow in a series of Data Frames
279                         FullHttpMessage old = putMessage(streamId, httpRequestWithEntity);
280                         httpRequestWithEntity = null;
281                         if (old != null) {
282                             old.release();
283                         }
284                     }
285                 } catch (Throwable t) {
286                     if (httpRequestWithEntity != null) {
287                         httpRequestWithEntity.release();
288                     }
289                     // If a client sends a SYN_STREAM without all of the getMethod, url (host and path),
290                     // scheme, and version headers the server must reply with an HTTP 400 BAD REQUEST reply.
291                     // Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
292                     SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
293                     spdySynReplyFrame.setLast(true);
294                     SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
295                     frameHeaders.setInt(STATUS, HttpResponseStatus.BAD_REQUEST.code());
296                     frameHeaders.setObject(VERSION, HttpVersion.HTTP_1_0);
297                     ctx.writeAndFlush(spdySynReplyFrame);
298                 }
299             }
300 
301         } else if (msg instanceof SpdySynReplyFrame) {
302 
303             SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
304             int streamId = spdySynReplyFrame.streamId();
305 
306             // If a client receives a SYN_REPLY with a truncated header block,
307             // reply with a RST_STREAM frame with error code INTERNAL_ERROR.
308             if (spdySynReplyFrame.isTruncated()) {
309                 SpdyRstStreamFrame spdyRstStreamFrame =
310                         new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INTERNAL_ERROR);
311                 ctx.writeAndFlush(spdyRstStreamFrame);
312                 return;
313             }
314 
315             FullHttpResponse httpResponseWithEntity = null;
316             try {
317                 httpResponseWithEntity = createHttpResponse(spdySynReplyFrame, ctx.alloc());
318 
319                 // Set the Stream-ID as a header
320                 httpResponseWithEntity.headers().setInt(Names.STREAM_ID, streamId);
321 
322                 if (spdySynReplyFrame.isLast()) {
323                     HttpUtil.setContentLength(httpResponseWithEntity, 0);
324                     out.add(httpResponseWithEntity);
325                     httpResponseWithEntity = null;
326                 } else {
327                     // Response body will follow in a series of Data Frames
328                     FullHttpMessage old = putMessage(streamId, httpResponseWithEntity);
329                     httpResponseWithEntity = null;
330                     if (old != null) {
331                         old.release();
332                     }
333                 }
334             } catch (Throwable t) {
335                 if (httpResponseWithEntity != null) {
336                     httpResponseWithEntity.release();
337                 }
338                 // If a client receives a SYN_REPLY without valid getStatus and version headers
339                 // the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR
340                 SpdyRstStreamFrame spdyRstStreamFrame =
341                     new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
342                 ctx.writeAndFlush(spdyRstStreamFrame);
343             }
344 
345         } else if (msg instanceof SpdyHeadersFrame) {
346 
347             SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
348             int streamId = spdyHeadersFrame.streamId();
349             FullHttpMessage fullHttpMessage = getMessage(streamId);
350 
351             if (fullHttpMessage == null) {
352                 // HEADERS frames may initiate a pushed response
353                 if (SpdyCodecUtil.isServerId(streamId)) {
354 
355                     // If a client receives a HEADERS with a truncated header block,
356                     // reply with a RST_STREAM frame with error code INTERNAL_ERROR.
357                     if (spdyHeadersFrame.isTruncated()) {
358                         SpdyRstStreamFrame spdyRstStreamFrame =
359                             new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INTERNAL_ERROR);
360                         ctx.writeAndFlush(spdyRstStreamFrame);
361                         return;
362                     }
363 
364                     try {
365                         fullHttpMessage = createHttpResponse(spdyHeadersFrame, ctx.alloc());
366 
367                         // Set the Stream-ID as a header
368                         fullHttpMessage.headers().setInt(Names.STREAM_ID, streamId);
369 
370                         if (spdyHeadersFrame.isLast()) {
371                             HttpUtil.setContentLength(fullHttpMessage, 0);
372                             out.add(fullHttpMessage);
373                             fullHttpMessage = null;
374                         } else {
375                             // Response body will follow in a series of Data Frames
376                             FullHttpMessage old = putMessage(streamId, fullHttpMessage);
377                             fullHttpMessage = null;
378                             if (old != null) {
379                                 old.release();
380                             }
381                         }
382                     } catch (Throwable t) {
383                         if (fullHttpMessage != null) {
384                             fullHttpMessage.release();
385                         }
386                         // If a client receives a SYN_REPLY without valid getStatus and version headers
387                         // the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR
388                         SpdyRstStreamFrame spdyRstStreamFrame =
389                             new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
390                         ctx.writeAndFlush(spdyRstStreamFrame);
391                     }
392                 }
393                 return;
394             }
395 
396             // Ignore trailers in a truncated HEADERS frame.
397             if (!spdyHeadersFrame.isTruncated()) {
398                 Long oldHeaderSize = headerSizeMap.get(streamId);
399                 long headerSize = oldHeaderSize == null ? 0 : oldHeaderSize;
400 
401                 boolean addHeaderSize = true;
402                 try {
403                     for (Map.Entry<CharSequence, CharSequence> e : spdyHeadersFrame.headers()) {
404                         long entrySize = headerEntrySize(e.getKey(), e.getValue());
405                         if (headerSize > maxHeaderSize - entrySize) {
406                             FullHttpMessage removed = removeMessage(streamId);
407                             if (removed != null && removed != fullHttpMessage) {
408                                 removed.release();
409                             }
410                             fullHttpMessage.release();
411                             addHeaderSize = false;
412                             throw new TooLongHttpHeaderException(
413                                 "HTTP header size exceeded " + maxHeaderSize + " bytes.");
414                         }
415                         headerSize += entrySize;
416                         fullHttpMessage.headers().add(e.getKey(), e.getValue());
417                     }
418                 } finally {
419                     if (addHeaderSize) {
420                         headerSizeMap.put(streamId, Long.valueOf(headerSize));
421                     }
422                 }
423             }
424 
425             if (spdyHeadersFrame.isLast()) {
426                 HttpUtil.setContentLength(fullHttpMessage, fullHttpMessage.content().readableBytes());
427                 FullHttpMessage removed = removeMessage(streamId);
428                 if (removed != null && removed != fullHttpMessage) {
429                     removed.release();
430                 }
431                 out.add(fullHttpMessage);
432             }
433         } else if (msg instanceof SpdyDataFrame) {
434 
435             SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
436             int streamId = spdyDataFrame.streamId();
437             FullHttpMessage fullHttpMessage = getMessage(streamId);
438 
439             // If message is not in map discard Data Frame.
440             if (fullHttpMessage == null) {
441                 return;
442             }
443 
444             ByteBuf content = fullHttpMessage.content();
445             if (content.readableBytes() > maxContentLength - spdyDataFrame.content().readableBytes()) {
446                 FullHttpMessage removed = removeMessage(streamId);
447                 if (removed != null && removed != fullHttpMessage) {
448                     removed.release();
449                 }
450                 fullHttpMessage.release();
451                 throw new TooLongFrameException(
452                         "HTTP content length exceeded " + maxContentLength + " bytes: "
453                                 + spdyDataFrame.content().readableBytes());
454             }
455 
456             ByteBuf spdyDataFrameData = spdyDataFrame.content();
457             int spdyDataFrameDataLen = spdyDataFrameData.readableBytes();
458             content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen);
459 
460             if (spdyDataFrame.isLast()) {
461                 HttpUtil.setContentLength(fullHttpMessage, content.readableBytes());
462                 FullHttpMessage removed = removeMessage(streamId);
463                 if (removed != null && removed != fullHttpMessage) {
464                     removed.release();
465                 }
466                 out.add(fullHttpMessage);
467             }
468 
469         } else if (msg instanceof SpdyRstStreamFrame) {
470 
471             SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
472             int streamId = spdyRstStreamFrame.streamId();
473             FullHttpMessage removed = removeMessage(streamId);
474             if (removed != null) {
475                 removed.release();
476             }
477         }
478     }
479 
480     private static long headerEntrySize(CharSequence name, CharSequence value) {
481         // Account for the ": " separator between name and value on the wire.
482         return (long) name.length() + value.length() + 2;
483     }
484 
485     private static FullHttpRequest createHttpRequest(SpdyHeadersFrame requestFrame, ByteBufAllocator alloc)
486        throws Exception {
487         // Create the first line of the request from the name/value pairs
488         SpdyHeaders headers     = requestFrame.headers();
489         HttpMethod  method      = HttpMethod.valueOf(headers.getAsString(METHOD));
490         String      url         = headers.getAsString(PATH);
491         HttpVersion httpVersion = HttpVersion.valueOf(headers.getAsString(VERSION));
492         headers.remove(METHOD);
493         headers.remove(PATH);
494         headers.remove(VERSION);
495 
496         boolean release = true;
497         ByteBuf buffer = alloc.buffer();
498         try {
499             FullHttpRequest req = new DefaultFullHttpRequest(httpVersion, method, url, buffer);
500 
501             // Remove the scheme header
502             headers.remove(SCHEME);
503 
504             // Replace the SPDY host header with the HTTP host header
505             CharSequence host = headers.get(HOST);
506             headers.remove(HOST);
507             req.headers().set(HttpHeaderNames.HOST, host);
508 
509             for (Map.Entry<CharSequence, CharSequence> e : requestFrame.headers()) {
510                 req.headers().add(e.getKey(), e.getValue());
511             }
512 
513             // The Connection and Keep-Alive headers are no longer valid
514             HttpUtil.setKeepAlive(req, true);
515 
516             // Transfer-Encoding header is not valid
517             req.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
518             release = false;
519             return req;
520         } finally {
521             if (release) {
522                 buffer.release();
523             }
524         }
525     }
526 
527     private FullHttpResponse createHttpResponse(SpdyHeadersFrame responseFrame, ByteBufAllocator alloc)
528             throws Exception {
529 
530         // Create the first line of the response from the name/value pairs
531         SpdyHeaders headers = responseFrame.headers();
532         HttpResponseStatus status = HttpResponseStatus.parseLine(headers.get(STATUS));
533         HttpVersion version = HttpVersion.valueOf(headers.getAsString(VERSION));
534         headers.remove(STATUS);
535         headers.remove(VERSION);
536 
537         boolean release = true;
538         ByteBuf buffer = alloc.buffer();
539         try {
540             FullHttpResponse res = new DefaultFullHttpResponse(
541                     version, status, buffer, headersFactory, trailersFactory);
542             for (Map.Entry<CharSequence, CharSequence> e: responseFrame.headers()) {
543                 res.headers().add(e.getKey(), e.getValue());
544             }
545 
546             // The Connection and Keep-Alive headers are no longer valid
547             HttpUtil.setKeepAlive(res, true);
548 
549             // Transfer-Encoding header is not valid
550             res.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
551             res.headers().remove(HttpHeaderNames.TRAILER);
552 
553             release = false;
554             return res;
555         } finally {
556             if (release) {
557                 buffer.release();
558             }
559         }
560     }
561 }