private boolean exitAfterHandler( Channel channel, NettyResponseFuture<?> future, HttpResponse response, AsyncHandler<?> handler, NettyResponseStatus status, HttpRequest httpRequest, HttpResponseHeaders responseHeaders) throws IOException, Exception { boolean exit = exitAfterHandlingStatus(channel, future, response, handler, status, httpRequest) || // exitAfterHandlingHeaders( channel, future, response, handler, responseHeaders, httpRequest) || // exitAfterHandlingReactiveStreams(channel, future, response, handler, httpRequest); if (exit) finishUpdate( future, channel, HttpHeaders.isTransferEncodingChunked(httpRequest) || HttpHeaders.isTransferEncodingChunked(response)); return exit; }
private boolean handleHanderAndExit( Channel channel, NettyResponseFuture<?> future, AsyncHandler<?> handler, HttpResponseStatus status, HttpResponseHeaders responseHeaders, HttpResponse response) throws Exception { if (!future.getAndSetStatusReceived(true) && (handler.onStatusReceived(status) != STATE.CONTINUE || handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE)) { finishUpdate(future, channel, HttpHeaders.isTransferEncodingChunked(response)); return true; } return false; }
@Override public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { if (msg instanceof HttpResponse) { HttpResponse response = (HttpResponse) msg; System.out.println("STATUS: " + response.getStatus()); System.out.println("VERSION: " + response.getProtocolVersion()); System.out.println(); if (!response.headers().isEmpty()) { for (String name : response.headers().names()) { for (String value : response.headers().getAll(name)) { System.out.println("HEADER: " + name + " = " + value); } } System.out.println(); } if (HttpHeaders.isTransferEncodingChunked(response)) { System.out.println("CHUNKED CONTENT {"); } else { System.out.println("CONTENT {"); } } if (msg instanceof HttpContent) { HttpContent content = (HttpContent) msg; System.out.print(content.content().toString(CharsetUtil.UTF_8)); System.out.flush(); if (content instanceof LastHttpContent) { System.out.println("} END OF CONTENT"); queue.add(ctx.channel().newSucceededFuture()); } } }
@Override protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception { ByteBuf buf = null; if (msg instanceof HttpMessage) { if (state != ST_INIT) { throw new IllegalStateException( "unexpected message type: " + StringUtil.simpleClassName(msg)); } @SuppressWarnings({"unchecked", "CastConflictsWithInstanceof"}) H m = (H) msg; buf = ctx.alloc().buffer(); // Encode the message. encodeInitialLine(buf, m); HttpHeaders.encode(m.headers(), buf); buf.writeBytes(CRLF); state = HttpHeaders.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK; } if (msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion) { if (state == ST_INIT) { throw new IllegalStateException( "unexpected message type: " + StringUtil.simpleClassName(msg)); } int contentLength = contentLength(msg); if (state == ST_CONTENT_NON_CHUNK) { if (contentLength > 0) { if (buf != null && buf.writableBytes() >= contentLength && msg instanceof HttpContent) { // merge into other buffer for performance reasons buf.writeBytes(((HttpContent) msg).content()); out.add(buf); } else { if (buf != null) { out.add(buf); } out.add(encodeAndRetain(msg)); } } else { if (buf != null) { out.add(buf); } else { // Need to produce some output otherwise an // IllegalStateException will be thrown out.add(EMPTY_BUFFER); } } if (msg instanceof LastHttpContent) { state = ST_INIT; } } else if (state == ST_CONTENT_CHUNK) { if (buf != null) { out.add(buf); } encodeChunkedContent(ctx, msg, contentLength, out); } else { throw new Error(); } } else { if (buf != null) { out.add(buf); } } }
private boolean exitAfterHandling407( // Channel channel, // NettyResponseFuture<?> future, // HttpResponse response, // Request request, // int statusCode, // ProxyServer proxyServer, // HttpRequest httpRequest) { if (future.getInProxyAuth().getAndSet(true)) { logger.info("Can't handle 407 as auth was already performed"); return false; } Realm proxyRealm = future.getProxyRealm(); if (proxyRealm == null) { logger.info("Can't handle 407 as there's no proxyRealm"); return false; } List<String> proxyAuthHeaders = response.headers().getAll(HttpHeaders.Names.PROXY_AUTHENTICATE); if (proxyAuthHeaders.isEmpty()) { logger.info("Can't handle 407 as response doesn't contain Proxy-Authenticate headers"); return false; } // FIXME what's this??? future.setChannelState(ChannelState.NEW); HttpHeaders requestHeaders = new DefaultHttpHeaders(false).add(request.getHeaders()); switch (proxyRealm.getScheme()) { case BASIC: if (getHeaderWithPrefix(proxyAuthHeaders, "Basic") == null) { logger.info( "Can't handle 407 with Basic realm as Proxy-Authenticate headers don't match"); return false; } if (proxyRealm.isUsePreemptiveAuth()) { // FIXME do we need this, as future.getAndSetAuth // was tested above? // auth was already performed, most likely auth // failed logger.info( "Can't handle 407 with Basic realm as auth was preemptive and already performed"); return false; } // FIXME do we want to update the realm, or directly // set the header? Realm newBasicRealm = realm(proxyRealm) // .setUsePreemptiveAuth(true) // .build(); future.setProxyRealm(newBasicRealm); break; case DIGEST: String digestHeader = getHeaderWithPrefix(proxyAuthHeaders, "Digest"); if (digestHeader == null) { logger.info( "Can't handle 407 with Digest realm as Proxy-Authenticate headers don't match"); return false; } Realm newDigestRealm = realm(proxyRealm) // .setUri(request.getUri()) // .setMethodName(request.getMethod()) // .setUsePreemptiveAuth(true) // .parseProxyAuthenticateHeader(digestHeader) // .build(); future.setProxyRealm(newDigestRealm); break; case NTLM: String ntlmHeader = getHeaderWithPrefix(proxyAuthHeaders, "NTLM"); if (ntlmHeader == null) { logger.info("Can't handle 407 with NTLM realm as Proxy-Authenticate headers don't match"); return false; } ntlmProxyChallenge(ntlmHeader, request, proxyRealm, requestHeaders, future); Realm newNtlmRealm = realm(proxyRealm) // .setUsePreemptiveAuth(true) // .build(); future.setProxyRealm(newNtlmRealm); break; case KERBEROS: case SPNEGO: if (getHeaderWithPrefix(proxyAuthHeaders, "Negociate") == null) { logger.info( "Can't handle 407 with Kerberos or Spnego realm as Proxy-Authenticate headers don't match"); return false; } try { kerberosProxyChallenge( channel, proxyAuthHeaders, request, proxyServer, proxyRealm, requestHeaders, future); } catch (SpnegoEngineException e) { // FIXME String ntlmHeader2 = getHeaderWithPrefix(proxyAuthHeaders, "NTLM"); if (ntlmHeader2 != null) { logger.warn("Kerberos/Spnego proxy auth failed, proceeding with NTLM"); ntlmChallenge(ntlmHeader2, request, requestHeaders, proxyRealm, future); Realm newNtlmRealm2 = realm(proxyRealm) // .setScheme(AuthScheme.NTLM) // .setUsePreemptiveAuth(true) // .build(); future.setProxyRealm(newNtlmRealm2); } else { requestSender.abort(channel, future, e); return false; } } break; default: throw new IllegalStateException("Invalid Authentication scheme " + proxyRealm.getScheme()); } RequestBuilder nextRequestBuilder = new RequestBuilder(future.getCurrentRequest()).setHeaders(requestHeaders); if (future.getCurrentRequest().getUri().isSecured()) { nextRequestBuilder.setMethod(CONNECT); } final Request nextRequest = nextRequestBuilder.build(); logger.debug("Sending proxy authentication to {}", request.getUri()); if (future.isKeepAlive() // && HttpHeaders.isKeepAlive(httpRequest) // && HttpHeaders.isKeepAlive(response) // // support broken Proxy-Connection && !response.headers().contains("Proxy-Connection", HttpHeaders.Values.CLOSE, true) // && !HttpHeaders.isTransferEncodingChunked(httpRequest) // && !HttpHeaders.isTransferEncodingChunked(response)) { future.setConnectAllowed(true); future.setReuseChannel(true); requestSender.drainChannelAndExecuteNextRequest(channel, future, nextRequest); } else { channelManager.closeChannel(channel); requestSender.sendNextRequest(nextRequest, future); } return true; }
private boolean handleUnauthorizedAndExit( int statusCode, Realm realm, final Request request, HttpResponse response, final NettyResponseFuture<?> future, ProxyServer proxyServer, final Channel channel) throws Exception { if (statusCode == UNAUTHORIZED.code() && realm != null) { List<String> authenticateHeaders = response.headers().getAll(HttpHeaders.Names.WWW_AUTHENTICATE); if (!authenticateHeaders.isEmpty() && !future.getAndSetAuth(true)) { future.setState(NettyResponseFuture.STATE.NEW); Realm newRealm = null; // NTLM boolean negociate = authenticateHeaders.contains("Negotiate"); if (!authenticateHeaders.contains("Kerberos") && (isNTLM(authenticateHeaders) || negociate)) { newRealm = ntlmChallenge( authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future); // SPNEGO KERBEROS } else if (negociate) { newRealm = kerberosChallenge( authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future); if (newRealm == null) { return true; } } else { newRealm = new Realm.RealmBuilder() .clone(realm) .setScheme(realm.getAuthScheme()) .setUri(request.getURI().getPath()) .setMethodName(request.getMethod()) .setUsePreemptiveAuth(true) .parseWWWAuthenticateHeader(authenticateHeaders.get(0)) .build(); } Realm nr = new Realm.RealmBuilder() .clone(newRealm) .setUri(URI.create(request.getUrl()).getPath()) .build(); final Request nextRequest = new RequestBuilder(future.getRequest()) .setHeaders(request.getHeaders()) .setRealm(nr) .build(); LOGGER.debug("Sending authentication to {}", request.getUrl()); Callback callback = new Callback(future) { public void call() throws Exception { channels.drainChannel(channel, future); requestSender.sendNextRequest(nextRequest, future); } }; if (future.isKeepAlive() && HttpHeaders.isTransferEncodingChunked(response)) { // We must make sure there is no bytes left // before executing the next request. Channels.setDefaultAttribute(channel, callback); } else { callback.call(); } return true; } } return false; }
@Override public void messageReceived(ChannelHandlerContext ctx, HttpObject msg) throws Exception { if (msg instanceof HttpRequest) { HttpRequest request = this.request = (HttpRequest) msg; URI uri = new URI(request.getUri()); if (!uri.getPath().startsWith("/form")) { // Write Menu writeMenu(ctx); return; } responseContent.setLength(0); responseContent.append("WELCOME TO THE WILD WILD WEB SERVER\r\n"); responseContent.append("===================================\r\n"); responseContent.append("VERSION: " + request.getProtocolVersion().text() + "\r\n"); responseContent.append("REQUEST_URI: " + request.getUri() + "\r\n\r\n"); responseContent.append("\r\n\r\n"); // new getMethod for (Entry<String, String> entry : request.headers()) { responseContent.append("HEADER: " + entry.getKey() + '=' + entry.getValue() + "\r\n"); } responseContent.append("\r\n\r\n"); // new getMethod Set<Cookie> cookies; String value = request.headers().get(COOKIE); if (value == null) { cookies = Collections.emptySet(); } else { cookies = CookieDecoder.decode(value); } for (Cookie cookie : cookies) { responseContent.append("COOKIE: " + cookie + "\r\n"); } responseContent.append("\r\n\r\n"); QueryStringDecoder decoderQuery = new QueryStringDecoder(request.getUri()); Map<String, List<String>> uriAttributes = decoderQuery.parameters(); for (Entry<String, List<String>> attr : uriAttributes.entrySet()) { for (String attrVal : attr.getValue()) { responseContent.append("URI: " + attr.getKey() + '=' + attrVal + "\r\n"); } } responseContent.append("\r\n\r\n"); // if GET Method: should not try to create a HttpPostRequestDecoder if (request.getMethod().equals(HttpMethod.GET)) { // GET Method: should not try to create a HttpPostRequestDecoder // So stop here responseContent.append("\r\n\r\nEND OF GET CONTENT\r\n"); writeResponse(ctx.channel()); return; } try { decoder = new HttpPostRequestDecoder(factory, request); } catch (ErrorDataDecoderException e1) { e1.printStackTrace(); responseContent.append(e1.getMessage()); writeResponse(ctx.channel()); ctx.channel().close(); return; } readingChunks = HttpHeaders.isTransferEncodingChunked(request); responseContent.append("Is Chunked: " + readingChunks + "\r\n"); responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n"); if (readingChunks) { // Chunk version responseContent.append("Chunks: "); readingChunks = true; } } // check if the decoder was constructed before // if not it handles the form get if (decoder != null) { if (msg instanceof HttpContent) { // New chunk is received HttpContent chunk = (HttpContent) msg; try { decoder.offer(chunk); } catch (ErrorDataDecoderException e1) { e1.printStackTrace(); responseContent.append(e1.getMessage()); writeResponse(ctx.channel()); ctx.channel().close(); return; } responseContent.append('o'); // example of reading chunk by chunk (minimize memory usage due to // Factory) readHttpDataChunkByChunk(); // example of reading only if at the end if (chunk instanceof LastHttpContent) { writeResponse(ctx.channel()); readingChunks = false; reset(); } } } }
@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; }
protected boolean exitAfterHandlingRedirect( // Channel channel, // NettyResponseFuture<?> future, // HttpResponse response, // Request request, // int statusCode) throws Exception { if (followRedirect(config, request) && REDIRECT_STATUSES.contains(statusCode)) { if (future.incrementAndGetCurrentRedirectCount() >= config.getMaxRedirects()) { throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); } else { // We must allow 401 handling again. future.getAndSetAuth(false); HttpHeaders responseHeaders = response.headers(); String location = responseHeaders.get(HttpHeaders.Names.LOCATION); Uri uri = Uri.create(future.getUri(), location); if (!uri.equals(future.getUri())) { final RequestBuilder requestBuilder = new RequestBuilder(future.getRequest()); if (!config.isRemoveQueryParamOnRedirect()) requestBuilder.addQueryParams(future.getRequest().getQueryParams()); // if we are to strictly handle 302, we should keep the original method (which browsers // don't) // 303 must force GET if ((statusCode == FOUND.code() && !config.isStrict302Handling()) || statusCode == SEE_OTHER.code()) requestBuilder.setMethod("GET"); // in case of a redirect from HTTP to HTTPS, future attributes might change final boolean initialConnectionKeepAlive = future.isKeepAlive(); final String initialPoolKey = channelManager.getPartitionId(future); future.setUri(uri); String newUrl = uri.toUrl(); if (request.getUri().getScheme().startsWith(WEBSOCKET)) { newUrl = newUrl.replaceFirst(HTTP, WEBSOCKET); } logger.debug("Redirecting to {}", newUrl); for (String cookieStr : responseHeaders.getAll(HttpHeaders.Names.SET_COOKIE)) { Cookie c = CookieDecoder.decode(cookieStr, timeConverter); if (c != null) requestBuilder.addOrReplaceCookie(c); } Callback callback = channelManager.newDrainCallback( future, channel, initialConnectionKeepAlive, initialPoolKey); if (HttpHeaders.isTransferEncodingChunked(response)) { // We must make sure there is no bytes left before // executing the next request. // FIXME investigate this Channels.setAttribute(channel, callback); } else { // FIXME don't understand: this offers the connection to the pool, or even closes it, // while the // request has not been sent, right? callback.call(); } Request redirectRequest = requestBuilder.setUrl(newUrl).build(); // FIXME why not reuse the channel is same host? requestSender.sendNextRequest(redirectRequest, future); return true; } } } return false; }
@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(); } } }