handleProxyRequest(ProxyContext context) {
+ ProxyRequest proxyRequest = context.request();
+ proxyRequest.setAuthority(HostAndPort.create("example.com", 80));
+ return ProxyInterceptor.super.handleProxyRequest(context);
+ }
+ });
+ }
+
+ public void cacheConfig(Vertx vertx, HttpClient proxyClient) {
+ HttpProxy proxy = HttpProxy.reverseProxy(new ProxyOptions().setCacheOptions(new CacheOptions()), proxyClient);
+ }
+}
diff --git a/sf-vertx/src/main/java/examples/package-info.java b/sf-vertx/src/main/java/examples/package-info.java
new file mode 100644
index 0000000..fd465db
--- /dev/null
+++ b/sf-vertx/src/main/java/examples/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * @author Emad Alblueshi
+ */
+@Source
+package examples;
+import io.vertx.docgen.Source;
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/Body.java b/sf-vertx/src/main/java/io/vertx/httpproxy/Body.java
new file mode 100644
index 0000000..18cd882
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/Body.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+import io.vertx.codegen.annotations.VertxGen;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.streams.ReadStream;
+import io.vertx.httpproxy.impl.BufferedReadStream;
+
+/**
+ * Handles the HTTP proxy body.
+ *
+ * @author Julien Viet
+ */
+@VertxGen
+public interface Body {
+
+ /**
+ * Create a new {@code Body} instance.
+ *
+ * @param stream the {@code ReadStream} of the body
+ * @param len the determined length of the body
+ * @return a reference to this, so the API can be used fluently
+ */
+ static Body body(ReadStream stream, long len) {
+ return new Body() {
+ @Override
+ public long length() {
+ return len;
+ }
+ @Override
+ public ReadStream stream() {
+ return stream;
+ }
+ };
+ }
+
+ /**
+ * Create a new {@code Body} instance.
+ *
+ * @param stream the {@link ReadStream} of the body
+ * @return a reference to this, so the API can be used fluently
+ */
+ static Body body(ReadStream stream) {
+ return body(stream, -1L);
+ }
+
+ /**
+ * Create a new {@code Body} instance.
+ *
+ * @param buffer the {@link Buffer} of the body
+ * @return a reference to this, so the API can be used fluently
+ */
+ static Body body(Buffer buffer) {
+ return new Body() {
+ @Override
+ public long length() {
+ return buffer.length();
+ }
+ @Override
+ public ReadStream stream() {
+ return new BufferedReadStream(buffer);
+ }
+ };
+ }
+
+ /**
+ *
+ * Get length of the {@code Body}.
+ *
+ * @return the body length or {@code -1} if that can't be determined
+ */
+ long length();
+
+ /**
+ *
+ * Get stream of the {@code Body}.
+ *
+ * @return the body stream
+ */
+ ReadStream stream();
+
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/HttpProxy.java b/sf-vertx/src/main/java/io/vertx/httpproxy/HttpProxy.java
new file mode 100644
index 0000000..1c1b2a7
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/HttpProxy.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+import io.vertx.codegen.annotations.Fluent;
+import io.vertx.codegen.annotations.GenIgnore;
+import io.vertx.codegen.annotations.VertxGen;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.core.http.HttpClient;
+import io.vertx.core.http.HttpClientRequest;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.http.RequestOptions;
+import io.vertx.core.net.SocketAddress;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.httpproxy.impl.ReverseProxy;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * Handles the HTTP reverse proxy logic between the user agent and the origin.
+ *
+ * @author Julien Viet
+ */
+@VertxGen
+public interface HttpProxy extends Handler {
+
+ /**
+ * Create a new {@code HttpProxy} instance.
+ *
+ * @param client the {@code HttpClient} that forwards outbound requests to the origin.
+ * @return a reference to this, so the API can be used fluently.
+ */
+ static HttpProxy reverseProxy(HttpClient client) {
+ return new ReverseProxy(new ProxyOptions(), client);
+ }
+
+ /**
+ * Create a new {@code HttpProxy} instance.
+ *
+ * @param client the {@code HttpClient} that forwards outbound requests to the origin.
+ * @return a reference to this, so the API can be used fluently.
+ */
+ static HttpProxy reverseProxy(ProxyOptions options, HttpClient client) {
+ return new ReverseProxy(options, client);
+ }
+
+ /**
+ * Set the {@code SocketAddress} of the origin.
+ *
+ * @param address the {@code SocketAddress} of the origin
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ default HttpProxy origin(SocketAddress address) {
+ return originSelector(req -> Future.succeededFuture(address));
+ }
+
+ /**
+ * Set the host name and port number of the origin.
+ *
+ * @param port the port number of the origin server
+ * @param host the host name of the origin server
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ default HttpProxy origin(int port, String host) {
+ return origin(SocketAddress.inetSocketAddress(port, host));
+ }
+
+ /**
+ * Set a selector that resolves the origin address based on the incoming HTTP request.
+ *
+ * @param selector the selector
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ default HttpProxy originSelector(Function> selector) {
+ return originRequestProvider((req, client) -> selector
+ .apply(req)
+ .flatMap(server -> client.request(new RequestOptions().setServer(server))));
+ }
+
+ /**
+ * Set a provider that creates the request to the origin server based the incoming HTTP request.
+ * Setting a provider overrides any origin selector previously set.
+ *
+ * @param provider the provider
+ * @return a reference to this, so the API can be used fluently
+ */
+ @GenIgnore()
+ @Fluent
+ HttpProxy originRequestProvider(BiFunction> provider);
+
+ /**
+ * Add an interceptor to the interceptor chain.
+ *
+ * @param interceptor
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ HttpProxy addInterceptor(ProxyInterceptor interceptor);
+
+ /**
+ * Handle the outbound {@code HttpServerRequest}.
+ *
+ * @param request the outbound {@code HttpServerRequest}
+ */
+ void handle(HttpServerRequest request);
+
+ void handle(RoutingContext ctx, WebClient mainWebClient);
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyContext.java b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyContext.java
new file mode 100644
index 0000000..8853325
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyContext.java
@@ -0,0 +1,48 @@
+package io.vertx.httpproxy;
+
+import io.vertx.codegen.annotations.VertxGen;
+import io.vertx.core.Future;
+
+/**
+ * A controller for proxy interception.
+ */
+@VertxGen
+public interface ProxyContext {
+
+ /**
+ * @return the proxy request
+ */
+ ProxyRequest request();
+
+ /**
+ * @return the proxy response, it might be {@code null} if the response has not been sent
+ */
+ ProxyResponse response();
+
+ /**
+ *
+ */
+ Future sendRequest();
+
+ /**
+ *
+ */
+ Future sendResponse();
+
+ /**
+ * Attach a payload to the context
+ *
+ * @param name the payload name
+ * @param value any payload value
+ */
+ void set(String name, Object value);
+
+ /**
+ * Get a payload attached to this context
+ *
+ * @param name the payload name
+ * @param type the expected payload type
+ * @return the attached payload
+ */
+ T get(String name, Class type);
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyInterceptor.java b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyInterceptor.java
new file mode 100644
index 0000000..18ad8a3
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyInterceptor.java
@@ -0,0 +1,31 @@
+package io.vertx.httpproxy;
+
+import io.vertx.codegen.annotations.VertxGen;
+import io.vertx.core.Future;
+
+/**
+ * A {@link HttpProxy} interceptor.
+ */
+@VertxGen(concrete = false)
+public interface ProxyInterceptor {
+
+ /**
+ * Handle the proxy request at the stage of this interceptor.
+ *
+ * @param context the proxy context
+ * @return when the request has actually been sent to the origin
+ */
+ default Future handleProxyRequest(ProxyContext context) {
+ return context.sendRequest();
+ }
+
+ /**
+ * Handle the proxy response at the stage of this interceptor.
+ *
+ * @param context the proxy context
+ * @return when the response has actually been sent to the user-agent
+ */
+ default Future handleProxyResponse(ProxyContext context) {
+ return context.sendResponse();
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyOptions.java b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyOptions.java
new file mode 100644
index 0000000..bae6bed
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyOptions.java
@@ -0,0 +1,79 @@
+package io.vertx.httpproxy;
+
+import io.vertx.codegen.annotations.DataObject;
+import io.vertx.codegen.json.annotations.JsonGen;
+import io.vertx.core.json.JsonObject;
+import io.vertx.httpproxy.cache.CacheOptions;
+
+/**
+ * Proxy options.
+ */
+@DataObject
+@JsonGen(publicConverter = false)
+public class ProxyOptions {
+
+ /**
+ * Enable WebSocket support : {@code true}
+ */
+ public static final boolean DEFAULT_SUPPORT_WEBSOCKET = true;
+
+ private CacheOptions cacheOptions;
+ private boolean supportWebSocket;
+
+ public ProxyOptions(JsonObject json) {
+ ProxyOptionsConverter.fromJson(json, this);
+ }
+
+ public ProxyOptions() {
+ supportWebSocket = DEFAULT_SUPPORT_WEBSOCKET;
+ }
+
+ /**
+ * @return the cache options
+ */
+ public CacheOptions getCacheOptions() {
+ return cacheOptions;
+ }
+
+ /**
+ * Set the cache options that configures the proxy.
+ *
+ * {@code null} cache options disables caching, by default cache is disabled.
+ *
+ * @param cacheOptions the cache options
+ * @return a reference to this, so the API can be used fluently
+ */
+ public ProxyOptions setCacheOptions(CacheOptions cacheOptions) {
+ this.cacheOptions = cacheOptions;
+ return this;
+ }
+
+ /**
+ * @return whether WebSocket are supported
+ */
+ public boolean getSupportWebSocket() {
+ return supportWebSocket;
+ }
+
+ /**
+ * Set whether WebSocket are supported.
+ *
+ * @param supportWebSocket {@code true} to enable WebSocket support, {@code false} otherwise
+ * @return a reference to this, so the API can be used fluently
+ */
+ public ProxyOptions setSupportWebSocket(boolean supportWebSocket) {
+ this.supportWebSocket = supportWebSocket;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return toJson().toString();
+ }
+
+ public JsonObject toJson() {
+ JsonObject json = new JsonObject();
+ ProxyOptionsConverter.toJson(this, json);
+ return json;
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyOptionsConverter.java b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyOptionsConverter.java
new file mode 100644
index 0000000..79e9c79
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyOptionsConverter.java
@@ -0,0 +1,47 @@
+package io.vertx.httpproxy;
+
+import io.vertx.core.json.JsonObject;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.impl.JsonUtil;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.util.Base64;
+
+/**
+ * Converter and mapper for {@link io.vertx.httpproxy.ProxyOptions}.
+ * NOTE: This class has been automatically generated from the {@link io.vertx.httpproxy.ProxyOptions} original class using Vert.x codegen.
+ */
+public class ProxyOptionsConverter {
+
+
+ private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER;
+ private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER;
+
+ static void fromJson(Iterable> json, ProxyOptions obj) {
+ for (java.util.Map.Entry member : json) {
+ switch (member.getKey()) {
+ case "cacheOptions":
+ if (member.getValue() instanceof JsonObject) {
+ obj.setCacheOptions(new io.vertx.httpproxy.cache.CacheOptions((io.vertx.core.json.JsonObject)member.getValue()));
+ }
+ break;
+ case "supportWebSocket":
+ if (member.getValue() instanceof Boolean) {
+ obj.setSupportWebSocket((Boolean)member.getValue());
+ }
+ break;
+ }
+ }
+ }
+
+ static void toJson(ProxyOptions obj, JsonObject json) {
+ toJson(obj, json.getMap());
+ }
+
+ static void toJson(ProxyOptions obj, java.util.Map json) {
+ if (obj.getCacheOptions() != null) {
+ json.put("cacheOptions", obj.getCacheOptions().toJson());
+ }
+ json.put("supportWebSocket", obj.getSupportWebSocket());
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyRequest.java b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyRequest.java
new file mode 100644
index 0000000..5f7bda9
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyRequest.java
@@ -0,0 +1,181 @@
+/*
+ * 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;
+
+import io.vertx.codegen.annotations.Fluent;
+import io.vertx.codegen.annotations.GenIgnore;
+import io.vertx.codegen.annotations.VertxGen;
+import io.vertx.core.Future;
+import io.vertx.core.MultiMap;
+import io.vertx.core.http.HttpClientRequest;
+import io.vertx.core.http.HttpMethod;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.http.HttpVersion;
+import io.vertx.core.net.HostAndPort;
+import io.vertx.httpproxy.impl.ProxiedRequest;
+
+/**
+ *
+ * Handles the interoperability of the request between the user agent and the origin.
+ *
+ * @author Julien Viet
+ */
+@VertxGen
+public interface ProxyRequest {
+
+ /**
+ * Create a new {@code ProxyRequest} instance, the proxied request will be paused.
+ *
+ * @param proxiedRequest the {@code HttpServerRequest} that is proxied
+ * @return a reference to this, so the API can be used fluently
+ */
+ static ProxyRequest reverseProxy(HttpServerRequest proxiedRequest) {
+ proxiedRequest.pause();
+ return new ProxiedRequest(proxiedRequest);
+ }
+
+ /**
+ * @return the HTTP version of the proxied request
+ */
+ HttpVersion version();
+
+ /**
+ * @return the absolute URI of the proxied request
+ */
+ String absoluteURI();
+
+ /**
+ * @return the HTTP method to be sent to the origin server.
+ */
+ HttpMethod getMethod();
+
+ /**
+ * Set the HTTP method to be sent to the origin server.
+ *
+ * The initial HTTP method value is the proxied request HTTP method.
+ *
+ * @param method the new HTTP method
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ ProxyRequest setMethod(HttpMethod method);
+
+ /**
+ * @return the request URI to be sent to the origin server.
+ */
+ String getURI();
+
+ /**
+ * Set the request URI to be sent to the origin server.
+ *
+ *
The initial request URI value is the proxied request URI.
+ *
+ * @param uri the new URI
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ ProxyRequest setURI(String uri);
+
+ /**
+ * @return the request body to be sent to the origin server.
+ */
+ Body getBody();
+
+ /**
+ * Set the request body to be sent to the origin server.
+ *
+ *
The initial request body value is the proxied request body.
+ *
+ * @param body the new body
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ ProxyRequest setBody(Body body);
+
+ /**
+ * Set the request authority
+ *
+ *
+ * - for HTTP/1 the {@literal Host} header
+ * - for HTTP/2 the {@literal :authority} pseudo header
+ *
+ *
+ * The value must follow the {@literal :} syntax.
+ *
+ * @param authority the authority
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ ProxyRequest setAuthority(HostAndPort authority);
+
+ /**
+ * @return the request authority, for HTTP2 the {@literal :authority} pseudo header otherwise the {@literal Host} header
+ */
+ HostAndPort getAuthority();
+
+ /**
+ * @return the headers that will be sent to the origin server, the returned headers can be modified. The headers
+ * map is populated with the proxied request headers
+ */
+ MultiMap headers();
+
+ /**
+ * Put an HTTP header
+ *
+ * @param name The header name
+ * @param value The header value
+ * @return a reference to this, so the API can be used fluently
+ */
+ @GenIgnore
+ @Fluent
+ ProxyRequest putHeader(CharSequence name, CharSequence value);
+
+ /**
+ * Proxy this request to the origin server using the specified {@code request} and then send the proxy response.
+ *
+ * @param request the request connected to the origin server
+ */
+ default Future proxy(HttpClientRequest request) {
+ return send(request).flatMap(resp -> resp.send());
+ }
+
+ /**
+ * Send this request to the origin server using the specified {@code request}.
+ *
+ * The returned future will be completed with the proxy response returned by the origin.
+ *
+ * @param request the request connected to the origin server
+ */
+ Future send(HttpClientRequest request);
+
+ /**
+ * Release the proxy request and its associated resources
+ *
+ * The HTTP server request is resumed, no HTTP server response is sent.
+ *
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ ProxyRequest release();
+
+ /**
+ * @return the proxied HTTP server request
+ */
+ HttpServerRequest proxiedRequest();
+
+ /**
+ * Create and return the proxy response.
+ *
+ * @return the proxy response
+ */
+ ProxyResponse response();
+
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyResponse.java b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyResponse.java
new file mode 100644
index 0000000..80b3e0e
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/ProxyResponse.java
@@ -0,0 +1,133 @@
+/*
+ * 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;
+
+import io.vertx.codegen.annotations.Fluent;
+import io.vertx.codegen.annotations.GenIgnore;
+import io.vertx.codegen.annotations.VertxGen;
+import io.vertx.core.Future;
+import io.vertx.core.MultiMap;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.streams.ReadStream;
+
+import java.util.function.Function;
+
+/**
+ *
+ * Handles the interoperability of the response between the origin and the user agent.
+ *
+ * @author Julien Viet
+ */
+@VertxGen
+public interface ProxyResponse {
+
+ /**
+ *
+ * Return the corresponding {@code ProxyRequest}.
+ *
+ * @return the proxy request
+ */
+ ProxyRequest request();
+
+ /**
+ * Get the status code.
+ *
+ * @return the status code to be sent to the user agent
+ */
+ int getStatusCode();
+
+ /**
+ * Set the status code to be sent to the user agent.
+ *
+ *
The initial value is the proxied response status code.
+ *
+ * @param sc the status code
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ ProxyResponse setStatusCode(int sc);
+
+ /**
+ * Get the status message.
+ *
+ * @return the status message to be sent to the user agent
+ */
+ String getStatusMessage();
+
+ /**
+ * Set the status message to be sent to the user agent.
+ *
+ *
The initial value is the proxied response status message.
+ *
+ * @param statusMessage the status message
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ ProxyResponse setStatusMessage(String statusMessage);
+
+ /**
+ * @return the headers that will be sent to the user agent, the returned headers can be modified. The headers
+ * map is populated with the proxied response headers
+ */
+ MultiMap headers();
+
+ /**
+ * Put an HTTP header.
+ *
+ * @param name The header name
+ * @param value The header value
+ * @return a reference to this, so the API can be used fluently
+ */
+ @GenIgnore
+ @Fluent
+ ProxyResponse putHeader(CharSequence name, CharSequence value);
+
+ /**
+ * Get the body of the response.
+ *
+ * @return the response body to be sent to the user agent
+ */
+ Body getBody();
+
+ /**
+ * Set the request body to be sent to the user agent.
+ *
+ *
The initial request body value is the proxied response body.
+ *
+ * @param body the new body
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ ProxyResponse setBody(Body body);
+
+ boolean publicCacheControl();
+
+ long maxAge();
+
+ /**
+ * @return the {@code etag} sent by the origin response
+ */
+ String etag();
+
+ /**
+ * Send the proxies response to the user agent.
+ */
+ Future send();
+
+ /**
+ * Release the proxy response.
+ *
+ * The proxied response is resumed, no HTTP response is sent to the user-agent
+ */
+ @Fluent
+ ProxyResponse release();
+
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java b/sf-vertx/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java
new file mode 100644
index 0000000..69af307
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java
@@ -0,0 +1,61 @@
+package io.vertx.httpproxy.cache;
+
+import io.vertx.codegen.annotations.DataObject;
+import io.vertx.codegen.json.annotations.JsonGen;
+import io.vertx.core.impl.Arguments;
+import io.vertx.core.json.JsonObject;
+import io.vertx.httpproxy.impl.CacheImpl;
+import io.vertx.httpproxy.spi.cache.Cache;
+
+/**
+ * Cache options.
+ */
+@DataObject
+@JsonGen(publicConverter = false)
+public class CacheOptions {
+
+ public static final int DEFAULT_MAX_SIZE = 1000;
+
+ private int maxSize = DEFAULT_MAX_SIZE;
+
+ public CacheOptions() {
+ }
+
+ public CacheOptions(JsonObject json) {
+ CacheOptionsConverter.fromJson(json, this);
+ }
+
+ /**
+ * @return the max number of entries the cache can hold
+ */
+ public int getMaxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Set the max number of entries the cache can hold.
+ *
+ * @param maxSize the max size
+ * @return a reference to this, so the API can be used fluently
+ */
+ public CacheOptions setMaxSize(int maxSize) {
+ Arguments.require(maxSize > 0, "Max size must be > 0");
+ this.maxSize = maxSize;
+ return this;
+ }
+
+ public Cache newCache() {
+ return new CacheImpl<>(this);
+ }
+
+ @Override
+ public String toString() {
+ return toJson().toString();
+ }
+
+ public JsonObject toJson() {
+ JsonObject json = new JsonObject();
+ CacheOptionsConverter.toJson(this, json);
+ return json;
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/cache/CacheOptionsConverter.java b/sf-vertx/src/main/java/io/vertx/httpproxy/cache/CacheOptionsConverter.java
new file mode 100644
index 0000000..6a637b5
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/cache/CacheOptionsConverter.java
@@ -0,0 +1,39 @@
+package io.vertx.httpproxy.cache;
+
+import io.vertx.core.json.JsonObject;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.impl.JsonUtil;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.util.Base64;
+
+/**
+ * Converter and mapper for {@link io.vertx.httpproxy.cache.CacheOptions}.
+ * NOTE: This class has been automatically generated from the {@link io.vertx.httpproxy.cache.CacheOptions} original class using Vert.x codegen.
+ */
+public class CacheOptionsConverter {
+
+
+ private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER;
+ private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER;
+
+ static void fromJson(Iterable> json, CacheOptions obj) {
+ for (java.util.Map.Entry member : json) {
+ switch (member.getKey()) {
+ case "maxSize":
+ if (member.getValue() instanceof Number) {
+ obj.setMaxSize(((Number)member.getValue()).intValue());
+ }
+ break;
+ }
+ }
+ }
+
+ static void toJson(CacheOptions obj, JsonObject json) {
+ toJson(obj, json.getMap());
+ }
+
+ static void toJson(CacheOptions obj, java.util.Map json) {
+ json.put("maxSize", obj.getMaxSize());
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/BufferedReadStream.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/BufferedReadStream.java
new file mode 100644
index 0000000..e4f040d
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/BufferedReadStream.java
@@ -0,0 +1,82 @@
+/*
+ * 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.Handler;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.streams.ReadStream;
+
+public class BufferedReadStream implements ReadStream {
+
+ private long demand = 0L;
+ private Handler endHandler;
+ private Handler handler;
+ private boolean ended = false;
+ private final Buffer content;
+
+ public BufferedReadStream() {
+ this.content = Buffer.buffer();
+ }
+
+ public BufferedReadStream(Buffer content) {
+ this.content = content;
+ }
+
+ @Override
+ public ReadStream exceptionHandler(Handler handler) {
+ return this;
+ }
+
+ @Override
+ public ReadStream handler(Handler handler) {
+ this.handler = handler;
+ return this;
+ }
+
+ @Override
+ public ReadStream pause() {
+ demand = 0L;
+ return this;
+ }
+
+ @Override
+ public ReadStream resume() {
+ fetch(Long.MAX_VALUE);
+ return this;
+ }
+
+ @Override
+ public ReadStream fetch(long amount) {
+ if (!ended && amount > 0) {
+ ended = true;
+ demand += amount;
+ if (demand < 0L) {
+ demand = Long.MAX_VALUE;
+ }
+ if (demand != Long.MAX_VALUE) {
+ demand--;
+ }
+ if (handler != null && content.length() > 0) {
+ handler.handle(content);
+ }
+ if (endHandler != null) {
+ endHandler.handle(null);
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public ReadStream endHandler(Handler endHandler) {
+ this.endHandler = endHandler;
+ return this;
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/BufferingReadStream.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/BufferingReadStream.java
new file mode 100644
index 0000000..8d6185d
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/BufferingReadStream.java
@@ -0,0 +1,76 @@
+/*
+ * 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.Handler;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.streams.ReadStream;
+
+class BufferingReadStream implements ReadStream {
+
+ private final ReadStream stream;
+ private final Buffer content;
+ private Handler endHandler;
+
+ public BufferingReadStream(ReadStream stream, Buffer content) {
+ this.stream = stream;
+ this.content = content;
+ }
+
+ @Override
+ public ReadStream exceptionHandler(Handler handler) {
+ stream.exceptionHandler(handler);
+ return this;
+ }
+
+ @Override
+ public ReadStream handler(Handler handler) {
+ if (handler != null) {
+ stream.handler(buff -> {
+ content.appendBuffer(buff);
+ handler.handle(buff);
+ });
+ } else {
+ stream.handler(null);
+ }
+ return this;
+ }
+
+ @Override
+ public ReadStream pause() {
+ stream.pause();
+ return this;
+ }
+
+ @Override
+ public ReadStream resume() {
+ stream.resume();
+ return this;
+ }
+
+ @Override
+ public ReadStream fetch(long amount) {
+ stream.fetch(amount);
+ return this;
+ }
+
+ @Override
+ public ReadStream endHandler(Handler endHandler) {
+ if (endHandler != null) {
+ stream.endHandler(v -> {
+ endHandler.handle(null);
+ });
+ } else {
+ stream.endHandler(null);
+ }
+ return this;
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/BufferingWriteStream.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/BufferingWriteStream.java
new file mode 100644
index 0000000..c0aa8fb
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/BufferingWriteStream.java
@@ -0,0 +1,69 @@
+/*
+ * 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.codegen.annotations.Nullable;
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.streams.ReadStream;
+import io.vertx.core.streams.WriteStream;
+
+class BufferingWriteStream implements WriteStream {
+
+ private final Buffer content;
+
+ public BufferingWriteStream() {
+ this.content = Buffer.buffer();
+ }
+
+ public Buffer content() {
+ return content;
+ }
+
+ @Override
+ public WriteStream exceptionHandler(Handler handler) {
+ return this;
+ }
+
+ @Override
+ public Future write(Buffer data) {
+ content.appendBuffer(data);
+ return Future.succeededFuture();
+ }
+
+ @Override
+ public void write(Buffer data, Handler> handler) {
+ content.appendBuffer(data);
+ handler.handle(Future.succeededFuture());
+ }
+
+ @Override
+ public void end(Handler> handler) {
+ handler.handle(Future.succeededFuture());
+ }
+
+ @Override
+ public WriteStream setWriteQueueMaxSize(int maxSize) {
+ return this;
+ }
+
+ @Override
+ public boolean writeQueueFull() {
+ return false;
+ }
+
+ @Override
+ public WriteStream drainHandler(@Nullable Handler handler) {
+ return this;
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/CacheControl.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/CacheControl.java
new file mode 100644
index 0000000..a89b94c
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/CacheControl.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+/**
+ * @author Julien Viet
+ */
+public class CacheControl {
+
+ private int maxAge;
+ private boolean _public;
+
+ public CacheControl parse(String header) {
+ maxAge = -1;
+ _public = false;
+ String[] parts = header.split(","); // No regex
+ for (String part : parts) {
+ part = part.trim().toLowerCase();
+ switch (part) {
+ case "public":
+ _public = true;
+ break;
+ default:
+ if (part.startsWith("max-age=")) {
+ maxAge = Integer.parseInt(part.substring(8));
+
+ }
+ break;
+ }
+ }
+ return this;
+ }
+
+ public int maxAge() {
+ return maxAge;
+ }
+
+ public boolean isPublic() {
+ return _public;
+ }
+
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java
new file mode 100644
index 0000000..4614707
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java
@@ -0,0 +1,23 @@
+package io.vertx.httpproxy.impl;
+
+import io.vertx.httpproxy.cache.CacheOptions;
+import io.vertx.httpproxy.spi.cache.Cache;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Simplistic implementation.
+ */
+public class CacheImpl extends LinkedHashMap implements Cache {
+
+ private final int maxSize;
+
+ public CacheImpl(CacheOptions options) {
+ this.maxSize = options.getMaxSize();
+ }
+
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > maxSize;
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java
new file mode 100644
index 0000000..5d1ed52
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java
@@ -0,0 +1,150 @@
+package io.vertx.httpproxy.impl;
+
+import io.vertx.core.Future;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpMethod;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.httpproxy.Body;
+import io.vertx.httpproxy.ProxyContext;
+import io.vertx.httpproxy.ProxyInterceptor;
+import io.vertx.httpproxy.ProxyRequest;
+import io.vertx.httpproxy.ProxyResponse;
+import io.vertx.httpproxy.spi.cache.Cache;
+
+import java.time.Instant;
+import java.util.function.BiFunction;
+
+class CachingFilter implements ProxyInterceptor {
+
+ 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 Cache cache;
+
+ public CachingFilter(Cache cache) {
+ this.cache = cache;
+ }
+
+ @Override
+ public Future handleProxyRequest(ProxyContext context) {
+ Future future = tryHandleProxyRequestFromCache(context);
+ if (future != null) {
+ return future;
+ }
+ return context.sendRequest();
+ }
+
+ @Override
+ public Future handleProxyResponse(ProxyContext context) {
+ return sendAndTryCacheProxyResponse(context);
+ }
+
+ private Future sendAndTryCacheProxyResponse(ProxyContext context) {
+
+ ProxyResponse response = context.response();
+ Resource cached = context.get("cached_resource", Resource.class);
+
+ if (cached != null && response.getStatusCode() == 304) {
+ // Warning: this relies on the fact that HttpServerRequest will not send a body for HEAD
+ response.release();
+ cached.init(response);
+ return context.sendResponse();
+ }
+
+ ProxyRequest request = response.request();
+ 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());
+ Body body = response.getBody();
+ response.setBody(Body.body(new BufferingReadStream(body.stream(), res.content), body.length()));
+ Future fut = context.sendResponse();
+ fut.onSuccess(v -> {
+ cache.put(absoluteUri, res);
+ });
+ return fut;
+ } 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());
+ }
+ }
+ }
+ return context.sendResponse();
+ }
+ } else {
+ return context.sendResponse();
+ }
+ }
+
+ private static boolean revalidateResource(ProxyResponse response, Resource resource) {
+ if (resource.etag != null && response.etag() != null) {
+ return resource.etag.equals(response.etag());
+ }
+ return true;
+ }
+
+ private Future tryHandleProxyRequestFromCache(ProxyContext context) {
+
+ ProxyRequest proxyRequest = context.request();
+
+ HttpServerRequest response = proxyRequest.proxiedRequest();
+
+ Resource resource;
+ HttpMethod method = response.method();
+ if (method == HttpMethod.GET || method == HttpMethod.HEAD) {
+ String cacheKey = proxyRequest.absoluteURI();
+ resource = cache.computeIfPresent(cacheKey, CACHE_GET_AND_VALIDATE);
+ if (resource == null) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+
+ String cacheControlHeader = response.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);
+ context.set("cached_resource", resource);
+ return context.sendRequest();
+ } else {
+ return null;
+ }
+ }
+ }
+ }
+
+ //
+ String ifModifiedSinceHeader = response.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
+ if ((response.method() == HttpMethod.GET || response.method() == HttpMethod.HEAD) && ifModifiedSinceHeader != null && resource.lastModified != null) {
+ Instant ifModifiedSince = ParseUtils.parseHeaderDate(ifModifiedSinceHeader);
+ if (!ifModifiedSince.isAfter(resource.lastModified)) {
+ response.response().setStatusCode(304).end();
+ return Future.succeededFuture();
+ }
+ }
+ proxyRequest.release();
+ ProxyResponse proxyResponse = proxyRequest.response();
+ resource.init(proxyResponse);
+ return Future.succeededFuture(proxyResponse);
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/HttpUtils.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/HttpUtils.java
new file mode 100644
index 0000000..4aa83f4
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/HttpUtils.java
@@ -0,0 +1,55 @@
+/*
+ * 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.MultiMap;
+import io.vertx.core.http.HttpHeaders;
+
+import java.time.Instant;
+import java.util.List;
+
+class HttpUtils {
+
+ static Boolean isChunked(MultiMap headers) {
+ List te = headers.getAll("transfer-encoding");
+ if (te != null) {
+ boolean chunked = false;
+ for (String val : te) {
+ if (val.equals("chunked")) {
+ chunked = true;
+ } else {
+ return null;
+ }
+ }
+ return chunked;
+ } else {
+ return false;
+ }
+ }
+
+ static Instant dateHeader(MultiMap headers) {
+ String dateHeader = headers.get(HttpHeaders.DATE);
+ if (dateHeader == null) {
+ List warningHeaders = headers.getAll("warning");
+ if (warningHeaders.size() > 0) {
+ for (String warningHeader : warningHeaders) {
+ Instant date = ParseUtils.parseWarningHeaderDate(warningHeader);
+ if (date != null) {
+ return date;
+ }
+ }
+ }
+ return null;
+ } else {
+ return ParseUtils.parseHeaderDate(dateHeader);
+ }
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ParseUtils.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ParseUtils.java
new file mode 100644
index 0000000..b22a224
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ParseUtils.java
@@ -0,0 +1,81 @@
+/*
+ * 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.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.util.*;
+
+/**
+ * @author Julien Viet
+ */
+public class ParseUtils {
+
+ public static final DateTimeFormatter RFC_850_DATE_TIME = new DateTimeFormatterBuilder()
+ .appendPattern("EEEE, dd-MMM-yy HH:mm:ss")
+ .parseLenient()
+ .appendLiteral(" GMT")
+ .toFormatter(Locale.US)
+ .withZone(ZoneId.of("UTC"));
+
+ public static final DateTimeFormatter ASC_TIME = new DateTimeFormatterBuilder()
+ .appendPattern("EEE MMM d HH:mm:ss yyyy")
+ .parseLenient()
+ .toFormatter(Locale.US)
+ .withZone(ZoneId.of("UTC"));
+
+ public static Instant parseHeaderDate(String value) {
+ try {
+ return parseHttpDate(value);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public static Instant parseWarningHeaderDate(String value) {
+ // warn-code
+ int index = value.indexOf(' ');
+ if (index > 0) {
+ // warn-agent
+ index = value.indexOf(' ', index + 1);
+ if (index > 0) {
+ // warn-text
+ index = value.indexOf(' ', index + 1);
+ if (index > 0) {
+ // warn-date
+ int len = value.length();
+ if (index + 2 < len && value.charAt(index + 1) == '"' && value.charAt(len - 1) == '"') {
+ // Space for 2 double quotes
+ return parseHeaderDate(value.substring(index + 2, len - 1));
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static String formatHttpDate(Instant date) {
+ return DateTimeFormatter.RFC_1123_DATE_TIME.format(OffsetDateTime.ofInstant(date, ZoneOffset.UTC));
+ }
+
+ // https://www.rfc-editor.org/rfc/rfc9110#http.date
+ public static Instant parseHttpDate(String value) throws Exception {
+ int pos = value.indexOf(',');
+ if (pos == 3) { // e.g. Sun, 06 Nov 1994 08:49:37 GMT
+ return DateTimeFormatter.RFC_1123_DATE_TIME.parse(value, Instant::from);
+ }
+ if (pos == -1) { // e.g. Sun Nov 6 08:49:37 1994
+ return ASC_TIME.parse(value, Instant::from);
+ }
+ return RFC_850_DATE_TIME.parse(value, Instant::from); // e.g. Sunday, 06-Nov-94 08:49:37 GMT
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java
new file mode 100644
index 0000000..b3da6fe
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.MultiMap;
+import io.vertx.core.Promise;
+import io.vertx.core.buffer.Buffer;
+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.impl.HttpServerRequestInternal;
+import io.vertx.core.impl.ContextInternal;
+import io.vertx.core.net.HostAndPort;
+import io.vertx.core.streams.Pipe;
+import io.vertx.httpproxy.Body;
+import io.vertx.httpproxy.ProxyRequest;
+import io.vertx.httpproxy.ProxyResponse;
+
+import java.util.Map;
+import java.util.Objects;
+
+public class ProxiedRequest implements ProxyRequest {
+
+ private static final CharSequence X_FORWARDED_HOST = HttpHeaders.createOptimized("x-forwarded-host");
+
+ private static final MultiMap HOP_BY_HOP_HEADERS = MultiMap.caseInsensitiveMultiMap()
+ .add(HttpHeaders.CONNECTION, "whatever")
+ .add(HttpHeaders.KEEP_ALIVE, "whatever")
+ .add(HttpHeaders.PROXY_AUTHENTICATE, "whatever")
+ .add(HttpHeaders.PROXY_AUTHORIZATION, "whatever")
+ .add("te", "whatever")
+ .add("trailer", "whatever")
+ .add(HttpHeaders.TRANSFER_ENCODING, "whatever")
+ .add(HttpHeaders.UPGRADE, "whatever");
+
+ final ContextInternal context;
+ private HttpMethod method;
+ private final HttpVersion version;
+ private String uri;
+ private final String absoluteURI;
+ private Body body;
+ private HostAndPort authority;
+ private final MultiMap headers;
+ HttpClientRequest request;
+ private final HttpServerRequest proxiedRequest;
+
+ public ProxiedRequest(HttpServerRequest proxiedRequest) {
+
+ // Determine content length
+ long contentLength = -1L;
+ String contentLengthHeader = proxiedRequest.getHeader(HttpHeaders.CONTENT_LENGTH);
+ if (contentLengthHeader != null) {
+ try {
+ contentLength = Long.parseLong(contentLengthHeader);
+ } catch (NumberFormatException e) {
+ // Ignore ???
+ }
+ }
+
+ this.method = proxiedRequest.method();
+ this.version = proxiedRequest.version();
+ this.body = Body.body(proxiedRequest, contentLength);
+ this.uri = proxiedRequest.uri();
+ this.headers = MultiMap.caseInsensitiveMultiMap().addAll(proxiedRequest.headers());
+ this.absoluteURI = proxiedRequest.absoluteURI();
+ this.proxiedRequest = proxiedRequest;
+ this.context = (ContextInternal) ((HttpServerRequestInternal) proxiedRequest).context();
+ this.authority = proxiedRequest.authority();
+ }
+
+ @Override
+ public HttpVersion version() {
+ return version;
+ }
+
+ @Override
+ public String getURI() {
+ return uri;
+ }
+
+ @Override
+ public ProxyRequest setURI(String uri) {
+ this.uri = uri;
+ return this;
+ }
+
+ @Override
+ public Body getBody() {
+ return body;
+ }
+
+ @Override
+ public ProxyRequest setBody(Body body) {
+ this.body = body;
+ return this;
+ }
+
+ @Override
+ public ProxyRequest setAuthority(HostAndPort authority) {
+ Objects.requireNonNull(authority);
+ this.authority= authority;
+ return this;
+ }
+
+ @Override
+ public HostAndPort getAuthority() {
+ return authority;
+ }
+
+ @Override
+ public String absoluteURI() {
+ return absoluteURI;
+ }
+
+ @Override
+ public HttpMethod getMethod() {
+ return method;
+ }
+
+ @Override
+ public ProxyRequest setMethod(HttpMethod method) {
+ this.method = method;
+ return this;
+ }
+
+ @Override
+ public HttpServerRequest proxiedRequest() {
+ return proxiedRequest;
+ }
+
+ @Override
+ public ProxyRequest release() {
+ body.stream().resume();
+ headers.clear();
+ body = null;
+ return this;
+ }
+
+ @Override
+ public ProxyResponse response() {
+ return new ProxiedResponse(this, proxiedRequest.response());
+ }
+
+ void sendRequest(Handler> responseHandler) {
+
+ request.response().map(r -> {
+ r.pause(); // Pause it
+ return new ProxiedResponse(this, proxiedRequest.response(), r);
+ }).onComplete(responseHandler);
+
+
+ request.setMethod(method);
+ request.setURI(uri);
+
+ // Add all headers
+ for (Map.Entry header : headers) {
+ String name = header.getKey();
+ String value = header.getValue();
+ if (!HOP_BY_HOP_HEADERS.contains(name) && !name.equals("host")) {
+ request.headers().add(name, value);
+ }
+ }
+
+ //
+ if (authority != null) {
+ request.authority(authority);
+ HostAndPort proxiedAuthority = proxiedRequest.authority();
+ if (!equals(authority, proxiedAuthority)) {
+ // Should cope with existing forwarded host headers
+ request.putHeader(X_FORWARDED_HOST, proxiedAuthority.toString());
+ }
+ }
+
+ long len = body.length();
+ if (len >= 0) {
+ request.putHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(len));
+ } else {
+ Boolean isChunked = HttpUtils.isChunked(proxiedRequest.headers());
+ request.setChunked(len == -1 && Boolean.TRUE == isChunked);
+ }
+
+ Pipe pipe = body.stream().pipe();
+ pipe.endOnComplete(true);
+ pipe.endOnFailure(false);
+ pipe.to(request, ar -> {
+ if (ar.failed()) {
+ request.reset();
+ }
+ });
+ }
+
+ private static boolean equals(HostAndPort hp1, HostAndPort hp2) {
+ if (hp1 == null || hp2 == null) {
+ return false;
+ }
+ return hp1.host().equals(hp2.host()) && hp1.port() == hp2.port();
+ }
+
+ @Override
+ public ProxyRequest putHeader(CharSequence name, CharSequence value) {
+ headers.set(name, value);
+ return this;
+ }
+
+ @Override
+ public MultiMap headers() {
+ return headers;
+ }
+
+ @Override
+ public Future send(HttpClientRequest request) {
+ Promise promise = context.promise();
+ this.request = request;
+ sendRequest(promise);
+ return promise.future();
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ProxiedResponse.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ProxiedResponse.java
new file mode 100644
index 0000000..5080734
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ProxiedResponse.java
@@ -0,0 +1,271 @@
+/*
+ * 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.MultiMap;
+import io.vertx.core.Promise;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpClientResponse;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpServerResponse;
+import io.vertx.core.http.HttpVersion;
+import io.vertx.core.streams.Pipe;
+import io.vertx.core.streams.ReadStream;
+import io.vertx.httpproxy.Body;
+import io.vertx.httpproxy.ProxyRequest;
+import io.vertx.httpproxy.ProxyResponse;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+class ProxiedResponse implements ProxyResponse {
+
+ private final ProxiedRequest request;
+ private final HttpServerResponse proxiedResponse;
+ private int statusCode;
+ private String statusMessage;
+ private Body body;
+ private final MultiMap headers;
+ private HttpClientResponse response;
+ private long maxAge;
+ private String etag;
+ private boolean publicCacheControl;
+
+ ProxiedResponse(ProxiedRequest request, HttpServerResponse proxiedResponse) {
+ this.response = null;
+ this.statusCode = 200;
+ this.headers = MultiMap.caseInsensitiveMultiMap();
+ this.request = request;
+ this.proxiedResponse = proxiedResponse;
+ }
+
+ ProxiedResponse(ProxiedRequest request, HttpServerResponse proxiedResponse, HttpClientResponse response) {
+
+ // Determine content length
+ long contentLength = -1L;
+ String contentLengthHeader = response.getHeader(HttpHeaders.CONTENT_LENGTH);
+ if (contentLengthHeader != null) {
+ try {
+ contentLength = Long.parseLong(contentLengthHeader);
+ } catch (NumberFormatException e) {
+ // Ignore ???
+ }
+ }
+
+ this.request = request;
+ this.response = response;
+ this.proxiedResponse = proxiedResponse;
+ this.statusCode = response.statusCode();
+ this.statusMessage = response.statusMessage();
+ this.body = Body.body(response, contentLength);
+
+ long maxAge = -1;
+ boolean publicCacheControl = false;
+ String cacheControlHeader = response.getHeader(HttpHeaders.CACHE_CONTROL);
+ if (cacheControlHeader != null) {
+ CacheControl cacheControl = new CacheControl().parse(cacheControlHeader);
+ if (cacheControl.isPublic()) {
+ publicCacheControl = true;
+ if (cacheControl.maxAge() > 0) {
+ maxAge = (long)cacheControl.maxAge() * 1000;
+ } else {
+ String dateHeader = response.getHeader(HttpHeaders.DATE);
+ String expiresHeader = response.getHeader(HttpHeaders.EXPIRES);
+ if (dateHeader != null && expiresHeader != null) {
+ maxAge = ParseUtils.parseHeaderDate(expiresHeader).toEpochMilli() - ParseUtils.parseHeaderDate(dateHeader).toEpochMilli();
+ }
+ }
+ }
+ }
+ this.maxAge = maxAge;
+ this.publicCacheControl = publicCacheControl;
+ this.etag = response.getHeader(HttpHeaders.ETAG);
+ this.headers = MultiMap.caseInsensitiveMultiMap().addAll(response.headers());
+ }
+
+ @Override
+ public ProxyRequest request() {
+ return request;
+ }
+
+ @Override
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ @Override
+ public ProxyResponse setStatusCode(int sc) {
+ statusCode = sc;
+ return this;
+ }
+
+ @Override
+ public String getStatusMessage() {
+ return statusMessage;
+ }
+
+ @Override
+ public ProxyResponse setStatusMessage(String statusMessage) {
+ this.statusMessage = statusMessage;
+ return this;
+ }
+
+ @Override
+ public Body getBody() {
+ return body;
+ }
+
+ @Override
+ public ProxyResponse setBody(Body body) {
+ this.body = body;
+ return this;
+ }
+
+ @Override
+ public boolean publicCacheControl() {
+ return publicCacheControl;
+ }
+
+ @Override
+ public long maxAge() {
+ return maxAge;
+ }
+
+ @Override
+ public String etag() {
+ return etag;
+ }
+
+ @Override
+ public MultiMap headers() {
+ return headers;
+ }
+
+ @Override
+ public ProxyResponse putHeader(CharSequence name, CharSequence value) {
+ headers.set(name, value);
+ return this;
+ }
+
+ @Override
+ public Future send() {
+ Promise promise = request.context.promise();
+ send(promise);
+ return promise.future();
+ }
+
+ public void send(Handler> completionHandler) {
+ // Set stuff
+ proxiedResponse.setStatusCode(statusCode);
+
+ if(statusMessage != null) {
+ proxiedResponse.setStatusMessage(statusMessage);
+ }
+
+ // Date header
+ Instant date = HttpUtils.dateHeader(headers);
+ if (date == null) {
+ date = Instant.now();
+ }
+ try {
+ proxiedResponse.putHeader("date", ParseUtils.formatHttpDate(date));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // Warning header
+ List warningHeaders = headers.getAll("warning");
+ if (warningHeaders.size() > 0) {
+ warningHeaders = new ArrayList<>(warningHeaders);
+ String dateHeader = headers.get("date");
+ Instant dateInstant = dateHeader != null ? ParseUtils.parseHeaderDate(dateHeader) : null;
+ Iterator i = warningHeaders.iterator();
+ // Suppress incorrect warning header
+ while (i.hasNext()) {
+ String warningHeader = i.next();
+ Instant warningInstant = ParseUtils.parseWarningHeaderDate(warningHeader);
+ if (warningInstant != null && dateInstant != null && !warningInstant.equals(dateInstant)) {
+ i.remove();
+ }
+ }
+ }
+ proxiedResponse.putHeader("warning", warningHeaders);
+
+ // Handle other headers
+ headers.forEach(header -> {
+ String name = header.getKey();
+ String value = header.getValue();
+ if (name.equalsIgnoreCase("date") || name.equalsIgnoreCase("warning") || name.equalsIgnoreCase("transfer-encoding")) {
+ // Skip
+ } else {
+ proxiedResponse.headers().add(name, value);
+ }
+ });
+
+ //
+ if (body == null) {
+ proxiedResponse.end();
+ return;
+ }
+
+ long len = body.length();
+ if (len >= 0) {
+ proxiedResponse.putHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(len));
+ } else {
+ if (request.proxiedRequest().version() == HttpVersion.HTTP_1_0) {
+ // Special handling for HTTP 1.0 clients that cannot handle chunked encoding
+ // we need to buffer the content
+ BufferingWriteStream buffer = new BufferingWriteStream();
+ body.stream().pipeTo(buffer, ar -> {
+ if (ar.succeeded()) {
+ Buffer content = buffer.content();
+ proxiedResponse.end(content, completionHandler);
+ } else {
+ System.out.println("Not implemented");
+ }
+ });
+ return;
+ }
+ proxiedResponse.setChunked(true);
+ }
+ ReadStream bodyStream = body.stream();
+ sendResponse(bodyStream, completionHandler);
+ }
+
+ @Override
+ public ProxyResponse release() {
+ if (response != null) {
+ response.resume();
+ response = null;
+ body = null;
+ headers.clear();
+ }
+ return this;
+ }
+
+ private void sendResponse(ReadStream body, Handler> completionHandler) {
+ Pipe pipe = body.pipe();
+ pipe.endOnSuccess(true);
+ pipe.endOnFailure(false);
+ pipe.to(proxiedResponse, ar -> {
+ if (ar.failed()) {
+ request.request.reset();
+ proxiedResponse.reset();
+ }
+ completionHandler.handle(ar);
+ });
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/Resource.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/Resource.java
new file mode 100644
index 0000000..2b7768b
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/Resource.java
@@ -0,0 +1,51 @@
+/*
+ * 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.MultiMap;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.httpproxy.Body;
+import io.vertx.httpproxy.ProxyResponse;
+
+import java.time.Instant;
+
+class Resource {
+
+ final String absoluteUri;
+ final int statusCode;
+ final String statusMessage;
+ final MultiMap headers;
+ final long timestamp;
+ final long maxAge;
+ final Instant lastModified;
+ final String etag;
+ final Buffer content = Buffer.buffer();
+
+ Resource(String absoluteUri, int statusCode, String statusMessage, MultiMap headers, long timestamp, long maxAge) {
+ String lastModifiedHeader = headers.get(HttpHeaders.LAST_MODIFIED);
+ this.absoluteUri = absoluteUri;
+ this.statusCode = statusCode;
+ this.statusMessage = statusMessage;
+ this.headers = headers;
+ this.timestamp = timestamp;
+ this.maxAge = maxAge;
+ this.lastModified = lastModifiedHeader != null ? ParseUtils.parseHeaderDate(lastModifiedHeader) : null;
+ this.etag = headers.get(HttpHeaders.ETAG);
+ }
+
+ void init(ProxyResponse proxyResponse) {
+ proxyResponse.setStatusCode(200);
+ proxyResponse.setStatusMessage(statusMessage);
+ proxyResponse.headers().addAll(headers);
+ proxyResponse.setBody(Body.body(content));
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java
new file mode 100644
index 0000000..b39373b
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java
@@ -0,0 +1,299 @@
+/*
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.sf.vertx.init.DynamicBuildServer;
+import com.sf.vertx.security.MainSecurity;
+import com.sf.vertx.service.impl.AppConfigServiceImpl;
+
+import io.vertx.core.Future;
+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.HttpClientResponse;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpMethod;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.http.HttpServerResponse;
+import io.vertx.core.json.JsonObject;
+import io.vertx.core.net.NetSocket;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.httpproxy.Body;
+import io.vertx.httpproxy.HttpProxy;
+import io.vertx.httpproxy.ProxyContext;
+import io.vertx.httpproxy.ProxyInterceptor;
+import io.vertx.httpproxy.ProxyOptions;
+import io.vertx.httpproxy.ProxyRequest;
+import io.vertx.httpproxy.ProxyResponse;
+import io.vertx.httpproxy.cache.CacheOptions;
+import io.vertx.httpproxy.spi.cache.Cache;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+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<>();
+ private RoutingContext ctx;
+ private WebClient mainWebClient;
+
+ 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(RoutingContext ctx, WebClient mainWebClient) {
+ // TODO 改造了这个地方
+ this.ctx = ctx;
+ this.mainWebClient = mainWebClient;
+ handle(ctx.request());
+ }
+
+ @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) {
+ HttpServerRequest proxiedRequest = proxyRequest.proxiedRequest();
+ resolveOrigin(proxiedRequest).onComplete(ar -> {
+ if (ar.succeeded()) {
+ HttpClientRequest request = ar.result();
+ request.setMethod(HttpMethod.GET);
+ request.setURI(proxiedRequest.uri());
+ 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) {
+ // TODO 改造了这个地方
+ // 创建一个响应并设置参数
+// ctx.request().response().setStatusCode(200)
+// .putHeader("content-type", "text/plain").end(body);
+ // 发起一个请求
+ String sacAppHeaderKey = proxyRequest.headers().get(DynamicBuildServer.SAC_APP_HEADER_KEY);
+ if (StringUtils.isNotBlank(sacAppHeaderKey) && AppConfigServiceImpl.appDataSecurity(sacAppHeaderKey)) {
+ String body = ctx.getBodyAsString();
+ String bodyData = MainSecurity.aesDecrypt(body, "dadddsdfadfadsfa33323223");
+ return Future.future(p -> {
+ mainWebClient.post(9198, "127.0.0.1", "/vertx/body").sendJson(bodyData, h -> {
+ if (h.succeeded()) {
+ // 释放资源
+ proxyRequest.release();
+ JsonObject responseData = h.result().bodyAsJsonObject();
+ log.info("responseData:{}", responseData);
+ // 加密
+ String dataStr = MainSecurity.aesEncrypt(responseData.toString(), "dadddsdfadfadsfa33323223");
+ log.info("aesEncrypt dataStr:{}", dataStr);
+ ctx.request().response().setStatusCode(200).putHeader("content-type", "application/json")
+ .end(dataStr);
+ Buffer buffer = Buffer.buffer(dataStr);
+ ProxyResponse proxyResponse = proxyRequest.response().setStatusCode(200)
+ .putHeader("content-type", "application/json").setBody(Body.body(buffer));
+ p.complete(proxyResponse);
+ } else {
+ p.fail(h.cause());
+ }
+ });
+ });
+ } else {
+ 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();
+ }
+ }
+}
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/package-info.java b/sf-vertx/src/main/java/io/vertx/httpproxy/package-info.java
new file mode 100644
index 0000000..d364f8e
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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
+ */
+
+/**
+ * @author Julien Viet
+ */
+@ModuleGen(name = "vertx-http-proxy", groupPackage = "io.vertx", useFutures = true)
+package io.vertx.httpproxy;
+
+import io.vertx.codegen.annotations.ModuleGen;
diff --git a/sf-vertx/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java b/sf-vertx/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java
new file mode 100644
index 0000000..c930719
--- /dev/null
+++ b/sf-vertx/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java
@@ -0,0 +1,9 @@
+package io.vertx.httpproxy.spi.cache;
+
+import java.util.Map;
+
+/**
+ * Cache SPI.
+ */
+public interface Cache extends Map {
+}