/** * 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(); } } }
/** * 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; }