/**
   * 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());
      }
    }
  }
  /**
   * 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;
  }
  /** @see java.io.Reader#ready() */
  public boolean ready() {

    if (closed) {
      return false;
    }
    if (!processingChars) {
      throw new IllegalStateException();
    }
    return (inputContentBuffer.hasRemaining() || httpHeader.isExpectContent());
  }
  /**
   * This method should be called if the InputBuffer is being used in conjunction with a {@link
   * java.io.Reader} implementation. If this method is not called, any character-based methods
   * called on this <code>InputBuffer</code> will throw a {@link IllegalStateException}.
   */
  public void processingChars() {

    if (!processingChars) {
      processingChars = true;
      final String enc = httpHeader.getCharacterEncoding();
      if (enc != null) {
        encoding = enc;
        final CharsetDecoder localDecoder = getDecoder();
        averageCharsPerByte = localDecoder.averageCharsPerByte();
      }
    }
  }
Example #5
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;
  }