Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * JBoss, Home of Professional Open Source.
   * Copyright 2014 Red Hat, Inc., and individual contributors
   * as indicated by the @author tags.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  *  Unless required by applicable law or agreed to in writing, software
  *  distributed under the License is distributed on an "AS IS" BASIS,
  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
 
 package io.undertow.server.handlers.proxy;
 
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
An HTTP handler which proxies content to a remote server.

This handler acts like a filter. The ProxyClient has a chance to decide if it knows how to proxy the request. If it does then it will provide a connection that can used to connect to the remote server, otherwise the next handler will be invoked and the request will proceed as normal.

Author(s):
David M. Lloyd
 
 public final class ProxyHandler implements HttpHandler {
 
     private static final Logger log = Logger.getLogger(ProxyHandler.class);
 
     public static final String UTF_8 = "UTF-8";
     private final ProxyClient proxyClient;
     private final int maxRequestTime;
 
     private static final AttachmentKey<ProxyConnectionCONNECTION = AttachmentKey.create(ProxyConnection.class);
     private static final AttachmentKey<HttpServerExchangeEXCHANGE = AttachmentKey.create(HttpServerExchange.class);
    private static final AttachmentKey<XnioExecutor.KeyTIMEOUT_KEY = AttachmentKey.create(XnioExecutor.Key.class);

    
Map of additional headers to add to the request.
    private final Map<HttpStringExchangeAttributerequestHeaders = new CopyOnWriteMap<>();
    private final HttpHandler next;
    private final boolean rewriteHostHeader;
    private final boolean reuseXForwarded;
    public ProxyHandler(ProxyClient proxyClientint maxRequestTimeHttpHandler next) {
        this(proxyClientmaxRequestTimenextfalsefalse);
    }

  

Parameters:
proxyClient the client to use to make the proxy call
maxRequestTime the maximum amount of time to allow the request to be processed
next the next handler in line
rewriteHostHeader should the HOST header be rewritten to use the target host of the call.
reuseXForwarded should any existing X-Forwarded-For header be used or should it be overwritten.
    public ProxyHandler(ProxyClient proxyClientint maxRequestTimeHttpHandler nextboolean rewriteHostHeaderboolean reuseXForwarded) {
        this. = proxyClient;
        this. = maxRequestTime;
        this. = next;
        this. = rewriteHostHeader;
        this. = reuseXForwarded;
    }
    public ProxyHandler(ProxyClient proxyClientHttpHandler next) {
        this(proxyClient, -1, next);
    }
    public void handleRequest(final HttpServerExchange exchangethrows Exception {
        final ProxyClient.ProxyTarget target = .findTarget(exchange);
        if (target == null) {
            .debugf("No proxy target for request to %s"exchange.getRequestURL());
            .handleRequest(exchange);
            return;
        }
        final int maxRetryAttempts = 0; // TODO make this configurable, or just take from the error policy?
        final long timeout =  > 0 ? System.currentTimeMillis() +  : 0;
        final ProxyClientHandler clientHandler = new ProxyClientHandler(exchangetargettimeoutmaxRetryAttempts);
        if (timeout > 0) {
            final XnioExecutor.Key key = exchange.getIoThread().executeAfter(new Runnable() {
                @Override
                public void run() {
                    clientHandler.cancel(exchange);
                }
            }, .);
            exchange.putAttachment(key);
            exchange.addExchangeCompleteListener(new ExchangeCompletionListener() {
                @Override
                public void exchangeEvent(HttpServerExchange exchangeNextListener nextListener) {
                    key.remove();
                    nextListener.proceed();
                }
            });
        }
        exchange.dispatch(exchange.isInIoThread() ? . : exchange.getIoThread(), clientHandler);
    }

    
Adds a request header to the outgoing request. If the header resolves to null or an empty string it will not be added, however any existing header with the same name will be removed.

Parameters:
header The header name
attribute The header value attribute.
Returns:
this
    public ProxyHandler addRequestHeader(final HttpString headerfinal ExchangeAttribute attribute) {
        .put(headerattribute);
        return this;
    }

    
Adds a request header to the outgoing request. If the header resolves to null or an empty string it will not be added, however any existing header with the same name will be removed.

Parameters:
header The header name
value The header value attribute.
Returns:
this
    public ProxyHandler addRequestHeader(final HttpString headerfinal String value) {
        .put(header, ExchangeAttributes.constant(value));
        return this;
    }

    
Adds a request header to the outgoing request. If the header resolves to null or an empty string it will not be added, however any existing header with the same name will be removed.

The attribute value will be parsed, and the resulting exchange attribute will be used to create the actual header value.

Parameters:
header The header name
attribute The header value attribute.
Returns:
this
    public ProxyHandler addRequestHeader(final HttpString headerfinal String attributefinal ClassLoader classLoader) {
        .put(header, ExchangeAttributes.parser(classLoader).parse(attribute));
        return this;
    }

    
Removes a request header

Parameters:
header the header
Returns:
this
    public ProxyHandler removeRequestHeader(final HttpString header) {
        .remove(header);
        return this;
    }
    static void copyHeaders(final HeaderMap tofinal HeaderMap from) {
        long f = from.fastIterateNonEmpty();
        HeaderValues values;
        while (f != -1L) {
            values = from.fiCurrent(f);
            if(!to.contains(values.getHeaderName())) {
                //don't over write existing headers, normally the map will be empty, if it is not we assume it is not for a reason
                to.putAll(values.getHeaderName(), values);
            }
            f = from.fiNextNonEmpty(f);
        }
    }
    public ProxyClient getProxyClient() {
        return ;
    }
    private final class ProxyClientHandler implements ProxyCallback<ProxyConnection>, Runnable {
        private int tries;
        private final long timeout;
        private final int maxRetryAttempts;
        private final HttpServerExchange exchange;
        private ProxyClient.ProxyTarget target;
        ProxyClientHandler(HttpServerExchange exchangeProxyClient.ProxyTarget targetlong timeoutint maxRetryAttempts) {
            this. = exchange;
            this. = timeout;
            this. = maxRetryAttempts;
            this. = target;
        }
        @Override
        public void run() {
            .getConnection(this, -1, .);
        }
        @Override
        public void completed(final HttpServerExchange exchangefinal ProxyConnection connection) {
            exchange.putAttachment(connection);
            exchange.dispatch(.new ProxyAction(connectionexchange));
        }
        @Override
        public void failed(final HttpServerExchange exchange) {
            final long time = System.currentTimeMillis();
            if (++ < ) {
                if ( > 0 && time > ) {
                    cancel(exchange);
                } else {
                     = .findTarget(exchange);
                    if ( != null) {
                        final long remaining =  > 0 ?  - time : -1;
                        .getConnection(exchangethisremaining.);
                    } else {
                        couldNotResolveBackend(exchange); // The context was registered when we started, so return 503
                    }
                }
            } else {
                couldNotResolveBackend(exchange);
            }
        }
        @Override
        public void queuedRequestFailed(HttpServerExchange exchange) {
            failed(exchange);
        }
        @Override
        public void couldNotResolveBackend(HttpServerExchange exchange) {
            if (exchange.isResponseStarted()) {
                IoUtils.safeClose(exchange.getConnection());
            } else {
                exchange.setResponseCode(503);
                exchange.endExchange();
            }
        }
        void cancel(final HttpServerExchange exchange) {
            final ProxyConnection connectionAttachment = exchange.getAttachment();
            if (connectionAttachment != null) {
                ClientConnection clientConnection = connectionAttachment.getConnection();
                ..timingOutRequest(clientConnection.getPeerAddress() + "" + exchange.getRequestURI());
                IoUtils.safeClose(clientConnection);
            } else {
                ..timingOutRequest(exchange.getRequestURI());
            }
            if (exchange.isResponseStarted()) {
                IoUtils.safeClose(exchange.getConnection());
            } else {
                exchange.setResponseCode(503);
                exchange.endExchange();
            }
        }
    }
    private static class ProxyAction implements Runnable {
        private final ProxyConnection clientConnection;
        private final HttpServerExchange exchange;
        private final Map<HttpStringExchangeAttributerequestHeaders;
        private final boolean rewriteHostHeader;
        private final boolean reuseXForwarded;
        public ProxyAction(final ProxyConnection clientConnectionfinal HttpServerExchange exchangeMap<HttpStringExchangeAttributerequestHeaders,
                           boolean rewriteHostHeaderboolean reuseXForwarded) {
            this. = clientConnection;
            this. = exchange;
            this. = requestHeaders;
            this. = rewriteHostHeader;
            this. = reuseXForwarded;
        }
        @Override
        public void run() {
            final ClientRequest request = new ClientRequest();
            StringBuilder requestURI = new StringBuilder();
            try {
                if (.getRelativePath().isEmpty()) {
                    requestURI.append(encodeUrlPart(.getTargetPath()));
                } else {
                    if (.getTargetPath().endsWith("/")) {
                        requestURI.append(.getTargetPath().substring(0, .getTargetPath().length() - 1));
                        requestURI.append(encodeUrlPart(.getRelativePath()));
                    } else {
                        requestURI = requestURI.append(.getTargetPath());
                        requestURI.append(encodeUrlPart(.getRelativePath()));
                    }
                }
                boolean first = true;
                if (!.getPathParameters().isEmpty()) {
                    requestURI.append(';');
                    for (Map.Entry<StringDeque<String>> entry : .getPathParameters().entrySet()) {
                        if (first) {
                            first = false;
                        } else {
                            requestURI.append('&');
                        }
                        for (String val : entry.getValue()) {
                            requestURI.append(URLEncoder.encode(entry.getKey(), ));
                            requestURI.append('=');
                            requestURI.append(URLEncoder.encode(val));
                        }
                    }
                }
                String qs = .getQueryString();
                if (qs != null && !qs.isEmpty()) {
                    requestURI.append('?');
                    requestURI.append(qs);
                }
            } catch (UnsupportedEncodingException e) {
                //impossible
                .setResponseCode(500);
                .endExchange();
                return;
            }
            request.setPath(requestURI.toString())
                    .setMethod(.getRequestMethod());
            final HeaderMap inboundRequestHeaders = .getRequestHeaders();
            final HeaderMap outboundRequestHeaders = request.getRequestHeaders();
            copyHeaders(outboundRequestHeadersinboundRequestHeaders);
            if (!.isPersistent()) {
                //just because the client side is non-persistent
                //we don't want to close the connection to the backend
                outboundRequestHeaders.put(."keep-alive");
            }
            for (Map.Entry<HttpStringExchangeAttributeentry : .entrySet()) {
                String headerValue = entry.getValue().readAttribute();
                if (headerValue == null || headerValue.isEmpty()) {
                    outboundRequestHeaders.remove(entry.getKey());
                } else {
                    outboundRequestHeaders.put(entry.getKey(), headerValue.replace('\n'' '));
                }
            }
            final SocketAddress address = .getConnection().getPeerAddress();
            final String remoteHost = (address != null && address instanceof InetSocketAddress) ? ((InetSocketAddressaddress).getHostString() : "localhost";
            request.putAttachment(.remoteHost);
            if ( && request.getRequestHeaders().contains(.)) {
                // We have an existing header so we shall simply append the host to the existing list
                final String current = request.getRequestHeaders().getFirst(.);
                if (current == null || current.isEmpty()) {
                    // It was empty so just add it
                    request.getRequestHeaders().put(.remoteHost);
                }
                else {
                    // Add the new entry and reset the existing header
                    request.getRequestHeaders().put(.current + "," + remoteHost);
                }
            }
            else {
                // No existing header or not allowed to reuse the header so set it here
                request.getRequestHeaders().put(.remoteHost);
            }
            // Set the protocol header and attachment
            final String proto = .getRequestScheme().equals("https") ? "https" : "http";
            request.getRequestHeaders().put(.proto);
            request.putAttachment(.proto.equals("https"));
            // Set the server name
            final String hostName = .getHostName();
            request.getRequestHeaders().put(.hostName);
            request.putAttachment(.hostName);
            // Set the port
            int port = .getConnection().getLocalAddress(InetSocketAddress.class).getPort();
            request.getRequestHeaders().put(.port);
            request.putAttachment(.port);
            SSLSessionInfo sslSessionInfo = .getConnection().getSslSessionInfo();
            if (sslSessionInfo != null) {
                X509Certificate[] peerCertificates;
                try {
                    peerCertificates = sslSessionInfo.getPeerCertificateChain();
                    if (peerCertificates.length > 0) {
                        request.putAttachment(., Certificates.toPem(peerCertificates[0]));
                    }
                } catch (SSLPeerUnverifiedException e) {
                    //ignore
                } catch (CertificateEncodingException e) {
                    //ignore
                } catch (RenegotiationRequiredException e) {
                    //ignore
                }
                request.putAttachment(.sslSessionInfo.getCipherSuite());
                request.putAttachment(.sslSessionInfo.getSessionId());
            }
            if() {
                InetSocketAddress targetAddress = .getConnection().getPeerAddress(InetSocketAddress.class);
                request.getRequestHeaders().put(.targetAddress.getHostString() + ":" + targetAddress.getPort());
            }
            .getConnection().sendRequest(requestnew ClientCallback<ClientExchange>() {
                @Override
                public void completed(final ClientExchange result) {
                    result.putAttachment();
                    boolean requiresContinueResponse = HttpContinue.requiresContinueResponse();
                    if (requiresContinueResponse) {
                        result.setContinueHandler(new ContinueNotification() {
                            @Override
                            public void handleContinue(final ClientExchange clientExchange) {
                                HttpContinue.sendContinueResponse(new IoCallback() {
                                    @Override
                                    public void onComplete(final HttpServerExchange exchangefinal Sender sender) {
                                        //don't care
                                    }
                                    @Override
                                    public void onException(final HttpServerExchange exchangefinal Sender senderfinal IOException exception) {
                                        IoUtils.safeClose(.getConnection());
                                    }
                                });
                            }
                        });
                    }
                    result.setResponseListener(new ResponseCallback());
                    final IoExceptionHandler handler = new IoExceptionHandler(.getConnection());
                    if(requiresContinueResponse) {
                        try {
                            if(!result.getRequestChannel().flush()) {
                                result.getRequestChannel().getWriteSetter().set(ChannelListeners.flushingChannelListener(new ChannelListener<StreamSinkChannel>() {
                                    @Override
                                    public void handleEvent(StreamSinkChannel channel) {
                                        ChannelListeners.initiateTransfer(..getRequestChannel(), result.getRequestChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(result), handlerhandler.getConnection().getBufferPool());
                                    }
                                }, handler));
                                result.getRequestChannel().resumeWrites();
                                return;
                            }
                        } catch (IOException e) {
                            handler.handleException(result.getRequestChannel(), e);
                        }
                    }
                    ChannelListeners.initiateTransfer(..getRequestChannel(), result.getRequestChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(result), handlerhandler.getConnection().getBufferPool());
                }
                @Override
                public void failed(IOException e) {
                    ..proxyRequestFailed(.getRequestURI(), e);
                    if (!.isResponseStarted()) {
                        .setResponseCode(503);
                        .endExchange();
                    } else {
                        IoUtils.safeClose(.getConnection());
                    }
                }
            });
        }
    }
    private static final class ResponseCallback implements ClientCallback<ClientExchange> {
        private final HttpServerExchange exchange;
        private ResponseCallback(HttpServerExchange exchange) {
            this. = exchange;
        }
        @Override
        public void completed(final ClientExchange result) {
            HttpServerExchange exchange = result.getAttachment();
            final ClientResponse response = result.getResponse();
            final HeaderMap inboundResponseHeaders = response.getResponseHeaders();
            final HeaderMap outboundResponseHeaders = exchange.getResponseHeaders();
            exchange.setResponseCode(response.getResponseCode());
            copyHeaders(outboundResponseHeadersinboundResponseHeaders);
            if (exchange.isUpgrade()) {
                exchange.upgradeChannel(new HttpUpgradeListener() {
                    @Override
                    public void handleUpgrade(StreamConnection streamConnectionHttpServerExchange exchange) {
                        StreamConnection clientChannel = null;
                        try {
                            clientChannel = result.getConnection().performUpgrade();
                            ChannelListeners.initiateTransfer(.clientChannel.getSourceChannel(), streamConnection.getSinkChannel(), ChannelListeners.closingChannelListener(), ChannelListeners.<StreamSinkChannel>writeShutdownChannelListener(ChannelListeners.<StreamSinkChannel>flushingChannelListener(ChannelListeners.closingChannelListener(), ChannelListeners.closingChannelExceptionHandler()), ChannelListeners.closingChannelExceptionHandler()), ChannelListeners.closingChannelExceptionHandler(), ChannelListeners.closingChannelExceptionHandler(), result.getConnection().getBufferPool());
                            ChannelListeners.initiateTransfer(.streamConnection.getSourceChannel(), clientChannel.getSinkChannel(), ChannelListeners.closingChannelListener(), ChannelListeners.<StreamSinkChannel>writeShutdownChannelListener(ChannelListeners.<StreamSinkChannel>flushingChannelListener(ChannelListeners.closingChannelListener(), ChannelListeners.closingChannelExceptionHandler()), ChannelListeners.closingChannelExceptionHandler()), ChannelListeners.closingChannelExceptionHandler(), ChannelListeners.closingChannelExceptionHandler(), result.getConnection().getBufferPool());
                        } catch (IOException e) {
                            IoUtils.safeClose(streamConnectionclientChannel);
                        }
                    }
                });
            }
            IoExceptionHandler handler = new IoExceptionHandler(exchangeresult.getConnection());
            ChannelListeners.initiateTransfer(.result.getResponseChannel(), exchange.getResponseChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(resultexchange), handlerhandlerexchange.getConnection().getBufferPool());
        }
        @Override
        public void failed(IOException e) {
            if (!.isResponseStarted()) {
                .setResponseCode(500);
                .endExchange();
            } else {
                IoUtils.safeClose(.getConnection());
            }
        }
    }
    private static final class HTTPTrailerChannelListener implements ChannelListener<StreamSinkChannel> {
        private final Attachable source;
        private final Attachable target;
        private HTTPTrailerChannelListener(final Attachable sourcefinal Attachable target) {
            this. = source;
            this. = target;
        }
        @Override
        public void handleEvent(final StreamSinkChannel channel) {
            HeaderMap trailers = .getAttachment(.);
            if (trailers != null) {
                .putAttachment(.trailers);
            }
            try {
                channel.shutdownWrites();
                if (!channel.flush()) {
                    channel.getWriteSetter().set(ChannelListeners.<StreamSinkChannel>flushingChannelListener(new ChannelListener<StreamSinkChannel>() {
                        @Override
                        public void handleEvent(StreamSinkChannel channel) {
                            channel.suspendWrites();
                            channel.getWriteSetter().set(null);
                        }
                    }, ChannelListeners.closingChannelExceptionHandler()));
                    channel.resumeWrites();
                } else {
                    channel.getWriteSetter().set(null);
                    channel.shutdownWrites();
                }
            } catch (IOException e) {
                ..ioException(e);
                IoUtils.safeClose(channel);
            }
        }
    }
    private static final class IoExceptionHandler implements ChannelExceptionHandler<Channel> {
        private final HttpServerExchange exchange;
        private final ClientConnection clientConnection;
        private IoExceptionHandler(HttpServerExchange exchangeClientConnection clientConnection) {
            this. = exchange;
            this. = clientConnection;
        }
        @Override
        public void handleException(Channel channelIOException exception) {
            IoUtils.safeClose(channel);
            if (.isResponseStarted()) {
                IoUtils.safeClose();
                ..debug("Exception reading from target server"exception);
                if (!.isResponseStarted()) {
                    .setResponseCode(500);
                    .endExchange();
                } else {
                    IoUtils.safeClose(.getConnection());
                }
            } else {
                .setResponseCode(500);
                .endExchange();
            }
        }
    }

    
perform URL encoding

TODO: this whole thing is kinda crappy.

Returns:
    private static String encodeUrlPart(final String partthrows UnsupportedEncodingException {
        //we need to go through and check part by part that a section does not need encoding
        int pos = 0;
        for (int i = 0; i < part.length(); ++i) {
            char c = part.charAt(i);
            if (c == '/') {
                if (pos != i) {
                    String original = part.substring(posi - 1);
                    String encoded = URLEncoder.encode(original);
                    if (!encoded.equals(original)) {
                        return realEncode(partpos);
                    }
                }
                pos = i + 1;
            } else if (c == ' ') {
                return realEncode(partpos);
            }
        }
        if (pos != part.length()) {
            String original = part.substring(pos);
            String encoded = URLEncoder.encode(original);
            if (!encoded.equals(original)) {
                return realEncode(partpos);
            }
        }
        return part;
    }
    private static String realEncode(String partint startPosthrows UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        sb.append(part.substring(0, startPos));
        int pos = startPos;
        for (int i = startPosi < part.length(); ++i) {
            char c = part.charAt(i);
            if (c == '/') {
                if (pos != i) {
                    String original = part.substring(posi - 1);
                    String encoded = URLEncoder.encode(original);
                    sb.append(encoded);
                    sb.append('/');
                    pos = i + 1;
                }
            }
        }
        String original = part.substring(pos);
        String encoded = URLEncoder.encode(original);
        sb.append(encoded);
        return sb.toString();
    }
    public static class Builder implements HandlerBuilder {
        @Override
        public String name() {
            return "reverse-proxy";
        }
        @Override
        public Map<StringClass<?>> parameters() {
            return Collections.<StringClass<?>>singletonMap("hosts"String[].class);
        }
        @Override
        public Set<StringrequiredParameters() {
            return Collections.singleton("hosts");
        }
        @Override
        public String defaultParameter() {
            return "hosts";
        }
        @Override
        public HandlerWrapper build(Map<StringObjectconfig) {
            String[] hosts = (String[]) config.get("hosts");
            List<URIuris = new ArrayList<>();
            for(String host : hosts) {
                try {
                    uris.add(new URI(host));
                } catch (URISyntaxException e) {
                    throw new RuntimeException(e);
                }
            }
            return new Wrapper(uris);
        }
    }
    private static class Wrapper implements HandlerWrapper {
        private final List<URIuris;
        private Wrapper(List<URIuris) {
            this. = uris;
        }
        @Override
        public HttpHandler wrap(HttpHandler handler) {
            LoadBalancingProxyClient loadBalancingProxyClient = new LoadBalancingProxyClient();
            for(URI url : ) {
                loadBalancingProxyClient.addHost(url);
            }
            return new ProxyHandler(loadBalancingProxyClienthandler);
        }
    }
New to GrepCode? Check out our FAQ X