@Override public void run() { final ClientRequest request = new ClientRequest(); StringBuilder requestURI = new StringBuilder(); if (!clientConnection.getTargetPath().isEmpty() && !clientConnection.getTargetPath().equals("/")) { requestURI.append(clientConnection.getTargetPath()); } if (exchange.isHostIncludedInRequestURI()) { int uriPart = exchange.getRequestURI().indexOf("//"); if (uriPart == -1) { requestURI.append(exchange.getRequestURI()); } else { uriPart = exchange.getRequestURI().indexOf("/", uriPart); requestURI.append(exchange.getRequestURI().substring(uriPart)); } } else { requestURI.append(exchange.getRequestURI()); } String qs = exchange.getQueryString(); if (qs != null && !qs.isEmpty()) { requestURI.append('?'); requestURI.append(qs); } request.setPath(requestURI.toString()).setMethod(exchange.getRequestMethod()); final HeaderMap inboundRequestHeaders = exchange.getRequestHeaders(); final HeaderMap outboundRequestHeaders = request.getRequestHeaders(); copyHeaders(outboundRequestHeaders, inboundRequestHeaders); if (!exchange.isPersistent()) { // just because the client side is non-persistent // we don't want to close the connection to the backend outboundRequestHeaders.put(Headers.CONNECTION, "keep-alive"); } if ("h2c".equals(exchange.getRequestHeaders().getFirst(Headers.UPGRADE))) { // we don't allow h2c upgrade requests to be passed through to the backend exchange.getRequestHeaders().remove(Headers.UPGRADE); outboundRequestHeaders.put(Headers.CONNECTION, "keep-alive"); } for (Map.Entry<HttpString, ExchangeAttribute> entry : requestHeaders.entrySet()) { String headerValue = entry.getValue().readAttribute(exchange); if (headerValue == null || headerValue.isEmpty()) { outboundRequestHeaders.remove(entry.getKey()); } else { outboundRequestHeaders.put(entry.getKey(), headerValue.replace('\n', ' ')); } } final SocketAddress address = exchange.getConnection().getPeerAddress(); final String remoteHost = (address != null && address instanceof InetSocketAddress) ? ((InetSocketAddress) address).getHostString() : "localhost"; request.putAttachment(ProxiedRequestAttachments.REMOTE_HOST, remoteHost); if (reuseXForwarded && request.getRequestHeaders().contains(Headers.X_FORWARDED_FOR)) { // We have an existing header so we shall simply append the host to the existing list final String current = request.getRequestHeaders().getFirst(Headers.X_FORWARDED_FOR); if (current == null || current.isEmpty()) { // It was empty so just add it request.getRequestHeaders().put(Headers.X_FORWARDED_FOR, remoteHost); } else { // Add the new entry and reset the existing header request.getRequestHeaders().put(Headers.X_FORWARDED_FOR, current + "," + remoteHost); } } else { // No existing header or not allowed to reuse the header so set it here request.getRequestHeaders().put(Headers.X_FORWARDED_FOR, remoteHost); } // if we don't support push set a header saying so // this is non standard, and a problem with the HTTP2 spec, but they did not want to listen if (!exchange.getConnection().isPushSupported() && clientConnection.getConnection().isPushSupported()) { request.getRequestHeaders().put(Headers.X_DISABLE_PUSH, "true"); } // Set the protocol header and attachment if (reuseXForwarded && exchange.getRequestHeaders().contains(Headers.X_FORWARDED_PROTO)) { final String proto = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PROTO); request.putAttachment(ProxiedRequestAttachments.IS_SSL, proto.equals("https")); } else { final String proto = exchange.getRequestScheme().equals("https") ? "https" : "http"; request.getRequestHeaders().put(Headers.X_FORWARDED_PROTO, proto); request.putAttachment(ProxiedRequestAttachments.IS_SSL, proto.equals("https")); } // Set the server name if (reuseXForwarded && exchange.getRequestHeaders().contains(Headers.X_FORWARDED_SERVER)) { final String hostName = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_SERVER); request.putAttachment(ProxiedRequestAttachments.SERVER_NAME, hostName); } else { final String hostName = exchange.getHostName(); request.getRequestHeaders().put(Headers.X_FORWARDED_SERVER, hostName); request.putAttachment(ProxiedRequestAttachments.SERVER_NAME, hostName); } if (!exchange.getRequestHeaders().contains(Headers.X_FORWARDED_HOST)) { final String hostName = exchange.getHostName(); if (hostName != null) { request.getRequestHeaders().put(Headers.X_FORWARDED_HOST, hostName); } } // Set the port if (reuseXForwarded && exchange.getRequestHeaders().contains(Headers.X_FORWARDED_PORT)) { try { int port = Integer.parseInt(exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PORT)); request.putAttachment(ProxiedRequestAttachments.SERVER_PORT, port); } catch (NumberFormatException e) { int port = exchange.getConnection().getLocalAddress(InetSocketAddress.class).getPort(); request.getRequestHeaders().put(Headers.X_FORWARDED_PORT, port); request.putAttachment(ProxiedRequestAttachments.SERVER_PORT, port); } } else { int port = exchange.getConnection().getLocalAddress(InetSocketAddress.class).getPort(); request.getRequestHeaders().put(Headers.X_FORWARDED_PORT, port); request.putAttachment(ProxiedRequestAttachments.SERVER_PORT, port); } SSLSessionInfo sslSessionInfo = exchange.getConnection().getSslSessionInfo(); if (sslSessionInfo != null) { X509Certificate[] peerCertificates; try { peerCertificates = sslSessionInfo.getPeerCertificateChain(); if (peerCertificates.length > 0) { request.putAttachment( ProxiedRequestAttachments.SSL_CERT, Certificates.toPem(peerCertificates[0])); } } catch (SSLPeerUnverifiedException | CertificateEncodingException | RenegotiationRequiredException e) { // ignore } request.putAttachment( ProxiedRequestAttachments.SSL_CYPHER, sslSessionInfo.getCipherSuite()); request.putAttachment( ProxiedRequestAttachments.SSL_SESSION_ID, sslSessionInfo.getSessionId()); } if (rewriteHostHeader) { InetSocketAddress targetAddress = clientConnection.getConnection().getPeerAddress(InetSocketAddress.class); request .getRequestHeaders() .put(Headers.HOST, targetAddress.getHostString() + ":" + targetAddress.getPort()); request .getRequestHeaders() .put(Headers.X_FORWARDED_HOST, exchange.getRequestHeaders().getFirst(Headers.HOST)); } if (log.isDebugEnabled()) { log.debugf( "Sending request %s to target %s for exchange %s", request, remoteHost, exchange); } clientConnection .getConnection() .sendRequest( request, new ClientCallback<ClientExchange>() { @Override public void completed(final ClientExchange result) { if (log.isDebugEnabled()) { log.debugf( "Sent request %s to target %s for exchange %s", request, remoteHost, exchange); } result.putAttachment(EXCHANGE, exchange); boolean requiresContinueResponse = HttpContinue.requiresContinueResponse(exchange); if (requiresContinueResponse) { result.setContinueHandler( new ContinueNotification() { @Override public void handleContinue(final ClientExchange clientExchange) { if (log.isDebugEnabled()) { log.debugf( "Relieved continue response to request %s to target %s for exchange %s", request, remoteHost, exchange); } HttpContinue.sendContinueResponse( exchange, new IoCallback() { @Override public void onComplete( final HttpServerExchange exchange, final Sender sender) { // don't care } @Override public void onException( final HttpServerExchange exchange, final Sender sender, final IOException exception) { IoUtils.safeClose(clientConnection.getConnection()); exchange.endExchange(); UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); } }); } }); } // handle server push if (exchange.getConnection().isPushSupported() && result.getConnection().isPushSupported()) { result.setPushHandler( new PushCallback() { @Override public boolean handlePush( ClientExchange originalRequest, final ClientExchange pushedRequest) { if (log.isDebugEnabled()) { log.debugf( "Sending push request %s received from %s to target %s for exchange %s", pushedRequest.getRequest(), request, remoteHost, exchange); } final ClientRequest request = pushedRequest.getRequest(); exchange .getConnection() .pushResource( request.getPath(), request.getMethod(), request.getRequestHeaders(), new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { String path = request.getPath(); int i = path.indexOf("?"); if (i > 0) { path = path.substring(0, i); } exchange.dispatch( SameThreadExecutor.INSTANCE, new ProxyAction( new ProxyConnection( pushedRequest.getConnection(), path), exchange, requestHeaders, rewriteHostHeader, reuseXForwarded)); } }); return true; } }); } result.setResponseListener(new ResponseCallback(exchange)); final IoExceptionHandler handler = new IoExceptionHandler(exchange, clientConnection.getConnection()); if (requiresContinueResponse) { try { if (!result.getRequestChannel().flush()) { result .getRequestChannel() .getWriteSetter() .set( ChannelListeners.flushingChannelListener( new ChannelListener<StreamSinkChannel>() { @Override public void handleEvent(StreamSinkChannel channel) { Transfer.initiateTransfer( exchange.getRequestChannel(), result.getRequestChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(exchange, result), handler, handler, exchange.getConnection().getByteBufferPool()); } }, handler)); result.getRequestChannel().resumeWrites(); return; } } catch (IOException e) { handler.handleException(result.getRequestChannel(), e); } } Transfer.initiateTransfer( exchange.getRequestChannel(), result.getRequestChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(exchange, result), handler, handler, exchange.getConnection().getByteBufferPool()); } @Override public void failed(IOException e) { UndertowLogger.PROXY_REQUEST_LOGGER.proxyRequestFailed( exchange.getRequestURI(), e); if (!exchange.isResponseStarted()) { exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE); exchange.endExchange(); } else { IoUtils.safeClose(exchange.getConnection()); } } }); }