All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.httpproxy.impl.ReverseProxy Maven / Gradle / Ivy

There is a newer version: 5.0.0.CR3
Show newest version
/*
 * Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
 */
package io.vertx.httpproxy.impl;

import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.*;
import io.vertx.core.internal.logging.Logger;
import io.vertx.core.internal.logging.LoggerFactory;
import io.vertx.core.net.NetSocket;
import io.vertx.core.streams.ReadStream;
import io.vertx.httpproxy.*;
import io.vertx.httpproxy.cache.CacheOptions;
import io.vertx.httpproxy.spi.cache.Cache;

import java.util.*;
import java.util.function.BiFunction;

import static io.vertx.core.http.HttpHeaders.CONNECTION;
import static io.vertx.core.http.HttpHeaders.UPGRADE;

public class ReverseProxy implements HttpProxy {

  private final static Logger log = LoggerFactory.getLogger(ReverseProxy.class);
  private final HttpClient client;
  private final boolean supportWebSocket;
  private BiFunction> selector = (req, client) -> Future.failedFuture("No origin available");
  private final List interceptors = new ArrayList<>();

  public ReverseProxy(ProxyOptions options, HttpClient client) {
    CacheOptions cacheOptions = options.getCacheOptions();
    if (cacheOptions != null) {
      Cache cache = cacheOptions.newCache();
      addInterceptor(new CachingFilter(cache));
    }
    this.client = client;
    this.supportWebSocket = options.getSupportWebSocket();
  }

  @Override
  public HttpProxy originRequestProvider(BiFunction> provider) {
    selector = provider;
    return this;
  }

  @Override
  public HttpProxy addInterceptor(ProxyInterceptor interceptor, boolean supportsWebSocketUpgrade) {
    interceptors.add(new ProxyInterceptorEntry(Objects.requireNonNull(interceptor), supportsWebSocketUpgrade));
    return this;
  }

  @Override
  public void handle(HttpServerRequest request) {
    ProxyRequest proxyRequest = ProxyRequest.reverseProxy(request);

    // Encoding sanity check
    Boolean chunked = HttpUtils.isChunked(request.headers());
    if (chunked == null) {
      end(proxyRequest, 400);
      return;
    }

    boolean isWebSocket = supportWebSocket && request.canUpgradeToWebSocket();
    Proxy proxy = new Proxy(proxyRequest, isWebSocket);
    proxy.sendRequest()
      .recover(throwable -> {
        log.trace("Error in sending the request", throwable);
        return Future.succeededFuture(proxyRequest.release().response().setStatusCode(502));
      })
      .compose(proxy::sendProxyResponse)
      .recover(throwable -> {
        log.trace("Error in sending the response", throwable);
        return proxy.response().release().setStatusCode(502).send();
      });
  }

  private void end(ProxyRequest proxyRequest, int sc) {
    proxyRequest
      .response()
      .release()
      .setStatusCode(sc)
      .putHeader(HttpHeaders.CONTENT_LENGTH, "0")
      .setBody(null)
      .send();
  }

  private Future resolveOrigin(HttpServerRequest proxiedRequest) {
    return selector.apply(proxiedRequest, client);
  }

  private class Proxy implements ProxyContext {

    private final ProxyRequest request;
    private ProxyResponse response;
    private final Map attachments = new HashMap<>();
    private final ListIterator filters;
    private final boolean isWebSocket;

    private Proxy(ProxyRequest request, boolean isWebSocket) {
      this.request = request;
      this.isWebSocket = isWebSocket;
      this.filters = interceptors.listIterator();
    }

    @Override
    public boolean isWebSocket() {
      return isWebSocket;
    }

    @Override
    public void set(String name, Object value) {
      attachments.put(name, value);
    }

    @Override
    public  T get(String name, Class type) {
      Object o = attachments.get(name);
      return type.isInstance(o) ? type.cast(o) : null;
    }

    @Override
    public ProxyRequest request() {
      return request;
    }

    @Override
    public Future sendRequest() {
      if (filters.hasNext()) {
        ProxyInterceptorEntry next = filters.next();
        if (isWebSocket && !next.supportsWebSocketUpgrade) {
          return sendRequest();
        }
        return next.interceptor.handleProxyRequest(this);
      } else {
        if (isWebSocket) {
          HttpServerRequest proxiedRequest = request().proxiedRequest();
          return resolveOrigin(proxiedRequest).compose(request -> {
            request.setMethod(request().getMethod());
            request.setURI(request().getURI());
            // Firefox is known to send an unexpected connection header value
            // Connection=keep-alive, Upgrade
            // It leads to a failure in websocket proxying
            // So we make sure the standard value is sent to the backend
            request.headers().addAll(request().headers()).set(CONNECTION, UPGRADE);
            Future responseFuture = request.connect();
            ReadStream readStream = request().getBody().stream();
            readStream.handler(request::write);
            readStream.resume();
            proxiedRequest.resume();
            return responseFuture;
          }).map(response -> new ProxiedResponse((ProxiedRequest) request(), request().proxiedRequest().response(), response));
        }
        return sendProxyRequest(request);
      }
    }

    @Override
    public ProxyResponse response() {
      return response;
    }

    @Override
    public Future sendResponse() {
      if (filters.hasPrevious()) {
        ProxyInterceptorEntry previous = filters.previous();
        if (isWebSocket && !previous.supportsWebSocketUpgrade) {
          return sendResponse();
        }
        return previous.interceptor.handleProxyResponse(this);
      } else {
        if (isWebSocket) {
          HttpClientResponse proxiedResponse = response().proxiedResponse();
          if (response.getStatusCode() == 101) {
            HttpServerResponse clientResponse = request().proxiedRequest().response();
            clientResponse.setStatusCode(101);
            clientResponse.headers().addAll(response.headers());
            Future otherso = request.proxiedRequest().toNetSocket();
            otherso.onComplete(ar3 -> {
              if (ar3.succeeded()) {
                NetSocket responseSocket = ar3.result();
                NetSocket proxyResponseSocket = proxiedResponse.netSocket();
                responseSocket.handler(proxyResponseSocket::write);
                proxyResponseSocket.handler(responseSocket::write);
                responseSocket.closeHandler(v -> proxyResponseSocket.close());
                proxyResponseSocket.closeHandler(v -> responseSocket.close());
              } else {
                // Find reproducer
                System.err.println("Handle this case");
                ar3.cause().printStackTrace();
              }
            });
          } else {
            request().proxiedRequest().resume();
            end(request(), proxiedResponse.statusCode());
          }
          return Future.succeededFuture();
        }
        return response.send();
      }
    }

    private Future sendProxyRequest(ProxyRequest proxyRequest) {
      return resolveOrigin(proxyRequest.proxiedRequest()).compose(proxyRequest::send);
    }

    private Future sendProxyResponse(ProxyResponse response) {

      this.response = response;

      // Check validity
      Boolean chunked = HttpUtils.isChunked(response.headers());
      if (chunked == null) {
        // response.request().release(); // Is it needed ???
        end(response.request(), 501);
        return Future.succeededFuture(); // should use END future here ???
      }

      return sendResponse();
    }
  }

  private static class ProxyInterceptorEntry {

    final ProxyInterceptor interceptor;
    final boolean supportsWebSocketUpgrade;

    ProxyInterceptorEntry(ProxyInterceptor interceptor, boolean supportsWebSocketUpgrade) {
      this.interceptor = interceptor;
      this.supportsWebSocketUpgrade = supportsWebSocketUpgrade;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy