@Test
  public void testWebSocketResponse() {
    byte[] data =
        ("HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
                + "Upgrade: WebSocket\r\n"
                + "Connection: Upgrade\r\n"
                + "Sec-WebSocket-Origin: http://localhost:8080\r\n"
                + "Sec-WebSocket-Location: ws://localhost/some/path\r\n"
                + "\r\n"
                + "1234567812345678")
            .getBytes();
    EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
    ch.writeInbound(Unpooled.wrappedBuffer(data));

    HttpResponse res = ch.readInbound();
    assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
    assertThat(res.getStatus(), is(HttpResponseStatus.SWITCHING_PROTOCOLS));
    HttpContent content = ch.readInbound();
    assertThat(content.content().readableBytes(), is(16));
    content.release();

    assertThat(ch.finish(), is(false));

    assertThat(ch.readInbound(), is(nullValue()));
  }
  @Test
  public void testResponseWithContentLength() {
    EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
    ch.writeInbound(
        Unpooled.copiedBuffer(
            "HTTP/1.1 200 OK\r\n" + "Content-Length: 10\r\n" + "\r\n", CharsetUtil.US_ASCII));

    byte[] data = new byte[10];
    for (int i = 0; i < data.length; i++) {
      data[i] = (byte) i;
    }
    ch.writeInbound(Unpooled.wrappedBuffer(data, 0, data.length / 2));
    ch.writeInbound(Unpooled.wrappedBuffer(data, 5, data.length / 2));

    HttpResponse res = ch.readInbound();
    assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
    assertThat(res.getStatus(), is(HttpResponseStatus.OK));

    HttpContent firstContent = ch.readInbound();
    assertThat(firstContent.content().readableBytes(), is(5));
    assertEquals(Unpooled.wrappedBuffer(data, 0, 5), firstContent.content());
    firstContent.release();

    LastHttpContent lastContent = ch.readInbound();
    assertEquals(5, lastContent.content().readableBytes());
    assertEquals(Unpooled.wrappedBuffer(data, 5, 5), lastContent.content());
    lastContent.release();

    assertThat(ch.finish(), is(false));
    assertThat(ch.readInbound(), is(nullValue()));
  }
  @Test
  public void testPrematureClosureWithChunkedEncoding2() throws Exception {
    EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());

    // Write the partial response.
    ch.writeInbound(
        Unpooled.copiedBuffer(
            "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n8\r\n12345678",
            CharsetUtil.US_ASCII));

    // Read the response headers.
    HttpResponse res = ch.readInbound();
    assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
    assertThat(res.getStatus(), is(HttpResponseStatus.OK));
    assertThat(res.headers().get(Names.TRANSFER_ENCODING), is("chunked"));

    // Read the partial content.
    HttpContent content = ch.readInbound();
    assertThat(content.content().toString(CharsetUtil.US_ASCII), is("12345678"));
    assertThat(content, is(not(instanceOf(LastHttpContent.class))));
    content.release();

    assertThat(ch.readInbound(), is(nullValue()));

    // Close the connection.
    ch.finish();

    // The decoder should not generate the last chunk because it's closed prematurely.
    assertThat(ch.readInbound(), is(nullValue()));
  }
  @Test
  public void testLastResponseWithHeaderRemoveTrailingSpaces() {
    EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
    ch.writeInbound(
        Unpooled.copiedBuffer(
            "HTTP/1.1 200 OK\r\nX-Header: h2=h2v2; Expires=Wed, 09-Jun-2021 10:18:14 GMT       \r\n\r\n",
            CharsetUtil.US_ASCII));

    HttpResponse res = ch.readInbound();
    assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
    assertThat(res.getStatus(), is(HttpResponseStatus.OK));
    assertThat(res.headers().get("X-Header"), is("h2=h2v2; Expires=Wed, 09-Jun-2021 10:18:14 GMT"));
    assertThat(ch.readInbound(), is(nullValue()));

    ch.writeInbound(Unpooled.wrappedBuffer(new byte[1024]));
    HttpContent content = ch.readInbound();
    assertThat(content.content().readableBytes(), is(1024));
    content.release();

    assertThat(ch.finish(), is(true));

    LastHttpContent lastContent = ch.readInbound();
    assertThat(lastContent.content().isReadable(), is(false));
    lastContent.release();

    assertThat(ch.readInbound(), is(nullValue()));
  }
  @Test
  public void testClosureWithoutContentLength2() throws Exception {
    EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());

    // Write the partial response.
    ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\n\r\n12345678", CharsetUtil.US_ASCII));

    // Read the response headers.
    HttpResponse res = ch.readInbound();
    assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
    assertThat(res.getStatus(), is(HttpResponseStatus.OK));

    // Read the partial content.
    HttpContent content = ch.readInbound();
    assertThat(content.content().toString(CharsetUtil.US_ASCII), is("12345678"));
    assertThat(content, is(not(instanceOf(LastHttpContent.class))));
    content.release();

    assertThat(ch.readInbound(), is(nullValue()));

    // Close the connection.
    assertTrue(ch.finish());

    // The decoder should still produce the last content.
    LastHttpContent lastContent = ch.readInbound();
    assertThat(lastContent.content().isReadable(), is(false));
    lastContent.release();

    // But nothing more.
    assertThat(ch.readInbound(), is(nullValue()));
  }
Example #6
0
  private Object[] encodeContent(HttpMessage header, HttpContent c) {
    ByteBuf newContent = Unpooled.buffer();
    ByteBuf content = c.data();
    encode(content, newContent);

    if (c instanceof LastHttpContent) {
      ByteBuf lastProduct = Unpooled.buffer();
      finishEncode(lastProduct);

      // Generate an additional chunk if the decoder produced
      // the last product on closure,
      if (lastProduct.isReadable()) {
        if (header == null) {
          return new Object[] {
            new DefaultHttpContent(newContent), new DefaultLastHttpContent(lastProduct)
          };
        } else {
          return new Object[] {
            header, new DefaultHttpContent(newContent), new DefaultLastHttpContent(lastProduct)
          };
        }
      }
    }
    if (header == null) {
      return new Object[] {new DefaultHttpContent(newContent)};
    } else {
      return new Object[] {header, new DefaultHttpContent(newContent)};
    }
  }
  @Override
  public HttpContent encode(Connection connection, HttpContent httpContent) {

    final HttpHeader httpHeader = httpContent.getHttpHeader();
    final Buffer input = httpContent.getContent();

    if (httpContent.isLast() && !input.hasRemaining()) {
      return httpContent;
    }

    final TransformationResult<Buffer, Buffer> result =
        encoder.transform(httpContent.getHttpHeader(), input);

    input.tryDispose();

    try {
      switch (result.getStatus()) {
        case COMPLETE:
          encoder.finish(httpHeader);
        case INCOMPLETE:
          {
            Buffer encodedBuffer = result.getMessage();
            if (encodedBuffer != null) {
              httpContent.setContent(encodedBuffer);
              return httpContent;
            } else {
              return null;
            }
          }

        case ERROR:
          {
            throw new IllegalStateException(
                "LZMA encode error. Code: "
                    + result.getErrorCode()
                    + " Description: "
                    + result.getErrorDescription());
          }

        default:
          throw new IllegalStateException("Unexpected status: " + result.getStatus());
      }
    } finally {
      result.recycle();
    }
  }
  @Override
  public ParsingResult decode(Connection connection, HttpContent httpContent) {
    final HttpHeader httpHeader = httpContent.getHttpHeader();
    final Buffer input = httpContent.getContent();
    final TransformationResult<Buffer, Buffer> result = decoder.transform(httpHeader, input);

    Buffer remainder = result.getExternalRemainder();

    if (remainder == null || !remainder.hasRemaining()) {
      input.tryDispose();
      remainder = null;
    } else {
      input.shrink();
    }

    try {
      switch (result.getStatus()) {
        case COMPLETE:
          {
            httpContent.setContent(result.getMessage());
            decoder.finish(httpHeader);
            return ParsingResult.create(httpContent, remainder);
          }

        case INCOMPLETE:
          {
            return ParsingResult.create(null, remainder);
          }

        case ERROR:
          {
            throw new IllegalStateException(
                "LZMA decode error. Code: "
                    + result.getErrorCode()
                    + " Description: "
                    + result.getErrorDescription());
          }

        default:
          throw new IllegalStateException("Unexpected status: " + result.getStatus());
      }
    } finally {
      result.recycle();
    }
  }
  @Test
  public void testResponseChunked() {
    EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
    ch.writeInbound(
        Unpooled.copiedBuffer(
            "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n", CharsetUtil.US_ASCII));

    HttpResponse res = ch.readInbound();
    assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
    assertThat(res.getStatus(), is(HttpResponseStatus.OK));

    byte[] data = new byte[64];
    for (int i = 0; i < data.length; i++) {
      data[i] = (byte) i;
    }

    for (int i = 0; i < 10; i++) {
      assertFalse(
          ch.writeInbound(
              Unpooled.copiedBuffer(
                  Integer.toHexString(data.length) + "\r\n", CharsetUtil.US_ASCII)));
      assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(data)));
      HttpContent content = ch.readInbound();
      assertEquals(data.length, content.content().readableBytes());

      byte[] decodedData = new byte[data.length];
      content.content().readBytes(decodedData);
      assertArrayEquals(data, decodedData);
      content.release();

      assertFalse(ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII)));
    }

    // Write the last chunk.
    ch.writeInbound(Unpooled.copiedBuffer("0\r\n\r\n", CharsetUtil.US_ASCII));

    // Ensure the last chunk was decoded.
    LastHttpContent content = ch.readInbound();
    assertFalse(content.content().isReadable());
    content.release();

    ch.finish();
    assertNull(ch.readInbound());
  }
  /** {@inheritDoc} */
  @Override
  public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
    if (msg instanceof HttpRequest) {
      HttpRequest request = this.request = (HttpRequest) msg;
      trackingType = setTrackingType(request);
    }

    if (msg instanceof HttpContent) {
      // New chunk is received
      HttpContent chunk = (HttpContent) msg;
      requestContent.append(chunk.content().toString(Charset.defaultCharset()));
      if (chunk instanceof LastHttpContent) {
        // All chunks have been received
        ResultTO resultTO = submitPayload(requestContent.toString());
        responseContent.append(gson.toJson(resultTO));
        writeResponse(ctx.channel());
        reset();
      }
    }
  }
  @Test
  public void testLastResponseWithoutContentLengthHeader() {
    EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
    ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\n\r\n", CharsetUtil.US_ASCII));

    HttpResponse res = ch.readInbound();
    assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
    assertThat(res.getStatus(), is(HttpResponseStatus.OK));
    assertThat(ch.readInbound(), is(nullValue()));

    ch.writeInbound(Unpooled.wrappedBuffer(new byte[1024]));
    HttpContent content = ch.readInbound();
    assertThat(content.content().readableBytes(), is(1024));
    content.release();

    assertThat(ch.finish(), is(true));

    LastHttpContent lastContent = ch.readInbound();
    assertThat(lastContent.content().isReadable(), is(false));
    lastContent.release();

    assertThat(ch.readInbound(), is(nullValue()));
  }
  private static void testResponseWithContentLengthFragmented(byte[] header, int fragmentSize) {
    EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
    // split up the header
    for (int a = 0; a < header.length; ) {
      int amount = fragmentSize;
      if (a + amount > header.length) {
        amount = header.length - a;
      }

      ch.writeInbound(Unpooled.wrappedBuffer(header, a, amount));
      a += amount;
    }
    byte[] data = new byte[10];
    for (int i = 0; i < data.length; i++) {
      data[i] = (byte) i;
    }
    ch.writeInbound(Unpooled.wrappedBuffer(data, 0, data.length / 2));
    ch.writeInbound(Unpooled.wrappedBuffer(data, 5, data.length / 2));

    HttpResponse res = ch.readInbound();
    assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
    assertThat(res.getStatus(), is(HttpResponseStatus.OK));

    HttpContent firstContent = ch.readInbound();
    assertThat(firstContent.content().readableBytes(), is(5));
    assertEquals(Unpooled.wrappedBuffer(data, 0, 5), firstContent.content());
    firstContent.release();

    LastHttpContent lastContent = ch.readInbound();
    assertEquals(5, lastContent.content().readableBytes());
    assertEquals(Unpooled.wrappedBuffer(data, 5, 5), lastContent.content());
    lastContent.release();

    assertThat(ch.finish(), is(false));
    assertThat(ch.readInbound(), is(nullValue()));
  }
Example #13
0
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

      messageReceiveCount++;
      if (msg instanceof HttpResponse) {
        try {
          HttpResponse response = (HttpResponse) msg;

          StringBuilder sb = new StringBuilder();
          if (LOG.isDebugEnabled()) {
            sb.append("STATUS: ")
                .append(response.getStatus())
                .append(", VERSION: ")
                .append(response.getProtocolVersion())
                .append(", HEADER: ");
          }
          if (!response.headers().names().isEmpty()) {
            for (String name : response.headers().names()) {
              for (String value : response.headers().getAll(name)) {
                if (LOG.isDebugEnabled()) {
                  sb.append(name).append(" = ").append(value);
                }
                if (this.length == -1 && name.equals("Content-Length")) {
                  this.length = Long.parseLong(value);
                }
              }
            }
          }
          if (LOG.isDebugEnabled()) {
            LOG.debug(sb.toString());
          }

          if (response.getStatus().code() == HttpResponseStatus.NO_CONTENT.code()) {
            LOG.warn("There are no data corresponding to the request");
            length = 0;
            return;
          } else if (response.getStatus().code() != HttpResponseStatus.OK.code()) {
            LOG.error(response.getStatus().reasonPhrase());
            state = TajoProtos.FetcherState.FETCH_FAILED;
            return;
          }
        } catch (Exception e) {
          LOG.error(e.getMessage(), e);
        } finally {
          ReferenceCountUtil.release(msg);
        }
      }

      if (msg instanceof HttpContent) {
        try {
          HttpContent httpContent = (HttpContent) msg;
          ByteBuf content = httpContent.content();
          if (content.isReadable()) {
            content.readBytes(fc, content.readableBytes());
          }

          if (msg instanceof LastHttpContent) {
            if (raf != null) {
              fileLen = file.length();
            }

            finishTime = System.currentTimeMillis();
            if (state != TajoProtos.FetcherState.FETCH_FAILED) {
              state = TajoProtos.FetcherState.FETCH_FINISHED;
            }

            IOUtils.cleanup(LOG, fc, raf);
          }
        } catch (Exception e) {
          LOG.error(e.getMessage(), e);
        } finally {
          ReferenceCountUtil.release(msg);
        }
      }
    }
Example #14
0
 @Override
 public AssembledHttpResponse retain(int increment) {
   content.retain(increment);
   return this;
 }
Example #15
0
 @Override
 public AssembledHttpResponse retain() {
   content.retain();
   return this;
 }
Example #16
0
 @Override
 public boolean release(int decrement) {
   return content.release(decrement);
 }
Example #17
0
 @Override
 public boolean release() {
   return content.release();
 }
Example #18
0
 @Override
 public int refCnt() {
   return content.refCnt();
 }
Example #19
0
 @Override
 public ByteBuf content() {
   return content.content();
 }
Example #20
0
  @Override
  protected void decode(ChannelHandlerContext ctx, HttpObject msg, MessageBuf<Object> out)
      throws Exception {
    FullHttpMessage currentMessage = this.currentMessage;

    if (msg instanceof HttpMessage) {
      assert currentMessage == null;

      HttpMessage m = (HttpMessage) msg;

      // Handle the 'Expect: 100-continue' header if necessary.
      // TODO: Respond with 413 Request Entity Too Large
      //   and discard the traffic or close the connection.
      //       No need to notify the upstream handlers - just log.
      //       If decoding a response, just throw an exception.
      if (is100ContinueExpected(m)) {
        ctx.write(CONTINUE.duplicate());
      }

      if (!m.getDecoderResult().isSuccess()) {
        removeTransferEncodingChunked(m);
        this.currentMessage = null;
        out.add(BufUtil.retain(m));
        return;
      }
      if (msg instanceof HttpRequest) {
        HttpRequest header = (HttpRequest) msg;
        this.currentMessage =
            currentMessage =
                new DefaultFullHttpRequest(
                    header.getProtocolVersion(),
                    header.getMethod(),
                    header.getUri(),
                    Unpooled.compositeBuffer(maxCumulationBufferComponents));
      } else if (msg instanceof HttpResponse) {
        HttpResponse header = (HttpResponse) msg;
        this.currentMessage =
            currentMessage =
                new DefaultFullHttpResponse(
                    header.getProtocolVersion(),
                    header.getStatus(),
                    Unpooled.compositeBuffer(maxCumulationBufferComponents));
      } else {
        throw new Error();
      }

      currentMessage.headers().set(m.headers());

      // A streamed message - initialize the cumulative buffer, and wait for incoming chunks.
      removeTransferEncodingChunked(currentMessage);
    } else if (msg instanceof HttpContent) {
      assert currentMessage != null;

      // Merge the received chunk into the content of the current message.
      HttpContent chunk = (HttpContent) msg;
      CompositeByteBuf content = (CompositeByteBuf) currentMessage.content();

      if (content.readableBytes() > maxContentLength - chunk.content().readableBytes()) {
        // TODO: Respond with 413 Request Entity Too Large
        //   and discard the traffic or close the connection.
        //       No need to notify the upstream handlers - just log.
        //       If decoding a response, just throw an exception.
        throw new TooLongFrameException(
            "HTTP content length exceeded " + maxContentLength + " bytes.");
      }

      // Append the content of the chunk
      if (chunk.content().isReadable()) {
        chunk.retain();
        content.addComponent(chunk.content());
        content.writerIndex(content.writerIndex() + chunk.content().readableBytes());
      }

      final boolean last;
      if (!chunk.getDecoderResult().isSuccess()) {
        currentMessage.setDecoderResult(DecoderResult.failure(chunk.getDecoderResult().cause()));
        last = true;
      } else {
        last = chunk instanceof LastHttpContent;
      }

      if (last) {
        this.currentMessage = null;

        // Merge trailing headers into the message.
        if (chunk instanceof LastHttpContent) {
          LastHttpContent trailer = (LastHttpContent) chunk;
          currentMessage.headers().add(trailer.trailingHeaders());
        }

        // Set the 'Content-Length' header.
        currentMessage
            .headers()
            .set(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(content.readableBytes()));

        // All done
        out.add(currentMessage);
      }
    } else {
      throw new Error();
    }
  }
Example #21
0
  @Override
  protected Object encode(ChannelHandlerContext ctx, HttpObject msg) throws Exception {

    if (msg instanceof HttpResponse && ((HttpResponse) msg).getStatus().code() == 100) {
      // 100-continue response must be passed through.
      return msg;
    }

    // handle the case of single complete message without content
    if (msg instanceof FullHttpMessage && !((FullHttpMessage) msg).data().isReadable()) {

      // Remove content encoding
      String acceptEncoding = acceptEncodingQueue.poll();
      if (acceptEncoding == null) {
        throw new IllegalStateException("cannot send more responses than requests");
      }

      return msg;
    }

    if (msg instanceof HttpMessage) {
      assert message == null;

      // check if this message is also of type HttpContent is such case just make a safe copy of the
      // headers
      // as the content will get handled later and this simplify the handling
      if (msg instanceof HttpContent) {
        if (msg instanceof HttpRequest) {
          HttpRequest req = (HttpRequest) msg;
          message = new DefaultHttpRequest(req.getProtocolVersion(), req.getMethod(), req.getUri());
          message.headers().set(req.headers());
        } else if (msg instanceof HttpResponse) {
          HttpResponse res = (HttpResponse) msg;
          message = new DefaultHttpResponse(res.getProtocolVersion(), res.getStatus());
          message.headers().set(res.headers());
        } else {
          return msg;
        }
      } else {
        message = (HttpMessage) msg;
      }

      cleanup();
    }

    if (msg instanceof HttpContent) {
      HttpContent c = (HttpContent) msg;

      if (!encodeStarted) {
        encodeStarted = true;
        HttpMessage message = this.message;
        HttpHeaders headers = message.headers();
        this.message = null;

        // Determine the content encoding.
        String acceptEncoding = acceptEncodingQueue.poll();
        if (acceptEncoding == null) {
          throw new IllegalStateException("cannot send more responses than requests");
        }

        Result result = beginEncode(message, c, acceptEncoding);

        if (result == null) {
          if (c instanceof LastHttpContent) {
            return new Object[] {message, new DefaultLastHttpContent(c.data().retain())};
          } else {
            return new Object[] {message, new DefaultHttpContent(c.data().retain())};
          }
        }

        encoder = result.contentEncoder();

        // Encode the content and remove or replace the existing headers
        // so that the message looks like a decoded message.
        headers.set(HttpHeaders.Names.CONTENT_ENCODING, result.targetContentEncoding());

        Object[] encoded = encodeContent(message, c);

        if (!HttpHeaders.isTransferEncodingChunked(message) && encoded.length == 3) {
          if (headers.contains(HttpHeaders.Names.CONTENT_LENGTH)) {
            long length =
                ((ByteBufHolder) encoded[1]).data().readableBytes()
                    + ((ByteBufHolder) encoded[2]).data().readableBytes();

            headers.set(HttpHeaders.Names.CONTENT_LENGTH, Long.toString(length));
          }
        }
        return encoded;
      }

      if (encoder != null) {
        return encodeContent(null, c);
      }

      c.retain();
      return c;
    }
    return null;
  }
  @Override
  protected void encode(ChannelHandlerContext ctx, HttpObject msg, ByteBuf out) throws Exception {
    if (msg instanceof HttpMessage) {
      if (state != ST_INIT) {
        throw new IllegalStateException(
            "unexpected message type: " + msg.getClass().getSimpleName());
      }

      @SuppressWarnings({"unchecked", "CastConflictsWithInstanceof"})
      H m = (H) msg;

      // Encode the message.
      encodeInitialLine(out, m);
      encodeHeaders(out, m);
      out.writeByte(CR);
      out.writeByte(LF);

      state = HttpHeaders.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
    }

    if (msg instanceof HttpContent) {
      if (state == ST_INIT) {
        throw new IllegalStateException(
            "unexpected message type: " + msg.getClass().getSimpleName());
      }

      HttpContent chunk = (HttpContent) msg;
      ByteBuf content = chunk.content();
      int contentLength = content.readableBytes();

      if (state == ST_CONTENT_NON_CHUNK) {
        if (contentLength > 0) {
          out.writeBytes(content, content.readerIndex(), content.readableBytes());
        }

        if (chunk instanceof LastHttpContent) {
          state = ST_INIT;
        }
      } else if (state == ST_CONTENT_CHUNK) {
        if (contentLength > 0) {
          out.writeBytes(copiedBuffer(Integer.toHexString(contentLength), CharsetUtil.US_ASCII));
          out.writeByte(CR);
          out.writeByte(LF);
          out.writeBytes(content, content.readerIndex(), contentLength);
          out.writeByte(CR);
          out.writeByte(LF);
        }

        if (chunk instanceof LastHttpContent) {
          out.writeByte((byte) '0');
          out.writeByte(CR);
          out.writeByte(LF);
          encodeTrailingHeaders(out, (LastHttpContent) chunk);
          out.writeByte(CR);
          out.writeByte(LF);
          state = ST_INIT;
        }
      } else {
        throw new Error();
      }
    }
  }