/**
   * Used to add additional HTTP message chunk content to {@link #inputContentBuffer}.
   *
   * @param requestedLen how much content should attempt to be read, <code>-1</code> means read till
   *     the end of the message.
   * @return the number of bytes actually read
   * @throws IOException if an I/O error occurs while reading content
   */
  private int fill(final int requestedLen) throws IOException {

    int read = 0;
    while ((requestedLen == -1 || read < requestedLen) && httpHeader.isExpectContent()) {

      final HttpContent c = blockingRead();

      final boolean isLast = c.isLast();
      // Check if HttpContent is chunked message trailer w/ headers
      checkHttpTrailer(c);

      final Buffer b;
      try {
        b = c.getContent();
      } catch (HttpBrokenContentException e) {
        final Throwable cause = e.getCause();
        throw Exceptions.makeIOException(cause != null ? cause : e);
      }

      read += b.remaining();
      updateInputContentBuffer(b);
      c.recycle();

      if (isLast) {
        finished();
        break;
      }
    }

    if (read > 0 || requestedLen == 0) {
      return read;
    }

    return -1;
  }
  /**
   * Appends the specified {@link Buffer} to the internal composite {@link Buffer}.
   *
   * @param httpContent the {@link HttpContent} to append
   * @return <code>true</code> if {@link ReadHandler} callback was invoked, otherwise returns <code>
   *     false</code>.
   * @throws IOException if an error occurs appending the {@link Buffer}
   */
  public boolean append(final HttpContent httpContent) throws IOException {

    // Stop waiting for data asynchronously and enable it again
    // only if we have a handler registered, which requirement
    // (expected size) is not met.
    isWaitingDataAsynchronously = false;

    // check if it's broken HTTP content message or not
    if (!HttpContent.isBroken(httpContent)) {
      final Buffer buffer = httpContent.getContent();

      if (closed) {
        buffer.dispose();
        return false;
      }

      final ReadHandler localHandler = handler;

      final boolean isLast = httpContent.isLast();

      // if we have a handler registered - switch the flag to true
      boolean askForMoreDataInThisThread = !isLast && localHandler != null;
      boolean invokeDataAvailable = false;

      if (buffer.hasRemaining()) {
        updateInputContentBuffer(buffer);
        if (localHandler != null) {
          final int available = readyData();
          if (available >= requestedSize) {
            invokeDataAvailable = true;
            askForMoreDataInThisThread = false;
          }
        }
      }

      if (askForMoreDataInThisThread) {
        // There is a ReadHandler registered, but it requested more
        // data to be available before we can notify it - so wait for
        // more data to come
        isWaitingDataAsynchronously = true;
        return true;
      }

      handler = null;

      if (isLast) {
        checkHttpTrailer(httpContent);
      }

      invokeHandlerOnProperThread(localHandler, invokeDataAvailable, isLast);

    } else { // broken content
      final ReadHandler localHandler = handler;
      handler = null;
      invokeErrorHandlerOnProperThread(
          localHandler, ((HttpBrokenContent) httpContent).getException());
    }

    return false;
  }
  @Override
  public NextAction handleRead(final FilterChainContext ctx) throws IOException {
    final HttpContent httpContent = ctx.getMessage();
    if (httpContent.isLast()) {
      // Perform the cleanup logic if it's the last chunk of the payload
      final HttpResponsePacket response = (HttpResponsePacket) httpContent.getHttpHeader();

      recycleRequestResponsePackets(ctx.getConnection(), response);
      return ctx.getStopAction();
    }

    return ctx.getInvokeAction();
  }
      @Override
      public NextAction handleRead(FilterChainContext ctx) throws IOException {

        HttpContent c = (HttpContent) ctx.getMessage();
        Buffer b = c.getContent();
        if (b.hasRemaining()) {
          sb.append(b.toStringContent());
        }

        // Last content from the server, set the future result so
        // the client can display the result and gracefully exit.
        if (c.isLast()) {
          future.result(sb.toString());
        }
        return ctx.getStopAction(); // discontinue filter chain execution
      }
  /**
   * Check if passed {@link HttpContent} is {@link HttpTrailer}, which represents trailer chunk
   * (when chunked Transfer-Encoding is used), if it is a trailer chunk - then copy all the
   * available trailer headers to request headers map.
   *
   * @param httpContent
   */
  private static void checkHttpTrailer(final HttpContent httpContent) {
    if (HttpTrailer.isTrailer(httpContent)) {
      final HttpTrailer httpTrailer = (HttpTrailer) httpContent;
      final HttpHeader httpHeader = httpContent.getHttpHeader();

      final MimeHeaders trailerHeaders = httpTrailer.getHeaders();
      final int size = trailerHeaders.size();
      for (int i = 0; i < size; i++) {
        httpHeader.addHeader(
            trailerHeaders.getName(i).toString(), trailerHeaders.getValue(i).toString());
      }
    }
  }
Exemple #6
0
    @Override
    public NextAction handleRead(FilterChainContext ctx) throws IOException {
      // Get the parsed HttpContent (we assume prev. filter was HTTP)
      HttpContent message = ctx.getMessage();
      Socket tunnelSocket = tunnelSockets.get(ctx.getConnection());

      if (tunnelSocket == null) {
        // handle connection procedure
        return GrizzlyModProxy.this.handleConnect(ctx, message);
      }

      if (message.getContent().hasRemaining()) {
        // relay the content to the tunnel connection

        Buffer buffer = message.getContent();
        message.recycle();

        tunnelSocket
            .getOutputStream()
            .write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
      }

      return ctx.getStopAction();
    }
  /**
   * Per-request initialization required for the InputBuffer to function properly.
   *
   * @param httpHeader the header from which input will be obtained.
   * @param ctx the FilterChainContext for the chain processing this request
   */
  public void initialize(final HttpHeader httpHeader, final FilterChainContext ctx) {

    if (ctx == null) {
      throw new IllegalArgumentException("ctx cannot be null.");
    }
    this.httpHeader = httpHeader;

    this.ctx = ctx;
    connection = ctx.getConnection();
    final Object message = ctx.getMessage();
    if (message instanceof HttpContent) {
      final HttpContent content = (HttpContent) message;

      // Check if HttpContent is chunked message trailer w/ headers
      checkHttpTrailer(content);
      updateInputContentBuffer(content.getContent());
      contentRead = content.isLast();
      content.recycle();

      if (LOGGER.isLoggable(LOGGER_LEVEL)) {
        log("InputBuffer %s initialize with ready content: %s", this, inputContentBuffer);
      }
    }
  }
  @SuppressWarnings("unchecked")
  public boolean sendRequest(
      final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket)
      throws IOException {

    boolean isWriteComplete = true;

    if (Utils.requestHasEntityBody(request)) {
      final HttpTxContext context = HttpTxContext.get(ctx);
      BodyHandler handler = bodyHandlerFactory.getBodyHandler(request);
      if (requestPacket.getHeaders().contains(Header.Expect)
          && requestPacket.getHeaders().getValue(1).equalsIgnoreCase("100-Continue")) {
        // We have to set the content-length now as the headers will be flushed
        // before the FileBodyHandler is invoked.  If we don't do it here, and
        // the user didn't explicitly set the length, then the transfer-encoding
        // will be chunked and zero-copy file transfer will not occur.
        final File f = request.getFile();
        if (f != null) {
          requestPacket.setContentLengthLong(f.length());
        }
        handler = new ExpectHandler(handler);
      }
      context.setBodyHandler(handler);
      if (logger.isDebugEnabled()) {
        logger.debug("REQUEST: {}", requestPacket);
      }
      isWriteComplete = handler.doHandle(ctx, request, requestPacket);
    } else {
      HttpContent content = HttpContent.builder(requestPacket).last(true).build();
      if (logger.isDebugEnabled()) {
        logger.debug("REQUEST: {}", requestPacket);
      }
      ctx.write(content, ctx.getTransportContext().getCompletionHandler());
    }

    return isWriteComplete;
  }
Exemple #9
0
 private void writeHttpResponse(FilterChainContext ctx, int status) {
   HttpResponsePacket responsePacket = getHttpRequest(ctx).getResponse();
   responsePacket.setProtocol(Protocol.HTTP_1_1);
   responsePacket.setStatus(status);
   ctx.write(HttpContent.builder(responsePacket).build());
 }
Exemple #10
0
  /**
   * Method invoked when a first massage (Assumed to be HTTP) is received. Normally this would be
   * HTTP CONNECT and this method processes it and opens a connection to the destination (the server
   * that the client wants to access).
   *
   * <p>This method can be overridden to provide a test-specific handling of the CONNECT method.
   */
  protected NextAction handleConnect(FilterChainContext ctx, HttpContent content) {
    System.out.println("Handle CONNECT start . . .");
    HttpHeader httpHeader = content.getHttpHeader();
    HttpRequestPacket requestPacket = (HttpRequestPacket) httpHeader.getHttpHeader();

    if (!requestPacket.getMethod().matchesMethod("CONNECT")) {
      System.out.println("Received method is not CONNECT");
      writeHttpResponse(ctx, 400);
      return ctx.getStopAction();
    }

    String destinationUri = requestPacket.getRequestURI();

    // We expect URI in form host:port, this is not flexible, but we use it only to test our clients
    int colonIdx = destinationUri.indexOf(':');

    if (colonIdx == -1) {
      System.out.println("Destination URI not in host:port format: " + destinationUri);
      writeHttpResponse(ctx, 400);
      return ctx.getStopAction();
    }

    String hostName = destinationUri.substring(0, colonIdx);
    String portStr = destinationUri.substring(colonIdx + 1);

    int port;
    try {
      port = Integer.parseInt(portStr);
    } catch (Throwable t) {
      System.out.println("Could not parse destination port: " + portStr);
      writeHttpResponse(ctx, 400);
      return ctx.getStopAction();
    }

    try {
      Socket tunnelSocket = new Socket(hostName, port);

      Connection grizzlyConnection = ctx.getConnection();
      tunnelSockets.set(grizzlyConnection, tunnelSocket);

      TunnelSocketReader tunnelSocketReader =
          new TunnelSocketReader(tunnelSocket, grizzlyConnection);
      executorService.submit(tunnelSocketReader::read);
    } catch (IOException e) {
      writeHttpResponse(ctx, 400);
      return ctx.getStopAction();
    }

    // Grizzly does not like CONNECT method and sets "keep alive" to false, if it is present
    // This hacks Grizzly, so it will keep the connection open
    HttpRequestPacket request = getHttpRequest(ctx);
    request.getResponse().getProcessingState().setKeepAlive(true);
    request.getResponse().setContentLength(0);
    request.setMethod("GET");
    // end of hack

    writeHttpResponse(ctx, 200);

    System.out.println("Connection to proxy established.");

    return ctx.getStopAction();
  }
  /**
   * Used to convert bytes to chars.
   *
   * @param requestedLen how much content should attempt to be read
   * @return the number of chars actually read
   * @throws IOException if an I/O error occurs while reading content
   */
  private int fillChars(final int requestedLen, final CharBuffer dst) throws IOException {

    int read = 0;

    // 1) Check pre-decoded singleCharBuf
    if (dst != singleCharBuf && singleCharBuf.hasRemaining()) {
      dst.put(singleCharBuf.get());
      read = 1;
    }

    // 2) Decode available byte[] -> char[]
    if (inputContentBuffer.hasRemaining()) {
      read += fillAvailableChars(requestedLen - read, dst);
    }

    if (read >= requestedLen) {
      dst.flip();
      return read;
    }

    // 3) If we don't expect more data - return what we've read so far
    if (!httpHeader.isExpectContent()) {
      dst.flip();
      return read > 0 ? read : -1;
    }

    // 4) Try to read more data (we may block)
    CharsetDecoder decoderLocal = getDecoder();

    boolean isNeedMoreInput =
        false; // true, if content in composite buffer is not enough to produce even 1 char
    boolean last = false;

    while (read < requestedLen && httpHeader.isExpectContent()) {

      if (isNeedMoreInput || !inputContentBuffer.hasRemaining()) {
        final HttpContent c = blockingRead();
        updateInputContentBuffer(c.getContent());
        last = c.isLast();

        c.recycle();
        isNeedMoreInput = false;
      }

      final ByteBuffer bytes = inputContentBuffer.toByteBuffer();

      final int bytesPos = bytes.position();
      final int dstPos = dst.position();

      final CoderResult result = decoderLocal.decode(bytes, dst, false);

      final int producedChars = dst.position() - dstPos;
      final int consumedBytes = bytes.position() - bytesPos;

      read += producedChars;

      if (consumedBytes > 0) {
        bytes.position(bytesPos);
        inputContentBuffer.position(inputContentBuffer.position() + consumedBytes);
        if (readAheadLimit == -1) {
          inputContentBuffer.shrink();
        }
      } else {
        isNeedMoreInput = true;
      }

      if (last || result == CoderResult.OVERFLOW) {
        break;
      }
    }

    dst.flip();

    if (last && read == 0) {
      read = -1;
    }
    return read;
  }