@Override
  public int onDataRead(
      ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)
      throws Http2Exception {
    FullHttpMessage msg = messageMap.get(streamId);
    if (msg == null) {
      throw connectionError(
          PROTOCOL_ERROR, "Data Frame received for unknown stream id %d", streamId);
    }

    ByteBuf content = msg.content();
    final int dataReadableBytes = data.readableBytes();
    if (content.readableBytes() > maxContentLength - dataReadableBytes) {
      throw connectionError(
          INTERNAL_ERROR,
          "Content length exceeded max of %d for stream id %d",
          maxContentLength,
          streamId);
    }

    content.writeBytes(data, data.readerIndex(), dataReadableBytes);

    if (endOfStream) {
      fireChannelRead(ctx, msg, streamId);
    }

    // All bytes have been processed.
    return dataReadableBytes + padding;
  }
Example #2
0
  /**
   * Checks if the given HTTP message should be considered as a last SPDY frame.
   *
   * @param httpMessage check this HTTP message
   * @return whether the given HTTP message should generate a <em>last</em> SPDY frame.
   */
  private static boolean isLast(HttpMessage httpMessage) {
    if (httpMessage instanceof FullHttpMessage) {
      FullHttpMessage fullMessage = (FullHttpMessage) httpMessage;
      if (fullMessage.trailingHeaders().isEmpty() && !fullMessage.content().isReadable()) {
        return true;
      }
    }

    return false;
  }
 @Test
 public void clientRequestStreamDependencyInHttpMessageFlow() throws Exception {
   setServerLatch(2);
   final String text = "hello world big time data!";
   final ByteBuf content = Unpooled.copiedBuffer(text.getBytes());
   final String text2 = "hello world big time data...number 2!!";
   final ByteBuf content2 = Unpooled.copiedBuffer(text2.getBytes());
   final FullHttpRequest request =
       new DefaultFullHttpRequest(
           HttpVersion.HTTP_1_1, HttpMethod.PUT, "/some/path/resource", content, true);
   final FullHttpMessage request2 =
       new DefaultFullHttpRequest(
           HttpVersion.HTTP_1_1, HttpMethod.PUT, "/some/path/resource2", content2, true);
   try {
     HttpHeaders httpHeaders = request.headers();
     httpHeaders.set(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
     httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
     HttpHeaders httpHeaders2 = request2.headers();
     httpHeaders2.set(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
     httpHeaders2.set(HttpUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3);
     httpHeaders2.set(HttpUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), 123);
     httpHeaders2.set(HttpHeaders.Names.CONTENT_LENGTH, text2.length());
     final Http2Headers http2Headers =
         new DefaultHttp2Headers().method(as("PUT")).path(as("/some/path/resource"));
     final Http2Headers http2Headers2 =
         new DefaultHttp2Headers().method(as("PUT")).path(as("/some/path/resource2"));
     runInChannel(
         clientChannel,
         new Http2Runnable() {
           @Override
           public void run() {
             frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
             frameWriter.writeHeaders(ctxClient(), 5, http2Headers2, 0, false, newPromiseClient());
             frameWriter.writePriority(ctxClient(), 5, 3, (short) 123, true, newPromiseClient());
             frameWriter.writeData(ctxClient(), 3, content.retain(), 0, true, newPromiseClient());
             frameWriter.writeData(ctxClient(), 5, content2.retain(), 0, true, newPromiseClient());
             ctxClient().flush();
           }
         });
     awaitRequests();
     ArgumentCaptor<FullHttpMessage> httpObjectCaptor =
         ArgumentCaptor.forClass(FullHttpMessage.class);
     verify(serverListener, times(2)).messageReceived(httpObjectCaptor.capture());
     capturedRequests = httpObjectCaptor.getAllValues();
     assertEquals(request, capturedRequests.get(0));
     assertEquals(request2, capturedRequests.get(1));
   } finally {
     request.release();
     request2.release();
   }
 }
 @Override
 public boolean mustSendImmediately(FullHttpMessage msg) {
   if (msg instanceof FullHttpResponse) {
     return ((FullHttpResponse) msg).status().codeClass() == HttpStatusClass.INFORMATIONAL;
   }
   if (msg instanceof FullHttpRequest) {
     return msg.headers().contains(HttpHeaderNames.EXPECT);
   }
   return false;
 }
  @Override
  public void onPushPromiseRead(
      ChannelHandlerContext ctx,
      int streamId,
      int promisedStreamId,
      Http2Headers headers,
      int padding)
      throws Http2Exception {
    // A push promise should not be allowed to add headers to an existing stream
    FullHttpMessage msg = processHeadersBegin(ctx, promisedStreamId, headers, false, false, false);
    if (msg == null) {
      throw connectionError(
          PROTOCOL_ERROR,
          "Push Promise Frame received for pre-existing stream id %d",
          promisedStreamId);
    }

    msg.headers()
        .setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_PROMISE_ID.text(), streamId);

    processHeadersEnd(ctx, promisedStreamId, msg, false);
  }
  @Test
  public void serverResponseHeaderInformational() throws Exception {
    final FullHttpMessage request =
        new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "/info/test", true);
    HttpHeaders httpHeaders = request.headers();
    httpHeaders.set(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
    httpHeaders.set(HttpHeaders.Names.EXPECT, HttpHeaders.Values.CONTINUE);
    httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
    final Http2Headers http2Headers =
        new DefaultHttp2Headers()
            .method(as("PUT"))
            .path(as("/info/test"))
            .set(
                as(HttpHeaders.Names.EXPECT.toString()),
                as(HttpHeaders.Values.CONTINUE.toString()));
    final FullHttpMessage response =
        new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
    final String text = "a big payload";
    final ByteBuf payload = Unpooled.copiedBuffer(text.getBytes());
    final FullHttpMessage request2 = request.copy(payload);
    final FullHttpMessage response2 =
        new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);

    try {
      runInChannel(
          clientChannel,
          new Http2Runnable() {
            @Override
            public void run() {
              frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
              ctxClient().flush();
            }
          });
      awaitRequests();
      ArgumentCaptor<FullHttpMessage> requestCaptor =
          ArgumentCaptor.forClass(FullHttpMessage.class);
      verify(serverListener).messageReceived(requestCaptor.capture());
      capturedRequests = requestCaptor.getAllValues();
      assertEquals(request, capturedRequests.get(0));
      cleanupCapturedRequests();
      reset(serverListener);

      httpHeaders = response.headers();
      httpHeaders.set(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
      httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
      final Http2Headers http2HeadersResponse = new DefaultHttp2Headers().status(as("100"));
      runInChannel(
          serverConnectedChannel,
          new Http2Runnable() {
            @Override
            public void run() {
              frameWriter.writeHeaders(
                  ctxServer(), 3, http2HeadersResponse, 0, false, newPromiseServer());
              ctxServer().flush();
            }
          });
      awaitResponses();
      ArgumentCaptor<FullHttpMessage> responseCaptor =
          ArgumentCaptor.forClass(FullHttpMessage.class);
      verify(clientListener).messageReceived(responseCaptor.capture());
      capturedResponses = responseCaptor.getAllValues();
      assertEquals(response, capturedResponses.get(0));
      cleanupCapturedResponses();
      reset(clientListener);

      setServerLatch(1);
      httpHeaders = request2.headers();
      httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
      httpHeaders.remove(HttpHeaders.Names.EXPECT);
      runInChannel(
          clientChannel,
          new Http2Runnable() {
            @Override
            public void run() {
              frameWriter.writeData(ctxClient(), 3, payload.retain(), 0, true, newPromiseClient());
              ctxClient().flush();
            }
          });
      awaitRequests();
      requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class);
      verify(serverListener).messageReceived(requestCaptor.capture());
      capturedRequests = requestCaptor.getAllValues();
      assertEquals(request2, capturedRequests.get(0));

      setClientLatch(1);
      httpHeaders = response2.headers();
      httpHeaders.set(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
      httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
      final Http2Headers http2HeadersResponse2 = new DefaultHttp2Headers().status(as("200"));
      runInChannel(
          serverConnectedChannel,
          new Http2Runnable() {
            @Override
            public void run() {
              frameWriter.writeHeaders(
                  ctxServer(), 3, http2HeadersResponse2, 0, true, newPromiseServer());
              ctxServer().flush();
            }
          });
      awaitResponses();
      responseCaptor = ArgumentCaptor.forClass(FullHttpMessage.class);
      verify(clientListener).messageReceived(responseCaptor.capture());
      capturedResponses = responseCaptor.getAllValues();
      assertEquals(response2, capturedResponses.get(0));
    } finally {
      request.release();
      request2.release();
      response.release();
      response2.release();
    }
  }
  @Test
  public void serverRequestPushPromise() throws Exception {
    setClientLatch(2);
    final String text = "hello world big time data!";
    final ByteBuf content = Unpooled.copiedBuffer(text.getBytes());
    final String text2 = "hello world smaller data?";
    final ByteBuf content2 = Unpooled.copiedBuffer(text2.getBytes());
    final FullHttpMessage response =
        new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content, true);
    final FullHttpMessage response2 =
        new DefaultFullHttpResponse(
            HttpVersion.HTTP_1_1, HttpResponseStatus.CREATED, content2, true);
    final FullHttpMessage request =
        new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/push/test", true);
    try {
      HttpHeaders httpHeaders = response.headers();
      httpHeaders.set(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
      httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
      HttpHeaders httpHeaders2 = response2.headers();
      httpHeaders2.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "https");
      httpHeaders2.set(HttpUtil.ExtensionHeaderNames.AUTHORITY.text(), "example.org");
      httpHeaders2.set(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
      httpHeaders2.set(HttpUtil.ExtensionHeaderNames.STREAM_PROMISE_ID.text(), 3);
      httpHeaders2.set(HttpHeaders.Names.CONTENT_LENGTH, text2.length());

      httpHeaders = request.headers();
      httpHeaders.set(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
      httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
      final Http2Headers http2Headers3 =
          new DefaultHttp2Headers().method(as("GET")).path(as("/push/test"));
      runInChannel(
          clientChannel,
          new Http2Runnable() {
            @Override
            public void run() {
              frameWriter.writeHeaders(ctxClient(), 3, http2Headers3, 0, true, newPromiseClient());
              ctxClient().flush();
            }
          });
      awaitRequests();
      ArgumentCaptor<FullHttpMessage> requestCaptor =
          ArgumentCaptor.forClass(FullHttpMessage.class);
      verify(serverListener).messageReceived(requestCaptor.capture());
      capturedRequests = requestCaptor.getAllValues();
      assertEquals(request, capturedRequests.get(0));

      final Http2Headers http2Headers = new DefaultHttp2Headers().status(as("200"));
      final Http2Headers http2Headers2 =
          new DefaultHttp2Headers()
              .status(as("201"))
              .scheme(as("https"))
              .authority(as("example.org"));
      runInChannel(
          serverConnectedChannel,
          new Http2Runnable() {
            @Override
            public void run() {
              frameWriter.writeHeaders(ctxServer(), 3, http2Headers, 0, false, newPromiseServer());
              frameWriter.writePushPromise(ctxServer(), 3, 5, http2Headers2, 0, newPromiseServer());
              frameWriter.writeData(ctxServer(), 3, content.retain(), 0, true, newPromiseServer());
              frameWriter.writeData(ctxServer(), 5, content2.retain(), 0, true, newPromiseServer());
              ctxServer().flush();
            }
          });
      awaitResponses();
      ArgumentCaptor<FullHttpMessage> responseCaptor =
          ArgumentCaptor.forClass(FullHttpMessage.class);
      verify(clientListener, times(2)).messageReceived(responseCaptor.capture());
      capturedResponses = responseCaptor.getAllValues();
      assertEquals(response, capturedResponses.get(0));
      assertEquals(response2, capturedResponses.get(1));
    } finally {
      request.release();
      response.release();
      response2.release();
    }
  }
Example #8
0
  @Override
  protected void decode(
      ChannelHandlerContext ctx, SpdyDataOrControlFrame msg, MessageBuf<Object> out)
      throws Exception {
    if (msg instanceof SpdySynStreamFrame) {

      // HTTP requests/responses are mapped one-to-one to SPDY streams.
      SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
      int streamId = spdySynStreamFrame.getStreamId();

      if (SpdyCodecUtil.isServerId(streamId)) {
        // SYN_STREAM frames initiated by the server are pushed resources
        int associatedToStreamId = spdySynStreamFrame.getAssociatedToStreamId();

        // If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0
        // it must reply with a RST_STREAM with error code INVALID_STREAM
        if (associatedToStreamId == 0) {
          SpdyRstStreamFrame spdyRstStreamFrame =
              new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INVALID_STREAM);
          ctx.write(spdyRstStreamFrame);
        }

        String URL = SpdyHeaders.getUrl(spdyVersion, spdySynStreamFrame);

        // If a client receives a SYN_STREAM without a 'url' header
        // it must reply with a RST_STREAM with error code PROTOCOL_ERROR
        if (URL == null) {
          SpdyRstStreamFrame spdyRstStreamFrame =
              new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
          ctx.write(spdyRstStreamFrame);
        }

        try {
          FullHttpResponse httpResponseWithEntity =
              createHttpResponse(spdyVersion, spdySynStreamFrame);

          // Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers
          SpdyHttpHeaders.setStreamId(httpResponseWithEntity, streamId);
          SpdyHttpHeaders.setAssociatedToStreamId(httpResponseWithEntity, associatedToStreamId);
          SpdyHttpHeaders.setPriority(httpResponseWithEntity, spdySynStreamFrame.getPriority());
          SpdyHttpHeaders.setUrl(httpResponseWithEntity, URL);

          if (spdySynStreamFrame.isLast()) {
            HttpHeaders.setContentLength(httpResponseWithEntity, 0);
            out.add(httpResponseWithEntity);
          } else {
            // Response body will follow in a series of Data Frames
            putMessage(streamId, httpResponseWithEntity);
          }
        } catch (Exception e) {
          SpdyRstStreamFrame spdyRstStreamFrame =
              new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
          ctx.write(spdyRstStreamFrame);
        }
      } else {
        // SYN_STREAM frames initiated by the client are HTTP requests
        try {
          FullHttpRequest httpRequestWithEntity =
              createHttpRequest(spdyVersion, spdySynStreamFrame);

          // Set the Stream-ID as a header
          SpdyHttpHeaders.setStreamId(httpRequestWithEntity, streamId);

          if (spdySynStreamFrame.isLast()) {
            out.add(httpRequestWithEntity);
          } else {
            // Request body will follow in a series of Data Frames
            putMessage(streamId, httpRequestWithEntity);
          }
        } catch (Exception e) {
          // If a client sends a SYN_STREAM without all of the getMethod, url (host and path),
          // scheme, and version headers the server must reply with a HTTP 400 BAD REQUEST reply.
          // Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
          SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
          spdySynReplyFrame.setLast(true);
          SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST);
          SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
          ctx.write(spdySynReplyFrame);
        }
      }

    } else if (msg instanceof SpdySynReplyFrame) {

      SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
      int streamId = spdySynReplyFrame.getStreamId();

      try {
        FullHttpResponse httpResponseWithEntity =
            createHttpResponse(spdyVersion, spdySynReplyFrame);

        // Set the Stream-ID as a header
        SpdyHttpHeaders.setStreamId(httpResponseWithEntity, streamId);

        if (spdySynReplyFrame.isLast()) {
          HttpHeaders.setContentLength(httpResponseWithEntity, 0);
          out.add(httpResponseWithEntity);
        } else {
          // Response body will follow in a series of Data Frames
          putMessage(streamId, httpResponseWithEntity);
        }
      } catch (Exception e) {
        // If a client receives a SYN_REPLY without valid getStatus and version headers
        // the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR
        SpdyRstStreamFrame spdyRstStreamFrame =
            new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
        ctx.write(spdyRstStreamFrame);
      }

    } else if (msg instanceof SpdyHeadersFrame) {

      SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
      int streamId = spdyHeadersFrame.getStreamId();
      FullHttpMessage fullHttpMessage = getMessage(streamId);

      // If message is not in map discard HEADERS frame.
      if (fullHttpMessage == null) {
        return;
      }

      for (Map.Entry<String, String> e : spdyHeadersFrame.headers().entries()) {
        fullHttpMessage.headers().add(e.getKey(), e.getValue());
      }

      if (spdyHeadersFrame.isLast()) {
        HttpHeaders.setContentLength(fullHttpMessage, fullHttpMessage.content().readableBytes());
        removeMessage(streamId);
        out.add(fullHttpMessage);
      }

    } else if (msg instanceof SpdyDataFrame) {

      SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
      int streamId = spdyDataFrame.getStreamId();
      FullHttpMessage fullHttpMessage = getMessage(streamId);

      // If message is not in map discard Data Frame.
      if (fullHttpMessage == null) {
        return;
      }

      ByteBuf content = fullHttpMessage.content();
      if (content.readableBytes() > maxContentLength - spdyDataFrame.content().readableBytes()) {
        removeMessage(streamId);
        throw new TooLongFrameException(
            "HTTP content length exceeded " + maxContentLength + " bytes.");
      }

      ByteBuf spdyDataFrameData = spdyDataFrame.content();
      int spdyDataFrameDataLen = spdyDataFrameData.readableBytes();
      content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen);

      if (spdyDataFrame.isLast()) {
        HttpHeaders.setContentLength(fullHttpMessage, content.readableBytes());
        removeMessage(streamId);
        out.add(fullHttpMessage);
      }

    } else if (msg instanceof SpdyRstStreamFrame) {

      SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
      int streamId = spdyRstStreamFrame.getStreamId();
      removeMessage(streamId);
    }
  }
 /**
  * Set final headers and fire a channel read event
  *
  * @param ctx The context to fire the event on
  * @param msg The message to send
  * @param streamId the streamId of the message which is being fired
  */
 protected void fireChannelRead(ChannelHandlerContext ctx, FullHttpMessage msg, int streamId) {
   removeMessage(streamId);
   HttpUtil.setContentLength(msg, msg.content().readableBytes());
   ctx.fireChannelRead(msg);
 }