io.vertx.httpproxy.impl.ReverseProxy Maven / Gradle / Ivy
/**
* XXX 复制4.5.7版本的io.vertx.httpproxy.impl.ReverseProxy类的代码,让websocket也支持代理拦截器
* 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 java.util.*;
import java.util.function.BiFunction;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.*;
import io.vertx.core.net.NetSocket;
import io.vertx.httpproxy.*;
import io.vertx.httpproxy.cache.CacheOptions;
import io.vertx.httpproxy.spi.cache.Cache;
public class ReverseProxy implements HttpProxy {
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) {
interceptors.add(interceptor);
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;
}
// WebSocket upgrade tunneling
if (supportWebSocket && io.vertx.core.http.impl.HttpUtils.canUpgradeToWebSocket(request)) {
handleWebSocketUpgrade(proxyRequest);
return;
}
Proxy proxy = new Proxy(proxyRequest);
proxy.filters = interceptors.listIterator();
proxy.sendRequest().compose(proxy::sendProxyResponse);
}
private void handleWebSocketUpgrade(ProxyRequest proxyRequest) {
// XXX 添加调用拦截器修改代理请求
interceptors.forEach(proxyInterceptor -> proxyInterceptor.modifyProxyRequest(proxyRequest));
HttpServerRequest proxiedRequest = proxyRequest.proxiedRequest();
resolveOrigin(proxiedRequest).onComplete(ar -> {
if (ar.succeeded()) {
HttpClientRequest request = ar.result();
request.setMethod(HttpMethod.GET);
// XXX 过滤器可能要改变目标请求的uri,所以这里用代理请求的URI,而不是用被代理请求的URI
// request.setURI(proxiedRequest.uri());
request.setURI(proxyRequest.getURI());
request.headers().addAll(proxiedRequest.headers());
Future fut2 = request.connect();
proxiedRequest.handler(request::write);
proxiedRequest.endHandler(v -> request.end());
proxiedRequest.resume();
fut2.onComplete(ar2 -> {
if (ar2.succeeded()) {
HttpClientResponse proxiedResponse = ar2.result();
if (proxiedResponse.statusCode() == 101) {
HttpServerResponse response = proxiedRequest.response();
response.setStatusCode(101);
response.headers().addAll(proxiedResponse.headers());
Future otherso = 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 {
// Rejection
proxiedRequest.resume();
end(proxyRequest, proxiedResponse.statusCode());
}
} else {
proxiedRequest.resume();
end(proxyRequest, 502);
}
});
} else {
proxiedRequest.resume();
end(proxyRequest, 502);
}
});
}
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 ListIterator filters;
private Proxy(ProxyRequest request) {
this.request = request;
}
@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()) {
ProxyInterceptor next = filters.next();
return next.handleProxyRequest(this);
} else {
return sendProxyRequest(request);
}
}
@Override
public ProxyResponse response() {
return response;
}
@Override
public Future sendResponse() {
if (filters.hasPrevious()) {
ProxyInterceptor filter = filters.previous();
return filter.handleProxyResponse(this);
} else {
return response.send();
}
}
private Future sendProxyRequest(ProxyRequest proxyRequest) {
Future f = resolveOrigin(proxyRequest.proxiedRequest());
f.onFailure(err -> {
// Should this be done here ? I don't think so
HttpServerRequest proxiedRequest = proxyRequest.proxiedRequest();
proxiedRequest.resume();
Promise promise = Promise.promise();
proxiedRequest.exceptionHandler(promise::tryFail);
proxiedRequest.endHandler(promise::tryComplete);
promise.future().onComplete(ar2 -> {
end(proxyRequest, 502);
});
});
return f.compose(a -> sendProxyRequest(proxyRequest, a));
}
private Future sendProxyRequest(ProxyRequest proxyRequest, HttpClientRequest request) {
Future fut = proxyRequest.send(request);
fut.onFailure(err -> {
proxyRequest.proxiedRequest().response().setStatusCode(502).end();
});
return fut;
}
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();
}
}
}