io.vertx.httpproxy.impl.HttpProxyImpl Maven / Gradle / Ivy
/*
* 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.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.http.RequestOptions;
import io.vertx.core.net.SocketAddress;
import io.vertx.httpproxy.Body;
import io.vertx.httpproxy.HttpProxy;
import io.vertx.httpproxy.ProxyRequest;
import io.vertx.httpproxy.ProxyResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
public class HttpProxyImpl implements HttpProxy {
private static final BiFunction CACHE_GET_AND_VALIDATE = (key, resource) -> {
long now = System.currentTimeMillis();
long val = resource.timestamp + resource.maxAge;
return val < now ? null : resource;
};
private final HttpClient client;
private Function> selector = req -> Future.failedFuture("No origin available");
private final Map cache = new HashMap<>();
public HttpProxyImpl(HttpClient client) {
this.client = client;
}
@Override
public HttpProxy originSelector(Function> selector) {
this.selector = selector;
return this;
}
@Override
public void handle(HttpServerRequest outboundRequest) {
handleProxyRequest(outboundRequest);
}
private Future resolveOrigin(HttpServerRequest outboundRequest) {
return selector.apply(outboundRequest).flatMap(server -> {
RequestOptions requestOptions = new RequestOptions();
requestOptions.setServer(server);
return client.request(requestOptions);
});
}
boolean revalidateResource(ProxyResponse response, Resource resource) {
if (resource.etag != null && response.etag() != null) {
return resource.etag.equals(response.etag());
}
return true;
}
private void end(ProxyRequest proxyRequest, int sc) {
proxyRequest
.response()
.release()
.setStatusCode(sc)
.putHeader(HttpHeaders.CONTENT_LENGTH, "0")
.setBody(null)
.send();
}
private void handleProxyRequest(HttpServerRequest outboundRequest) {
ProxyRequestImpl proxyRequest = (ProxyRequestImpl) ProxyRequest.reverseProxy(outboundRequest);
// Encoding check
Boolean chunked = HttpUtils.isChunked(outboundRequest.headers());
if (chunked == null) {
end(proxyRequest, 400);
return;
}
// Handle from cache
HttpMethod method = outboundRequest.method();
if (method == HttpMethod.GET || method == HttpMethod.HEAD) {
String cacheKey = proxyRequest.absoluteURI();
Resource resource = cache.computeIfPresent(cacheKey, CACHE_GET_AND_VALIDATE);
if (resource != null) {
if (tryHandleProxyRequestFromCache(proxyRequest, resource)) {
return;
}
}
}
handleProxyRequestAndProxyResponse(proxyRequest);
}
private void handleProxyRequestAndProxyResponse(ProxyRequest proxyRequest) {
handleProxyRequest(proxyRequest, ar -> {
if (ar.succeeded()) {
handleProxyResponse(ar.result(), ar2 -> {});
} else {
// TODO ???
}
});
}
private void handleProxyRequest(ProxyRequest proxyRequest, Handler> handler) {
Future f = resolveOrigin(proxyRequest.outboundRequest());
f.onComplete(ar -> {
if (ar.succeeded()) {
handleProxyRequest(proxyRequest, ar.result(), handler);
} else {
HttpServerRequest outboundRequest = proxyRequest.outboundRequest();
outboundRequest.resume();
Promise promise = Promise.promise();
outboundRequest.exceptionHandler(promise::tryFail);
outboundRequest.endHandler(promise::tryComplete);
promise.future().onComplete(ar2 -> {
end(proxyRequest, 502);
});
handler.handle(Future.failedFuture(ar.cause()));
}
});
}
private void handleProxyRequest(ProxyRequest proxyRequest, HttpClientRequest inboundRequest, Handler> handler) {
((ProxyRequestImpl)proxyRequest).send(inboundRequest, ar2 -> {
if (ar2.succeeded()) {
handler.handle(ar2);
} else {
proxyRequest.outboundRequest().response().setStatusCode(502).end();
handler.handle(Future.failedFuture(ar2.cause()));
}
});
}
private void handleProxyResponse(ProxyResponse response, Handler> completionHandler) {
// Check validity
Boolean chunked = HttpUtils.isChunked(response.headers());
if (chunked == null) {
// response.request().release(); // Is it needed ???
end(response.request(), 501);
completionHandler.handle(Future.failedFuture("TODO"));
return;
}
if (chunked && response.request().version() == HttpVersion.HTTP_1_0) {
String contentLength = response.headers().get(HttpHeaders.CONTENT_LENGTH);
if (contentLength == null) {
// Special handling for HTTP 1.0 clients that cannot handle chunked encoding
Body body = response.getBody();
response.release();
BufferingWriteStream buffer = new BufferingWriteStream();
body.stream().pipeTo(buffer, ar -> {
if (ar.succeeded()) {
Buffer content = buffer.content();
response.setBody(Body.body(content));
continueHandleResponse(response, completionHandler);
} else {
System.out.println("Not implemented");
}
});
return;
}
}
continueHandleResponse(response, completionHandler);
}
private void continueHandleResponse(ProxyResponse response, Handler> completionHandler) {
ProxyRequest request = response.request();
Handler> handler;
if (response.publicCacheControl() && response.maxAge() > 0) {
if (request.getMethod() == HttpMethod.GET) {
String absoluteUri = request.absoluteURI();
Resource res = new Resource(
absoluteUri,
response.getStatusCode(),
response.getStatusMessage(),
response.headers(),
System.currentTimeMillis(),
response.maxAge());
response.bodyFilter(s -> new BufferingReadStream(s, res.content));
handler = ar3 -> {
completionHandler.handle(ar3);
if (ar3.succeeded()) {
cache.put(absoluteUri, res);
}
};
} else {
if (request.getMethod() == HttpMethod.HEAD) {
Resource resource = cache.get(request.absoluteURI());
if (resource != null) {
if (!revalidateResource(response, resource)) {
// Invalidate cache
cache.remove(request.absoluteURI());
}
}
}
handler = completionHandler;
}
} else {
handler = completionHandler;
}
((ProxyResponseImpl)response).send(handler);
}
private boolean tryHandleProxyRequestFromCache(ProxyRequestImpl proxyRequest, Resource resource) {
HttpServerRequest outboundRequest = proxyRequest.outboundRequest();
String cacheControlHeader = outboundRequest.getHeader(HttpHeaders.CACHE_CONTROL);
if (cacheControlHeader != null) {
CacheControl cacheControl = new CacheControl().parse(cacheControlHeader);
if (cacheControl.maxAge() >= 0) {
long now = System.currentTimeMillis();
long currentAge = now - resource.timestamp;
if (currentAge > cacheControl.maxAge() * 1000) {
String etag = resource.headers.get(HttpHeaders.ETAG);
if (etag != null) {
proxyRequest.headers().set(HttpHeaders.IF_NONE_MATCH, resource.etag);
handleProxyRequest(proxyRequest, ar -> {
if (ar.succeeded()) {
ProxyResponse proxyResp = ar.result();
int sc = proxyResp.getStatusCode();
switch (sc) {
case 200:
handleProxyResponse(proxyResp, ar2 -> {});
break;
case 304:
// Warning: this relies on the fact that HttpServerRequest will not send a body for HEAD
proxyResp.release();
resource.sendTo(proxyRequest.response());
break;
default:
System.out.println("Not implemented");
break;
}
} else {
System.out.println("Not implemented");
}
});
return true;
} else {
return false;
}
}
}
}
//
String ifModifiedSinceHeader = outboundRequest.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
if ((outboundRequest.method() == HttpMethod.GET || outboundRequest.method() == HttpMethod.HEAD) && ifModifiedSinceHeader != null && resource.lastModified != null) {
Date ifModifiedSince = ParseUtils.parseHeaderDate(ifModifiedSinceHeader);
if (resource.lastModified.getTime() <= ifModifiedSince.getTime()) {
outboundRequest.response().setStatusCode(304).end();
return true;
}
}
resource.sendTo(proxyRequest.response());
return true;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy