From c579135560746e3d609acd3948fe0296173e7c26 Mon Sep 17 00:00:00 2001 From: Hiram Chirino Date: Thu, 27 Mar 2025 17:28:52 -0400 Subject: [PATCH] Refactoring to clean up things. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move non-jaxrs specific bits back to proxy-wasm-java-host. Move the jaxrs unit tests back to proxy-wasm-jaxrs-quarkus Make the proxy-wasm-jaxrs-quarkus-example simpler so that we can point user’s to it as a starting point. Signed-off-by: Hiram Chirino --- .../proxywasm/plugin/HttpContext.java | 281 +++++++++++++++++ .../proxywasm/plugin/HttpRequestAdaptor.java | 36 +++ .../roastedroot/proxywasm/plugin}/Logger.java | 2 +- .../roastedroot/proxywasm/plugin/Plugin.java | 43 +-- .../proxywasm/plugin/PluginFactory.java | 7 + .../proxywasm/plugin}/PluginHandler.java | 33 +- .../io/roastedroot/proxywasm/plugin/Pool.java | 36 +-- .../proxywasm/plugin/SendResponse.java | 25 ++ .../proxywasm/plugin/ServerAdaptor.java | 4 +- .../proxywasm/jaxrs/example/App.java | 60 +--- .../proxywasm/jaxrs/example/Resources.java | 58 +--- .../proxywasm/jaxrs/example/Helpers.java | 19 -- .../jaxrs/example/IsTrueMatcher.java | 27 -- proxy-wasm-jaxrs-quarkus/pom.xml | 22 +- .../proxywasm/jaxrs/example/App.java | 89 ++++++ .../proxywasm/jaxrs/example/Helpers.java | 44 +++ .../proxywasm/jaxrs/example/MockLogger.java | 2 +- .../proxywasm/jaxrs/example/Resources.java | 72 +++++ .../jaxrs/example/tests/FFITest.java | 22 ++ .../jaxrs/example/tests}/HeadersTest.java | 2 +- .../jaxrs/example/tests/HttpCallTest.java | 8 +- .../jaxrs/example/tests}/TickTest.java | 2 +- .../src/test/resources/application.properties | 2 + proxy-wasm-jaxrs/pom.xml | 16 - ...dler.java => JaxrsHttpRequestAdaptor.java} | 283 ++---------------- ...oxyMap.java => MultivaluedMapAdaptor.java} | 12 +- .../proxywasm/jaxrs/ProxyWasmFilter.java | 156 +++++----- .../proxywasm/jaxrs/WasmPluginFactory.java | 7 - .../proxywasm/jaxrs/WasmPluginFeature.java | 34 ++- .../jaxrs/servlet/ServletHttpServer.java | 107 ------- .../servlet/ServletHttpServerRequest.java | 39 --- .../jaxrs/spi/HttpServerRequest.java | 15 - ...uest.java => VertxHttpRequestAdaptor.java} | 13 +- .../jaxrs/vertx/VertxHttpServer.java | 4 +- 34 files changed, 808 insertions(+), 774 deletions(-) create mode 100644 proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/HttpContext.java create mode 100644 proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/HttpRequestAdaptor.java rename {proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs => proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin}/Logger.java (78%) rename proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java => proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Plugin.java (72%) create mode 100644 proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/PluginFactory.java rename {proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs => proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin}/PluginHandler.java (94%) rename proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginPool.java => proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Pool.java (58%) create mode 100644 proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/SendResponse.java rename proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/spi/HttpServer.java => proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/ServerAdaptor.java (93%) delete mode 100644 proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Helpers.java delete mode 100644 proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/IsTrueMatcher.java create mode 100644 proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/App.java create mode 100644 proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Helpers.java rename {proxy-wasm-jaxrs-quarkus-example/src/main => proxy-wasm-jaxrs-quarkus/src/test}/java/io/roastedroot/proxywasm/jaxrs/example/MockLogger.java (94%) create mode 100644 proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Resources.java create mode 100644 proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/FFITest.java rename {proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example => proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests}/HeadersTest.java (96%) rename proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/HttpCallTests.java => proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java (80%) rename {proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example => proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests}/TickTest.java (96%) create mode 100644 proxy-wasm-jaxrs-quarkus/src/test/resources/application.properties rename proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/{HttpHandler.java => JaxrsHttpRequestAdaptor.java} (66%) rename proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/{JaxrsProxyMap.java => MultivaluedMapAdaptor.java} (88%) delete mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFactory.java delete mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/servlet/ServletHttpServer.java delete mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/servlet/ServletHttpServerRequest.java delete mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/spi/HttpServerRequest.java rename proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/{VertxHttpServerRequest.java => VertxHttpRequestAdaptor.java} (61%) diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/HttpContext.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/HttpContext.java new file mode 100644 index 0000000..3123af3 --- /dev/null +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/HttpContext.java @@ -0,0 +1,281 @@ +package io.roastedroot.proxywasm.plugin; + +import io.roastedroot.proxywasm.Action; +import io.roastedroot.proxywasm.ChainedHandler; +import io.roastedroot.proxywasm.Handler; +import io.roastedroot.proxywasm.Helpers; +import io.roastedroot.proxywasm.ProxyMap; +import io.roastedroot.proxywasm.StreamType; +import io.roastedroot.proxywasm.WasmException; +import io.roastedroot.proxywasm.WasmResult; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +public class HttpContext { + + private final Plugin plugin; + private final io.roastedroot.proxywasm.HttpContext context; + private final HttpRequestAdaptor requestAdaptor; + private final long startedAt = System.currentTimeMillis(); + + final HashMap, byte[]> properties = new HashMap<>(); + private byte[] httpRequestBody = new byte[0]; + private byte[] grpcReceiveBuffer = new byte[0]; + private byte[] upstreamData = new byte[0]; + private byte[] downStreamData = new byte[0]; + private byte[] httpResponseBody = new byte[0]; + private SendResponse sendResponse; + private Action action; + private CountDownLatch resumeLatch; + + HttpContext(Plugin plugin, HttpRequestAdaptor requestAdaptor) { + this.plugin = plugin; + this.requestAdaptor = requestAdaptor; + this.context = plugin.wasm.createHttpContext(new HandlerImpl()); + } + + public Plugin plugin() { + return plugin; + } + + public io.roastedroot.proxywasm.HttpContext context() { + return context; + } + + public HttpRequestAdaptor requestAdaptor() { + return requestAdaptor; + } + + public Action getAction() { + return action; + } + + public void maybePause() { + // don't pause if plugin wants us to continue + if (action == Action.CONTINUE) { + return; + } + // don't pause if the plugin wants to respond to the request + if (sendResponse != null) { + return; + } + + // ok, lets pause the request processing. A tick or http call response event will + // need to resume the processing + resumeLatch = new CountDownLatch(1); + plugin.unlock(); + try { + resumeLatch.await(); + } catch (InterruptedException ignore) { + return; + } finally { + plugin.lock(); + resumeLatch = null; + } + } + + public byte[] getHttpRequestBody() { + return httpRequestBody; + } + + public byte[] getHttpResponseBody() { + return httpResponseBody; + } + + public void setHttpRequestBody(byte[] httpRequestBody) { + this.httpRequestBody = httpRequestBody; + } + + public byte[] getGrpcReceiveBuffer() { + return grpcReceiveBuffer; + } + + public void setGrpcReceiveBuffer(byte[] grpcReceiveBuffer) { + this.grpcReceiveBuffer = grpcReceiveBuffer; + } + + public byte[] getUpstreamData() { + return upstreamData; + } + + public void setUpstreamData(byte[] upstreamData) { + this.upstreamData = upstreamData; + } + + public byte[] getDownStreamData() { + return downStreamData; + } + + public void setDownStreamData(byte[] downStreamData) { + this.downStreamData = downStreamData; + } + + public void setHttpResponseBody(byte[] httpResponseBody) { + this.httpResponseBody = httpResponseBody; + } + + public SendResponse getSendResponse() { + return sendResponse; + } + + public SendResponse consumeSentHttpResponse() { + var result = sendResponse; + sendResponse = null; + return result; + } + + class HandlerImpl extends ChainedHandler { + + @Override + protected Handler next() { + return plugin.handler; + } + + public ProxyMap getHttpRequestHeaders() { + return requestAdaptor.getHttpRequestHeaders(); + } + + public ProxyMap getHttpRequestTrailers() { + return requestAdaptor.getHttpRequestTrailers(); + } + + public ProxyMap getHttpResponseHeaders() { + return requestAdaptor.getHttpResponseHeaders(); + } + + public ProxyMap getHttpResponseTrailers() { + return requestAdaptor.getHttpResponseTrailers(); + } + + public ProxyMap getGrpcReceiveInitialMetaData() { + return requestAdaptor.getGrpcReceiveInitialMetaData(); + } + + public ProxyMap getGrpcReceiveTrailerMetaData() { + return requestAdaptor.getGrpcReceiveTrailerMetaData(); + } + + // ////////////////////////////////////////////////////////////////////// + // Buffers + // ////////////////////////////////////////////////////////////////////// + + @Override + public byte[] getHttpRequestBody() { + return httpRequestBody; + } + + @Override + public WasmResult setHttpRequestBody(byte[] body) { + httpRequestBody = body; + return WasmResult.OK; + } + + public void appendHttpRequestBody(byte[] body) { + httpRequestBody = Helpers.append(httpRequestBody, body); + } + + @Override + public byte[] getGrpcReceiveBuffer() { + return grpcReceiveBuffer; + } + + @Override + public WasmResult setGrpcReceiveBuffer(byte[] buffer) { + grpcReceiveBuffer = buffer; + return WasmResult.OK; + } + + @Override + public byte[] getUpstreamData() { + return upstreamData; + } + + @Override + public WasmResult setUpstreamData(byte[] data) { + upstreamData = data; + return WasmResult.OK; + } + + @Override + public byte[] getDownStreamData() { + return downStreamData; + } + + @Override + public WasmResult setDownStreamData(byte[] data) { + downStreamData = data; + return WasmResult.OK; + } + + @Override + public byte[] getHttpResponseBody() { + return httpResponseBody; + } + + @Override + public WasmResult setHttpResponseBody(byte[] body) { + httpResponseBody = body; + return WasmResult.OK; + } + + public void appendHttpResponseBody(byte[] body) { + httpResponseBody = Helpers.append(httpResponseBody, body); + } + + // ////////////////////////////////////////////////////////////////////// + // HTTP streams + // ////////////////////////////////////////////////////////////////////// + + @Override + public WasmResult sendHttpResponse( + int responseCode, + byte[] responseCodeDetails, + byte[] responseBody, + ProxyMap additionalHeaders, + int grpcStatus) { + sendResponse = + new SendResponse( + responseCode, + responseCodeDetails, + responseBody, + additionalHeaders, + grpcStatus); + + if (resumeLatch != null) { + resumeLatch.countDown(); + } + return WasmResult.OK; + } + + @Override + public WasmResult setAction(StreamType streamType, Action actionValue) { + action = actionValue; + if (action == Action.CONTINUE && resumeLatch != null) { + resumeLatch.countDown(); + } + return WasmResult.OK; + } + + // ////////////////////////////////////////////////////////////////////// + // Properties + // ////////////////////////////////////////////////////////////////////// + @Override + public byte[] getProperty(List path) throws WasmException { + byte[] result = requestAdaptor.getProperty(HttpContext.this, path); + if (result == null) { + result = properties.get(path); + } + return result; + } + + @Override + public WasmResult setProperty(List path, byte[] value) { + var result = requestAdaptor.setProperty(HttpContext.this, path, value); + if (result == WasmResult.NOT_FOUND) { + properties.put(path, value); + } + return WasmResult.OK; + } + } +} diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/HttpRequestAdaptor.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/HttpRequestAdaptor.java new file mode 100644 index 0000000..b6237b3 --- /dev/null +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/HttpRequestAdaptor.java @@ -0,0 +1,36 @@ +package io.roastedroot.proxywasm.plugin; + +import io.roastedroot.proxywasm.ProxyMap; +import io.roastedroot.proxywasm.WasmException; +import io.roastedroot.proxywasm.WasmResult; +import java.util.List; + +/** + * This interface will help us deal with differences in the http server impl. + */ +public interface HttpRequestAdaptor { + + String remoteAddress(); + + String remotePort(); + + String localAddress(); + + String localPort(); + + ProxyMap getHttpRequestHeaders(); + + ProxyMap getHttpRequestTrailers(); + + ProxyMap getHttpResponseHeaders(); + + ProxyMap getHttpResponseTrailers(); + + ProxyMap getGrpcReceiveInitialMetaData(); + + ProxyMap getGrpcReceiveTrailerMetaData(); + + byte[] getProperty(HttpContext pluginRequest, List path) throws WasmException; + + WasmResult setProperty(HttpContext pluginRequest, List path, byte[] value); +} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/Logger.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Logger.java similarity index 78% rename from proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/Logger.java rename to proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Logger.java index 5cde4c4..c9226e4 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/Logger.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Logger.java @@ -1,4 +1,4 @@ -package io.roastedroot.proxywasm.jaxrs; +package io.roastedroot.proxywasm.plugin; import io.roastedroot.proxywasm.LogLevel; diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Plugin.java similarity index 72% rename from proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java rename to proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Plugin.java index 2d29b8c..43d103b 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Plugin.java @@ -1,4 +1,4 @@ -package io.roastedroot.proxywasm.jaxrs; +package io.roastedroot.proxywasm.plugin; import static io.roastedroot.proxywasm.Helpers.bytes; @@ -8,25 +8,24 @@ import io.roastedroot.proxywasm.ForeignFunction; import io.roastedroot.proxywasm.ProxyWasm; import io.roastedroot.proxywasm.StartException; -import io.roastedroot.proxywasm.jaxrs.spi.HttpServer; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.locks.ReentrantLock; -public class WasmPlugin { +public final class Plugin { final PluginHandler handler; private final ReentrantLock lock = new ReentrantLock(); private final boolean shared; final ProxyWasm wasm; - HttpServer httpServer; + ServerAdaptor httpServer; public Logger logger() { return handler.logger; } - private WasmPlugin(ProxyWasm proxyWasm, PluginHandler handler, boolean shared) { + private Plugin(ProxyWasm proxyWasm, PluginHandler handler, boolean shared) { Objects.requireNonNull(proxyWasm); Objects.requireNonNull(handler); this.shared = shared; @@ -39,8 +38,8 @@ public String name() { return handler.getName(); } - public static WasmPlugin.Builder builder() { - return new WasmPlugin.Builder(); + public static Plugin.Builder builder() { + return new Plugin.Builder(); } public void lock() { @@ -55,10 +54,14 @@ public boolean isShared() { return shared; } - public void setHttpServer(HttpServer httpServer) { + public void setHttpServer(ServerAdaptor httpServer) { this.httpServer = httpServer; } + public HttpContext createHttpContext(HttpRequestAdaptor requestAdaptor) { + return new HttpContext(this, requestAdaptor); + } + public void close() { lock(); try { @@ -76,7 +79,7 @@ public static class Builder implements Cloneable { ProxyWasm.builder().withPluginHandler(handler).withStart(false); private boolean shared = true; - public WasmPlugin.Builder withName(String name) { + public Plugin.Builder withName(String name) { this.handler.name = name; return this; } @@ -106,50 +109,50 @@ public Builder withLogger(Logger logger) { return this; } - public WasmPlugin.Builder withShared(boolean shared) { + public Plugin.Builder withShared(boolean shared) { this.shared = shared; return this; } - public WasmPlugin.Builder withVmConfig(byte[] vmConfig) { + public Plugin.Builder withVmConfig(byte[] vmConfig) { this.handler.vmConfig = vmConfig; return this; } - public WasmPlugin.Builder withVmConfig(String vmConfig) { + public Plugin.Builder withVmConfig(String vmConfig) { this.handler.vmConfig = bytes(vmConfig); return this; } - public WasmPlugin.Builder withPluginConfig(byte[] pluginConfig) { + public Plugin.Builder withPluginConfig(byte[] pluginConfig) { this.handler.pluginConfig = pluginConfig; return this; } - public WasmPlugin.Builder withPluginConfig(String pluginConfig) { + public Plugin.Builder withPluginConfig(String pluginConfig) { this.handler.pluginConfig = bytes(pluginConfig); return this; } - public WasmPlugin.Builder withImportMemory(ImportMemory memory) { + public Plugin.Builder withImportMemory(ImportMemory memory) { proxyWasmBuilder = proxyWasmBuilder.withImportMemory(memory); return this; } - public WasmPlugin build(WasmModule module) throws StartException { + public Plugin build(WasmModule module) throws StartException { return build(proxyWasmBuilder.build(module)); } - public WasmPlugin build(Instance.Builder instanceBuilder) throws StartException { + public Plugin build(Instance.Builder instanceBuilder) throws StartException { return build(proxyWasmBuilder.build(instanceBuilder)); } - public WasmPlugin build(Instance instance) throws StartException { + public Plugin build(Instance instance) throws StartException { return build(proxyWasmBuilder.build(instance)); } - public WasmPlugin build(ProxyWasm proxyWasm) throws StartException { - return new WasmPlugin(proxyWasm, handler, shared); + public Plugin build(ProxyWasm proxyWasm) throws StartException { + return new Plugin(proxyWasm, handler, shared); } } } diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/PluginFactory.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/PluginFactory.java new file mode 100644 index 0000000..e5b1a35 --- /dev/null +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/PluginFactory.java @@ -0,0 +1,7 @@ +package io.roastedroot.proxywasm.plugin; + +import io.roastedroot.proxywasm.StartException; + +public interface PluginFactory { + Plugin create() throws StartException; +} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/PluginHandler.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/PluginHandler.java similarity index 94% rename from proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/PluginHandler.java rename to proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/PluginHandler.java index 24aa2dc..a32c15c 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/PluginHandler.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/PluginHandler.java @@ -1,4 +1,4 @@ -package io.roastedroot.proxywasm.jaxrs; +package io.roastedroot.proxywasm.plugin; import static io.roastedroot.proxywasm.Helpers.bytes; import static io.roastedroot.proxywasm.WellKnownHeaders.AUTHORITY; @@ -17,8 +17,8 @@ import io.roastedroot.proxywasm.ProxyMap; import io.roastedroot.proxywasm.WasmException; import io.roastedroot.proxywasm.WasmResult; -import jakarta.ws.rs.core.UriBuilder; import java.net.URI; +import java.net.URISyntaxException; import java.util.HashMap; import java.util.List; import java.util.Objects; @@ -30,7 +30,7 @@ class PluginHandler extends ChainedHandler { // Filter Chain Methods // ////////////////////////////////////////////////////////////////////// private Handler next; - WasmPlugin plugin; + Plugin plugin; PluginHandler() { this(new Handler() {}); @@ -203,7 +203,7 @@ public WasmResult setFuncCallData(byte[] data) { return WasmResult.OK; } - public void setPlugin(WasmPlugin plugin) { + public void setPlugin(Plugin plugin) { this.plugin = plugin; } @@ -249,7 +249,13 @@ public int httpCall( connectHostPort = authority; } - var connectUri = UriBuilder.newInstance().scheme(scheme).host(connectHostPort).build(); + URI connectUri = null; + try { + connectUri = URI.create(scheme + "://" + connectHostPort); + } catch (IllegalArgumentException e) { + throw new WasmException(WasmResult.BAD_ARGUMENT); + } + var connectHost = connectUri.getHost(); var connectPort = connectUri.getPort(); if (connectPort == -1) { @@ -264,15 +270,14 @@ public int httpCall( path = "/" + path; } - var uri = - URI.create( - UriBuilder.newInstance() - .scheme(scheme) - .host(authority) - .port(connectPort) - .build() - .toString() - + path); + URI uri = null; + try { + uri = + URI.create( + new URI(scheme, null, authority, connectPort, null, null, null) + path); + } catch (IllegalArgumentException | URISyntaxException e) { + throw new WasmException(WasmResult.BAD_ARGUMENT); + } // Remove all the pseudo headers for (var r : new ArrayProxyMap(headers).entries()) { diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginPool.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Pool.java similarity index 58% rename from proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginPool.java rename to proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Pool.java index 4f4003e..2d7307f 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginPool.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/Pool.java @@ -1,31 +1,31 @@ -package io.roastedroot.proxywasm.jaxrs; +package io.roastedroot.proxywasm.plugin; import io.roastedroot.proxywasm.StartException; -import jakarta.annotation.PreDestroy; import java.util.Collection; import java.util.List; -public interface WasmPluginPool { +public interface Pool { - WasmPlugin borrow() throws StartException; + Plugin borrow() throws StartException; String name(); - void release(WasmPlugin plugin); + void release(Plugin plugin); - class AppScoped implements WasmPluginPool { - private final WasmPlugin plugin; + default void close() {} - public AppScoped(WasmPlugin plugin) throws StartException { + class AppScoped implements Pool { + private final Plugin plugin; + + public AppScoped(Plugin plugin) throws StartException { this.plugin = plugin; } - @PreDestroy public void close() { plugin.wasm.close(); } - public Collection getPluginPools() { + public Collection getPluginPools() { return List.of(plugin); } @@ -35,25 +35,25 @@ public String name() { } @Override - public void release(WasmPlugin plugin) { + public void release(Plugin plugin) { if (plugin != this.plugin) { throw new IllegalArgumentException("Plugin not from this pool"); } } @Override - public WasmPlugin borrow() throws StartException { + public Plugin borrow() throws StartException { plugin.wasm.start(); return plugin; } } - class RequestScoped implements WasmPluginPool { + class RequestScoped implements Pool { - final WasmPluginFactory factory; + final PluginFactory factory; private final String name; - public RequestScoped(WasmPluginFactory factory, WasmPlugin plugin) { + public RequestScoped(PluginFactory factory, Plugin plugin) { this.factory = factory; this.name = plugin.name(); release(plugin); @@ -65,15 +65,15 @@ public String name() { } @Override - public WasmPlugin borrow() throws StartException { - WasmPlugin plugin = factory.create(); + public Plugin borrow() throws StartException { + Plugin plugin = factory.create(); plugin.wasm.start(); return plugin; } // Return the plugin to the pool @Override - public void release(WasmPlugin plugin) { + public void release(Plugin plugin) { // TODO: maybe implementing pooling in the future to reduce GC pressure // but for now, we just close the plugin plugin.close(); diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/SendResponse.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/SendResponse.java new file mode 100644 index 0000000..b79b2b1 --- /dev/null +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/SendResponse.java @@ -0,0 +1,25 @@ +package io.roastedroot.proxywasm.plugin; + +import io.roastedroot.proxywasm.ProxyMap; + +public class SendResponse { + + public final int statusCode; + public final byte[] statusCodeDetails; + public final byte[] body; + public final ProxyMap headers; + public final int grpcStatus; + + public SendResponse( + int responseCode, + byte[] responseCodeDetails, + byte[] responseBody, + ProxyMap additionalHeaders, + int grpcStatus) { + this.statusCode = responseCode; + this.statusCodeDetails = responseCodeDetails; + this.body = responseBody; + this.headers = additionalHeaders; + this.grpcStatus = grpcStatus; + } +} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/spi/HttpServer.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/ServerAdaptor.java similarity index 93% rename from proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/spi/HttpServer.java rename to proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/ServerAdaptor.java index 8fdd7e0..1940555 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/spi/HttpServer.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/plugin/ServerAdaptor.java @@ -1,4 +1,4 @@ -package io.roastedroot.proxywasm.jaxrs.spi; +package io.roastedroot.proxywasm.plugin; import io.roastedroot.proxywasm.ProxyMap; import java.net.URI; @@ -6,7 +6,7 @@ /** * This interface will help us deal with differences in the http server impl. */ -public interface HttpServer { +public interface ServerAdaptor { Runnable scheduleTick(long delay, Runnable task); diff --git a/proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/App.java b/proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/App.java index ec7b877..31d18ff 100644 --- a/proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/App.java +++ b/proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/App.java @@ -4,8 +4,8 @@ import com.dylibso.chicory.wasm.WasmModule; import com.google.gson.Gson; import io.roastedroot.proxywasm.StartException; -import io.roastedroot.proxywasm.jaxrs.WasmPlugin; -import io.roastedroot.proxywasm.jaxrs.WasmPluginFactory; +import io.roastedroot.proxywasm.plugin.Plugin; +import io.roastedroot.proxywasm.plugin.PluginFactory; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; import java.nio.file.Path; @@ -21,48 +21,18 @@ public static WasmModule parseTestModule(String file) { return Parser.parse(Path.of(EXAMPLES_DIR + file)); } + // configure the ffiTests wasm plugin @Produces - public WasmPluginFactory headerTests() throws StartException { + public PluginFactory ffiTests() throws StartException { return () -> - WasmPlugin.builder() - .withName("headerTests") - .withLogger(new MockLogger("headerTests")) - .withPluginConfig(gson.toJson(Map.of("type", "headerTests"))) - .build(parseTestModule("/go-examples/unit_tester/main.wasm")); - } - - @Produces - public WasmPluginFactory headerTestsNotShared() throws StartException { - return () -> - WasmPlugin.builder() - .withName("headerTestsNotShared") - .withShared(false) - .withLogger(new MockLogger("headerTestsNotShared")) - .withPluginConfig(gson.toJson(Map.of("type", "headerTests"))) - .build(parseTestModule("/go-examples/unit_tester/main.wasm")); - } - - @Produces - public WasmPluginFactory tickTests() throws StartException { - return () -> - WasmPlugin.builder() - .withName("tickTests") - .withLogger(new MockLogger("tickTests")) - .withPluginConfig(gson.toJson(Map.of("type", "tickTests"))) - .build(parseTestModule("/go-examples/unit_tester/main.wasm")); - } - - @Produces - public WasmPluginFactory ffiTests() throws StartException { - return () -> - WasmPlugin.builder() + Plugin.builder() .withName("ffiTests") - .withLogger(new MockLogger("ffiTests")) - .withPluginConfig(gson.toJson(Map.of("type", "ffiTests"))) + .withPluginConfig("{ \"type\": \"ffiTests\" }") .withForeignFunctions(Map.of("reverse", App::reverse)) .build(parseTestModule("/go-examples/unit_tester/main.wasm")); } + // This function can be called from the Wasm module public static byte[] reverse(byte[] data) { byte[] reversed = new byte[data.length]; for (int i = 0; i < data.length; i++) { @@ -70,20 +40,4 @@ public static byte[] reverse(byte[] data) { } return reversed; } - - @Produces - public WasmPluginFactory httpCallTests() throws StartException { - return () -> - WasmPlugin.builder() - .withName("httpCallTests") - .withLogger(new MockLogger("httpCallTests")) - .withPluginConfig( - gson.toJson( - Map.of( - "type", "httpCallTests", - "upstream", "web_service", - "path", "/ok"))) - .withUpstreams(Map.of("web_service", "localhost:8081")) - .build(parseTestModule("/go-examples/unit_tester/main.wasm")); - } } diff --git a/proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/Resources.java b/proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/Resources.java index 51b1624..4ccec2e 100644 --- a/proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/Resources.java +++ b/proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/Resources.java @@ -1,72 +1,16 @@ package io.roastedroot.proxywasm.jaxrs.example; import io.roastedroot.proxywasm.jaxrs.NamedWasmPlugin; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.container.ContainerRequestContext; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.Response; @Path("/") public class Resources { - @Context ContainerRequestContext requestContext; - - @Path("/fail") - @GET - public Response fail() { - Response.ResponseBuilder builder = Response.status(Response.Status.BAD_REQUEST); - for (String header : requestContext.getHeaders().keySet()) { - builder.header("echo-" + header, requestContext.getHeaderString(header)); - } - return builder.build(); - } - - @Path("/ok") - @GET - public Response ok() { - Response.ResponseBuilder builder = Response.status(Response.Status.OK); - for (String header : requestContext.getHeaders().keySet()) { - builder.header("echo-" + header, requestContext.getHeaderString(header)); - } - return builder.entity("ok").build(); - } - - @Path("/headerTests") - @GET - @NamedWasmPlugin("headerTests") - public String uhttpHeaders(@HeaderParam("x-request-counter") String counter) { - return String.format("counter: %s", counter); - } - - @Path("/headerTestsNotShared") - @GET - @NamedWasmPlugin("headerTestsNotShared") - public String unotSharedHttpHeaders(@HeaderParam("x-request-counter") String counter) { - return String.format("counter: %s", counter); - } - - @Path("/tickTests/{sub: .+ }") - @GET - @NamedWasmPlugin("tickTests") - public String tickTests(@PathParam("sub") String sub) { - return "hello world"; - } - @Path("/ffiTests/reverse") @POST - @NamedWasmPlugin("ffiTests") + @NamedWasmPlugin("ffiTests") // filter with ffiTests wasm plugin public String ffiTests(String body) { return body; } - - @Path("/httpCallTests") - @GET - @NamedWasmPlugin("httpCallTests") - public String httpCallTests() { - return "hello world"; - } } diff --git a/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Helpers.java b/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Helpers.java deleted file mode 100644 index 9c54d15..0000000 --- a/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Helpers.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.roastedroot.proxywasm.jaxrs.example; - -import java.util.ArrayList; -import org.junit.jupiter.api.Assertions; - -public class Helpers { - private Helpers() {} - - public static void assertLogsContain(ArrayList loggedMessages, String... message) { - for (String m : message) { - Assertions.assertTrue( - loggedMessages.contains(m), "logged messages does not contain: " + m); - } - } - - public static IsTrueMatcher isTrue(IsTrueMatcher.Predicate predicate) { - return new IsTrueMatcher(predicate); - } -} diff --git a/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/IsTrueMatcher.java b/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/IsTrueMatcher.java deleted file mode 100644 index 3792521..0000000 --- a/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/IsTrueMatcher.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.roastedroot.proxywasm.jaxrs.example; - -import org.hamcrest.Description; -import org.hamcrest.TypeSafeMatcher; - -public class IsTrueMatcher extends TypeSafeMatcher { - - public interface Predicate { - boolean matchesSafely(T value); - } - - Predicate predicate; - - public IsTrueMatcher(Predicate predicate) { - this.predicate = predicate; - } - - @Override - protected boolean matchesSafely(T item) { - return predicate.matchesSafely(item); - } - - @Override - public void describeTo(Description description) { - description.appendText("is not true"); - } -} diff --git a/proxy-wasm-jaxrs-quarkus/pom.xml b/proxy-wasm-jaxrs-quarkus/pom.xml index 7fa2311..74275f6 100644 --- a/proxy-wasm-jaxrs-quarkus/pom.xml +++ b/proxy-wasm-jaxrs-quarkus/pom.xml @@ -38,17 +38,35 @@ io.quarkus quarkus-jaxrs-spi-deployment - io.roastedroot proxy-wasm-jaxrs ${project.version} + + com.google.code.gson + gson + 2.12.1 + test + + + io.quarkus - quarkus-junit5-internal + quarkus-junit5 test + + io.quarkus + quarkus-rest-jackson + test + + + io.rest-assured + rest-assured + test + + diff --git a/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/App.java b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/App.java new file mode 100644 index 0000000..9980a03 --- /dev/null +++ b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/App.java @@ -0,0 +1,89 @@ +package io.roastedroot.proxywasm.jaxrs.example; + +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.WasmModule; +import com.google.gson.Gson; +import io.roastedroot.proxywasm.StartException; +import io.roastedroot.proxywasm.plugin.Plugin; +import io.roastedroot.proxywasm.plugin.PluginFactory; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import java.nio.file.Path; +import java.util.Map; + +@ApplicationScoped +public class App { + + public static final String EXAMPLES_DIR = "../proxy-wasm-java-host/src/test"; + private static final Gson gson = new Gson(); + + public static WasmModule parseTestModule(String file) { + return Parser.parse(Path.of(EXAMPLES_DIR + file)); + } + + @Produces + public PluginFactory headerTests() throws StartException { + return () -> + Plugin.builder() + .withName("headerTests") + .withLogger(new MockLogger("headerTests")) + .withPluginConfig(gson.toJson(Map.of("type", "headerTests"))) + .build(parseTestModule("/go-examples/unit_tester/main.wasm")); + } + + @Produces + public PluginFactory headerTestsNotShared() throws StartException { + return () -> + Plugin.builder() + .withName("headerTestsNotShared") + .withShared(false) + .withLogger(new MockLogger("headerTestsNotShared")) + .withPluginConfig(gson.toJson(Map.of("type", "headerTests"))) + .build(parseTestModule("/go-examples/unit_tester/main.wasm")); + } + + @Produces + public PluginFactory tickTests() throws StartException { + return () -> + Plugin.builder() + .withName("tickTests") + .withLogger(new MockLogger("tickTests")) + .withPluginConfig(gson.toJson(Map.of("type", "tickTests"))) + .build(parseTestModule("/go-examples/unit_tester/main.wasm")); + } + + @Produces + public PluginFactory ffiTests() throws StartException { + return () -> + Plugin.builder() + .withName("ffiTests") + .withLogger(new MockLogger("ffiTests")) + .withPluginConfig(gson.toJson(Map.of("type", "ffiTests"))) + .withForeignFunctions(Map.of("reverse", App::reverse)) + .build(parseTestModule("/go-examples/unit_tester/main.wasm")); + } + + public static byte[] reverse(byte[] data) { + byte[] reversed = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + reversed[i] = data[data.length - 1 - i]; + } + return reversed; + } + + @Produces + public PluginFactory httpCallTests() throws StartException { + return () -> + Plugin.builder() + .withName("httpCallTests") + .withLogger(new MockLogger("httpCallTests")) + .withPluginConfig( + gson.toJson( + Map.of( + "type", "httpCallTests", + "upstream", "web_service", + "path", "/ok"))) + .withUpstreams(Map.of("web_service", "localhost:8081")) + .build(parseTestModule("/go-examples/unit_tester/main.wasm")); + } +} diff --git a/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Helpers.java b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Helpers.java new file mode 100644 index 0000000..0719367 --- /dev/null +++ b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Helpers.java @@ -0,0 +1,44 @@ +package io.roastedroot.proxywasm.jaxrs.example; + +import java.util.ArrayList; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.junit.jupiter.api.Assertions; + +public class Helpers { + private Helpers() {} + + public static void assertLogsContain(ArrayList loggedMessages, String... message) { + for (String m : message) { + Assertions.assertTrue( + loggedMessages.contains(m), "logged messages does not contain: " + m); + } + } + + public static TypeSafeMatcher isTrue(IsTrueMatcher.Predicate predicate) { + return new IsTrueMatcher(predicate); + } + + public static class IsTrueMatcher extends TypeSafeMatcher { + + public interface Predicate { + boolean matchesSafely(T value); + } + + Predicate predicate; + + public IsTrueMatcher(Predicate predicate) { + this.predicate = predicate; + } + + @Override + protected boolean matchesSafely(T item) { + return predicate.matchesSafely(item); + } + + @Override + public void describeTo(Description description) { + description.appendText("is not true"); + } + } +} diff --git a/proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/MockLogger.java b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/MockLogger.java similarity index 94% rename from proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/MockLogger.java rename to proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/MockLogger.java index 68b9229..33799f8 100644 --- a/proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/MockLogger.java +++ b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/MockLogger.java @@ -1,7 +1,7 @@ package io.roastedroot.proxywasm.jaxrs.example; import io.roastedroot.proxywasm.LogLevel; -import io.roastedroot.proxywasm.jaxrs.Logger; +import io.roastedroot.proxywasm.plugin.Logger; import java.util.ArrayList; public class MockLogger implements Logger { diff --git a/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Resources.java b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Resources.java new file mode 100644 index 0000000..51b1624 --- /dev/null +++ b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/Resources.java @@ -0,0 +1,72 @@ +package io.roastedroot.proxywasm.jaxrs.example; + +import io.roastedroot.proxywasm.jaxrs.NamedWasmPlugin; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; + +@Path("/") +public class Resources { + + @Context ContainerRequestContext requestContext; + + @Path("/fail") + @GET + public Response fail() { + Response.ResponseBuilder builder = Response.status(Response.Status.BAD_REQUEST); + for (String header : requestContext.getHeaders().keySet()) { + builder.header("echo-" + header, requestContext.getHeaderString(header)); + } + return builder.build(); + } + + @Path("/ok") + @GET + public Response ok() { + Response.ResponseBuilder builder = Response.status(Response.Status.OK); + for (String header : requestContext.getHeaders().keySet()) { + builder.header("echo-" + header, requestContext.getHeaderString(header)); + } + return builder.entity("ok").build(); + } + + @Path("/headerTests") + @GET + @NamedWasmPlugin("headerTests") + public String uhttpHeaders(@HeaderParam("x-request-counter") String counter) { + return String.format("counter: %s", counter); + } + + @Path("/headerTestsNotShared") + @GET + @NamedWasmPlugin("headerTestsNotShared") + public String unotSharedHttpHeaders(@HeaderParam("x-request-counter") String counter) { + return String.format("counter: %s", counter); + } + + @Path("/tickTests/{sub: .+ }") + @GET + @NamedWasmPlugin("tickTests") + public String tickTests(@PathParam("sub") String sub) { + return "hello world"; + } + + @Path("/ffiTests/reverse") + @POST + @NamedWasmPlugin("ffiTests") + public String ffiTests(String body) { + return body; + } + + @Path("/httpCallTests") + @GET + @NamedWasmPlugin("httpCallTests") + public String httpCallTests() { + return "hello world"; + } +} diff --git a/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/FFITest.java b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/FFITest.java new file mode 100644 index 0000000..73c4e57 --- /dev/null +++ b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/FFITest.java @@ -0,0 +1,22 @@ +package io.roastedroot.proxywasm.jaxrs.example.tests; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +@QuarkusTest +public class FFITest { + + @Test + public void reverse() throws InterruptedException { + + given().body("My Test") + .when() + .post("/ffiTests/reverse") + .then() + .statusCode(200) + .body(equalTo("tseT yM")); + } +} diff --git a/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/HeadersTest.java b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HeadersTest.java similarity index 96% rename from proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/HeadersTest.java rename to proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HeadersTest.java index fbb1a95..d4d80ce 100644 --- a/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/HeadersTest.java +++ b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HeadersTest.java @@ -1,4 +1,4 @@ -package io.roastedroot.proxywasm.jaxrs.example; +package io.roastedroot.proxywasm.jaxrs.example.tests; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.equalTo; diff --git a/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/HttpCallTests.java b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java similarity index 80% rename from proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/HttpCallTests.java rename to proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java index c73453b..44b9479 100644 --- a/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/HttpCallTests.java +++ b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java @@ -1,4 +1,4 @@ -package io.roastedroot.proxywasm.jaxrs.example; +package io.roastedroot.proxywasm.jaxrs.example.tests; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.equalTo; @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; @QuarkusTest -public class HttpCallTests { +public class HttpCallTest { @Inject WasmPluginFeature feature; @@ -23,7 +23,7 @@ public void test() throws InterruptedException, StartException { .get("/httpCallTests") .then() .statusCode(200) - .header("echo-test", "ok") - .body(equalTo("ok")); + .body(equalTo("ok")) + .header("echo-test", "ok"); } } diff --git a/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/TickTest.java b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/TickTest.java similarity index 96% rename from proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/TickTest.java rename to proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/TickTest.java index 950bd1d..536725d 100644 --- a/proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/TickTest.java +++ b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/TickTest.java @@ -1,4 +1,4 @@ -package io.roastedroot.proxywasm.jaxrs.example; +package io.roastedroot.proxywasm.jaxrs.example.tests; import static io.restassured.RestAssured.given; import static io.roastedroot.proxywasm.jaxrs.example.Helpers.isTrue; diff --git a/proxy-wasm-jaxrs-quarkus/src/test/resources/application.properties b/proxy-wasm-jaxrs-quarkus/src/test/resources/application.properties new file mode 100644 index 0000000..6e516f2 --- /dev/null +++ b/proxy-wasm-jaxrs-quarkus/src/test/resources/application.properties @@ -0,0 +1,2 @@ +quarkus.log.level=INFO +quarkus.log.category."org.hibernate".level=DEBUG diff --git a/proxy-wasm-jaxrs/pom.xml b/proxy-wasm-jaxrs/pom.xml index 4bd3778..700c614 100644 --- a/proxy-wasm-jaxrs/pom.xml +++ b/proxy-wasm-jaxrs/pom.xml @@ -86,22 +86,6 @@ provided - - - io.quarkus - quarkus-junit5 - test - - - io.quarkus - quarkus-rest-jackson - test - - - io.rest-assured - rest-assured - test - diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/HttpHandler.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHttpRequestAdaptor.java similarity index 66% rename from proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/HttpHandler.java rename to proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHttpRequestAdaptor.java index 9586b7a..47a141d 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/HttpHandler.java +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHttpRequestAdaptor.java @@ -41,49 +41,26 @@ import static io.roastedroot.proxywasm.WellKnownProperties.SOURCE_ADDRESS; import static io.roastedroot.proxywasm.WellKnownProperties.SOURCE_PORT; -import io.roastedroot.proxywasm.Action; -import io.roastedroot.proxywasm.ChainedHandler; -import io.roastedroot.proxywasm.Handler; -import io.roastedroot.proxywasm.Helpers; import io.roastedroot.proxywasm.ProxyMap; -import io.roastedroot.proxywasm.StreamType; import io.roastedroot.proxywasm.WasmException; import io.roastedroot.proxywasm.WasmResult; import io.roastedroot.proxywasm.WellKnownProperties; -import io.roastedroot.proxywasm.jaxrs.spi.HttpServerRequest; +import io.roastedroot.proxywasm.plugin.HttpContext; +import io.roastedroot.proxywasm.plugin.HttpRequestAdaptor; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerResponseContext; -import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import java.net.URI; import java.time.Duration; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.concurrent.CountDownLatch; -class HttpHandler extends ChainedHandler { - - private final PluginHandler next; - private final HttpServerRequest httpServer; - private final long startedAt; - - HttpHandler(PluginHandler pluginHandler, HttpServerRequest httpServer) { - this.next = pluginHandler; - this.httpServer = httpServer; - this.startedAt = System.currentTimeMillis(); - } - - @Override - protected Handler next() { - return next; - } - - // ////////////////////////////////////////////////////////////////////// - // HTTP fields - // ////////////////////////////////////////////////////////////////////// +public abstract class JaxrsHttpRequestAdaptor implements HttpRequestAdaptor { private ContainerRequestContext requestContext; + private ContainerResponseContext responseContext; + + private final long startedAt = System.currentTimeMillis(); public ContainerRequestContext getRequestContext() { return requestContext; @@ -93,9 +70,21 @@ public void setRequestContext(ContainerRequestContext requestContext) { this.requestContext = requestContext; } + public ContainerResponseContext getResponseContext() { + return responseContext; + } + + public void setResponseContext(ContainerResponseContext responseContext) { + this.responseContext = responseContext; + } + + // ////////////////////////////////////////////////////////////////////// + // HTTP fields + // ////////////////////////////////////////////////////////////////////// + @Override public ProxyMap getHttpRequestHeaders() { - return new JaxrsProxyMap(requestContext.getHeaders()); + return new MultivaluedMapAdaptor<>(requestContext.getHeaders()); } @Override @@ -103,15 +92,9 @@ public ProxyMap getHttpRequestTrailers() { return null; } - private ContainerResponseContext responseContext; - - public void setResponseContext(ContainerResponseContext responseContext) { - this.responseContext = responseContext; - } - @Override public ProxyMap getHttpResponseHeaders() { - return new JaxrsProxyMap(responseContext.getHeaders()); + return new MultivaluedMapAdaptor<>(responseContext.getHeaders()); } @Override @@ -129,201 +112,8 @@ public ProxyMap getGrpcReceiveTrailerMetaData() { return null; } - // ////////////////////////////////////////////////////////////////////// - // Buffers - // ////////////////////////////////////////////////////////////////////// - - private byte[] httpRequestBody = new byte[0]; - - @Override - public byte[] getHttpRequestBody() { - return this.httpRequestBody; - } - - @Override - public WasmResult setHttpRequestBody(byte[] body) { - this.httpRequestBody = body; - return WasmResult.OK; - } - - public void appendHttpRequestBody(byte[] body) { - this.httpRequestBody = Helpers.append(this.httpRequestBody, body); - } - - private byte[] grpcReceiveBuffer = new byte[0]; - - @Override - public byte[] getGrpcReceiveBuffer() { - return this.grpcReceiveBuffer; - } - - @Override - public WasmResult setGrpcReceiveBuffer(byte[] buffer) { - this.grpcReceiveBuffer = buffer; - return WasmResult.OK; - } - - private byte[] upstreamData = new byte[0]; - @Override - public byte[] getUpstreamData() { - return this.upstreamData; - } - - @Override - public WasmResult setUpstreamData(byte[] data) { - this.upstreamData = data; - return WasmResult.OK; - } - - private byte[] downStreamData = new byte[0]; - - @Override - public byte[] getDownStreamData() { - return this.downStreamData; - } - - @Override - public WasmResult setDownStreamData(byte[] data) { - this.downStreamData = data; - return WasmResult.OK; - } - - private byte[] httpResponseBody = new byte[0]; - - @Override - public byte[] getHttpResponseBody() { - return this.httpResponseBody; - } - - @Override - public WasmResult setHttpResponseBody(byte[] body) { - this.httpResponseBody = body; - return WasmResult.OK; - } - - public void appendHttpResponseBody(byte[] body) { - this.httpResponseBody = Helpers.append(this.httpResponseBody, body); - } - - // ////////////////////////////////////////////////////////////////////// - // HTTP streams - // ////////////////////////////////////////////////////////////////////// - - public static class HttpResponse { - - public final int statusCode; - public final byte[] statusCodeDetails; - public final byte[] body; - public final ProxyMap headers; - public final int grpcStatus; - - public HttpResponse( - int responseCode, - byte[] responseCodeDetails, - byte[] responseBody, - ProxyMap additionalHeaders, - int grpcStatus) { - this.statusCode = responseCode; - this.statusCodeDetails = responseCodeDetails; - this.body = responseBody; - this.headers = additionalHeaders; - this.grpcStatus = grpcStatus; - } - - public Response toResponse() { - Response.ResponseBuilder builder = - Response.status(statusCode, string(statusCodeDetails)); - if (headers != null) { - for (var entry : headers.entries()) { - builder = builder.header(entry.getKey(), entry.getValue()); - } - } - builder.entity(body); - return builder.build(); - } - } - - private HttpResponse senthttpResponse; - - @Override - public WasmResult sendHttpResponse( - int responseCode, - byte[] responseCodeDetails, - byte[] responseBody, - ProxyMap additionalHeaders, - int grpcStatus) { - this.senthttpResponse = - new HttpResponse( - responseCode, - responseCodeDetails, - responseBody, - additionalHeaders, - grpcStatus); - - if (resumeLatch != null) { - resumeLatch.countDown(); - } - return WasmResult.OK; - } - - public HttpResponse getSentHttpResponse() { - return senthttpResponse; - } - - public HttpResponse consumeSentHttpResponse() { - var result = senthttpResponse; - senthttpResponse = null; - return result; - } - - private Action action; - private CountDownLatch resumeLatch; - - @Override - public WasmResult setAction(StreamType streamType, Action action) { - this.action = action; - if (this.action == Action.CONTINUE && resumeLatch != null) { - resumeLatch.countDown(); - } - return WasmResult.OK; - } - - public Action getAction() { - return action; - } - - void maybePause(WasmPlugin plugin) { - // don't pause if plugin wants us to continue - if (action == Action.CONTINUE) { - return; - } - // don't pause if the plugin wants to respond to the request - if (senthttpResponse != null) { - return; - } - - // ok, lets pause the request processing. A tick or http call response event will - // need to resume the processing - resumeLatch = new CountDownLatch(1); - plugin.unlock(); - try { - resumeLatch.await(); - } catch (InterruptedException ignore) { - } finally { - plugin.lock(); - resumeLatch = null; - } - } - - // ////////////////////////////////////////////////////////////////////// - // Properties - // ////////////////////////////////////////////////////////////////////// - - final HashMap, byte[]> properties = new HashMap<>(); - - @Override - public byte[] getProperty(List path) throws WasmException { + public byte[] getProperty(HttpContext pluginRequest, List path) throws WasmException { // Check to see if it's a well known property @@ -332,13 +122,13 @@ public byte[] getProperty(List path) throws WasmException { // Do we need to generate one? return null; } else if (SOURCE_ADDRESS.equals(path)) { - return bytes(httpServer.remoteAddress()); + return bytes(remoteAddress()); } else if (SOURCE_PORT.equals(path)) { - return bytes(httpServer.remotePort()); + return bytes(remotePort()); } else if (DESTINATION_ADDRESS.equals(path)) { - return bytes(httpServer.localAddress()); + return bytes(localAddress()); } else if (DESTINATION_PORT.equals(path)) { - return bytes(httpServer.localPort()); + return bytes(localPort()); } // TODO: get TLS connection properties @@ -447,6 +237,7 @@ else if (REQUEST_PROTOCOL.equals(path)) { } else if (REQUEST_DURATION.equals(path)) { return bytes(Duration.ofMillis((System.currentTimeMillis() - startedAt))); } else if (REQUEST_SIZE.equals(path)) { + var httpRequestBody = pluginRequest.getHttpRequestBody(); if (httpRequestBody == null) { return null; } @@ -458,14 +249,8 @@ else if (REQUEST_PROTOCOL.equals(path)) { // HTTP response properties else if (RESPONSE_CODE.equals(path)) { - if (responseContext == null) { - return null; - } return bytes(responseContext.getStatus()); } else if (RESPONSE_CODE_DETAILS.equals(path)) { - if (responseContext == null) { - return null; - } return bytes(responseContext.getStatusInfo().getReasonPhrase()); } else if (RESPONSE_FLAGS.equals(path)) { // TODO: implement response flags retrieval @@ -489,6 +274,7 @@ else if (RESPONSE_CODE.equals(path)) { // TODO: implement backend latency retrieval return null; } else if (RESPONSE_SIZE.equals(path)) { + var httpResponseBody = pluginRequest.getHttpResponseBody(); if (httpResponseBody == null) { return null; } @@ -498,15 +284,11 @@ else if (RESPONSE_CODE.equals(path)) { return null; } - byte[] result = properties.get(path); - if (result != null) { - return result; - } - return next().getProperty(path); + return null; } @Override - public WasmResult setProperty(List path, byte[] value) { + public WasmResult setProperty(HttpContext pluginRequest, List path, byte[] value) { // Check to see if it's a well known property if (REQUEST_PATH.equals(path)) { @@ -589,9 +371,6 @@ else if (REQUEST_PROTOCOL.equals(path)) { // HTTP response properties else if (RESPONSE_CODE.equals(path)) { - if (responseContext == null) { - return null; - } responseContext.setStatus(int32(value)); } else if (RESPONSE_CODE_DETAILS.equals(path)) { // TODO: @@ -599,9 +378,9 @@ else if (RESPONSE_CODE.equals(path)) { // TODO: } else if (RESPONSE_TRAILERS.equals(path)) { // TODO: + } else { + return WasmResult.NOT_FOUND; } - - properties.put(path, value); return WasmResult.OK; } } diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsProxyMap.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/MultivaluedMapAdaptor.java similarity index 88% rename from proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsProxyMap.java rename to proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/MultivaluedMapAdaptor.java index c8db392..513e2e4 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsProxyMap.java +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/MultivaluedMapAdaptor.java @@ -8,26 +8,26 @@ import java.util.Objects; import java.util.stream.Stream; -public class JaxrsProxyMap implements ProxyMap { +public class MultivaluedMapAdaptor implements ProxyMap { final MultivaluedMap entries; - public JaxrsProxyMap() { + public MultivaluedMapAdaptor() { this.entries = new MultivaluedHashMap<>(); } - public JaxrsProxyMap(int mapSize) { + public MultivaluedMapAdaptor(int mapSize) { this.entries = new MultivaluedHashMap<>(); } - public JaxrsProxyMap(ProxyMap other) { + public MultivaluedMapAdaptor(ProxyMap other) { this(other.size()); for (Map.Entry entry : other.entries()) { add(entry.getKey(), entry.getValue()); } } - public JaxrsProxyMap(MultivaluedMap other) { + public MultivaluedMapAdaptor(MultivaluedMap other) { this.entries = other; } @@ -89,7 +89,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - JaxrsProxyMap that = (JaxrsProxyMap) o; + MultivaluedMapAdaptor that = (MultivaluedMapAdaptor) o; return Objects.equals(entries, that.entries); } diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/ProxyWasmFilter.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/ProxyWasmFilter.java index e56a6f8..36aec35 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/ProxyWasmFilter.java +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/ProxyWasmFilter.java @@ -1,9 +1,13 @@ package io.roastedroot.proxywasm.jaxrs; +import static io.roastedroot.proxywasm.Helpers.string; + import io.roastedroot.proxywasm.Action; -import io.roastedroot.proxywasm.HttpContext; import io.roastedroot.proxywasm.StartException; -import io.roastedroot.proxywasm.jaxrs.spi.HttpServerRequest; +import io.roastedroot.proxywasm.plugin.HttpContext; +import io.roastedroot.proxywasm.plugin.Plugin; +import io.roastedroot.proxywasm.plugin.Pool; +import io.roastedroot.proxywasm.plugin.SendResponse; import jakarta.enterprise.inject.Instance; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.container.ContainerRequestContext; @@ -18,41 +22,21 @@ public class ProxyWasmFilter implements ContainerRequestFilter, WriterInterceptor, ContainerResponseFilter { - private static final String FILTER_CONTEXT_PROPERTY_NAME = "WasmHttpFilterContext"; + private static final String FILTER_CONTEXT_PROPERTY_NAME = HttpContext.class.getName(); - private final WasmPluginPool pluginPool; + private final Pool pluginPool; - Instance httpServer; + Instance requestAdaptor; - public ProxyWasmFilter(WasmPluginPool pluginPool, Instance httpServer) { + public ProxyWasmFilter(Pool pluginPool, Instance httpServer) { this.pluginPool = pluginPool; - this.httpServer = httpServer; - } - - String name() { - return pluginPool.name(); - } - - // TODO: the HttpContext and ProxyWasm object's should be closed once the request is done. - // is there an easy way to hook up cleanup code for this? - static class WasmHttpFilterContext { - final WasmPlugin plugin; - final PluginHandler pluginHandler; - final HttpHandler httpHandler; - final HttpContext httpContext; - - public WasmHttpFilterContext(WasmPlugin plugin, HttpServerRequest httpServer) { - this.plugin = plugin; - this.pluginHandler = plugin.handler; - this.httpHandler = new HttpHandler(plugin.handler, httpServer); - this.httpContext = plugin.wasm.createHttpContext(this.httpHandler); - } + this.requestAdaptor = httpServer; } @Override public void filter(ContainerRequestContext requestContext) throws IOException { - WasmPlugin plugin; + Plugin plugin; try { plugin = pluginPool.borrow(); } catch (StartException e) { @@ -62,55 +46,47 @@ public void filter(ContainerRequestContext requestContext) throws IOException { plugin.lock(); try { - var wasmHttpFilterContext = new WasmHttpFilterContext(plugin, this.httpServer.get()); - requestContext.setProperty(FILTER_CONTEXT_PROPERTY_NAME, wasmHttpFilterContext); + var requestAdaptor = this.requestAdaptor.get(); + var httpContext = plugin.createHttpContext(requestAdaptor); + requestContext.setProperty(FILTER_CONTEXT_PROPERTY_NAME, httpContext); // the plugin may not be interested in the request headers. - if (wasmHttpFilterContext.httpContext.hasOnRequestHeaders()) { + if (httpContext.context().hasOnRequestHeaders()) { - wasmHttpFilterContext.httpHandler.setRequestContext(requestContext); - var action = wasmHttpFilterContext.httpContext.callOnRequestHeaders(false); - if (action == Action.CONTINUE) { - // continue means plugin is done reading the headers. - wasmHttpFilterContext.httpHandler.setRequestContext(null); - } else { - wasmHttpFilterContext.httpHandler.maybePause(wasmHttpFilterContext.plugin); + requestAdaptor.setRequestContext(requestContext); + var action = httpContext.context().callOnRequestHeaders(false); + if (action == Action.PAUSE) { + httpContext.maybePause(); } // does the plugin want to respond early? - HttpHandler.HttpResponse sendResponse = - wasmHttpFilterContext.httpHandler.consumeSentHttpResponse(); + var sendResponse = httpContext.consumeSentHttpResponse(); if (sendResponse != null) { - requestContext.abortWith(sendResponse.toResponse()); + requestContext.abortWith(toResponse(sendResponse)); } } // the plugin may not be interested in the request body. - if (wasmHttpFilterContext.httpContext.hasOnRequestBody()) { + if (httpContext.context().hasOnRequestBody()) { // TODO: find out if it's more efficient to read the body in chunks and do multiple // callOnRequestBody calls. byte[] bytes = requestContext.getEntityStream().readAllBytes(); - wasmHttpFilterContext.httpHandler.setHttpRequestBody(bytes); - var action = wasmHttpFilterContext.httpContext.callOnRequestBody(true); - bytes = wasmHttpFilterContext.httpHandler.getHttpRequestBody(); + httpContext.setHttpRequestBody(bytes); + var action = httpContext.context().callOnRequestBody(true); + bytes = httpContext.getHttpRequestBody(); if (action == Action.CONTINUE) { // continue means plugin is done reading the body. - wasmHttpFilterContext.httpHandler.setHttpRequestBody(null); + httpContext.setHttpRequestBody(null); } else { - wasmHttpFilterContext.httpHandler.maybePause(wasmHttpFilterContext.plugin); + httpContext.maybePause(); } - // TODO: find out more details about what to do here in a PAUSE condition. - // does it mean that we park the request here and wait for another event like - // tick to resume us before forwarding to the next filter? - // does the plugin want to respond early? - HttpHandler.HttpResponse sendResponse = - wasmHttpFilterContext.httpHandler.consumeSentHttpResponse(); + var sendResponse = httpContext.consumeSentHttpResponse(); if (sendResponse != null) { - throw new WebApplicationException(sendResponse.toResponse()); + throw new WebApplicationException(toResponse(sendResponse)); } // plugin may have modified the body @@ -130,38 +106,34 @@ private static Response interalServerError() { public void filter( ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { - var wasmHttpFilterContext = - (WasmHttpFilterContext) requestContext.getProperty(FILTER_CONTEXT_PROPERTY_NAME); - if (wasmHttpFilterContext == null) { + var httpContext = (HttpContext) requestContext.getProperty(FILTER_CONTEXT_PROPERTY_NAME); + if (httpContext == null) { throw new WebApplicationException(interalServerError()); } // the plugin may not be interested in the request headers. - if (wasmHttpFilterContext.httpContext.hasOnResponseHeaders()) { + if (httpContext.context().hasOnResponseHeaders()) { try { - wasmHttpFilterContext.plugin.lock(); + httpContext.plugin().lock(); - wasmHttpFilterContext.httpHandler.setResponseContext(responseContext); - var action = wasmHttpFilterContext.httpContext.callOnResponseHeaders(false); - if (action == Action.CONTINUE) { - // continue means plugin is done reading the headers. - wasmHttpFilterContext.httpHandler.setResponseContext(null); - } else { - wasmHttpFilterContext.httpHandler.maybePause(wasmHttpFilterContext.plugin); + var requestAdaptor = this.requestAdaptor.get(); + requestAdaptor.setResponseContext(responseContext); + var action = httpContext.context().callOnResponseHeaders(false); + if (action == Action.PAUSE) { + httpContext.maybePause(); } // does the plugin want to respond early? - HttpHandler.HttpResponse sendResponse = - wasmHttpFilterContext.httpHandler.consumeSentHttpResponse(); + var sendResponse = httpContext.consumeSentHttpResponse(); if (sendResponse != null) { - Response response = sendResponse.toResponse(); + Response response = toResponse(sendResponse); responseContext.setStatus(response.getStatus()); responseContext.getHeaders().putAll(response.getHeaders()); responseContext.setEntity(response.getEntity()); } } finally { // allow other request to use the plugin. - wasmHttpFilterContext.plugin.unlock(); + httpContext.plugin().unlock(); } } } @@ -169,16 +141,15 @@ public void filter( @Override public void aroundWriteTo(WriterInterceptorContext ctx) throws IOException, WebApplicationException { - var wasmHttpFilterContext = - (WasmHttpFilterContext) ctx.getProperty(FILTER_CONTEXT_PROPERTY_NAME); - if (wasmHttpFilterContext == null) { + var httpContext = (HttpContext) ctx.getProperty(FILTER_CONTEXT_PROPERTY_NAME); + if (httpContext == null) { throw new WebApplicationException(interalServerError()); } try { // the plugin may not be interested in the request body. - if (wasmHttpFilterContext.httpContext.hasOnResponseBody()) { + if (httpContext.context().hasOnResponseBody()) { var original = ctx.getOutputStream(); ctx.setOutputStream( new ByteArrayOutputStream() { @@ -199,25 +170,22 @@ public void close() throws IOException { byte[] bytes = this.toByteArray(); - wasmHttpFilterContext.plugin.lock(); + httpContext.plugin().lock(); - wasmHttpFilterContext.httpHandler.setHttpResponseBody(bytes); - var action = - wasmHttpFilterContext.httpContext.callOnResponseBody(false); - bytes = wasmHttpFilterContext.httpHandler.getHttpResponseBody(); + httpContext.setHttpResponseBody(bytes); + var action = httpContext.context().callOnResponseBody(false); + bytes = httpContext.getHttpResponseBody(); if (action == Action.CONTINUE) { // continue means plugin is done reading the body. - wasmHttpFilterContext.httpHandler.setHttpResponseBody(null); + httpContext.setHttpResponseBody(null); } else { - wasmHttpFilterContext.httpHandler.maybePause( - wasmHttpFilterContext.plugin); + httpContext.maybePause(); } // does the plugin want to respond early? - HttpHandler.HttpResponse sendResponse = - wasmHttpFilterContext.httpHandler.consumeSentHttpResponse(); + var sendResponse = httpContext.consumeSentHttpResponse(); if (sendResponse != null) { - throw new WebApplicationException(sendResponse.toResponse()); + throw new WebApplicationException(toResponse(sendResponse)); } // plugin may have modified the body @@ -230,11 +198,23 @@ public void close() throws IOException { ctx.proceed(); } finally { // allow other request to use the plugin. - wasmHttpFilterContext.httpContext.close(); - wasmHttpFilterContext.plugin.unlock(); + httpContext.context().close(); + httpContext.plugin().unlock(); // TODO: will aroundWriteTo always get called so that we can avoid leaking the plugin? - this.pluginPool.release(wasmHttpFilterContext.plugin); + this.pluginPool.release(httpContext.plugin()); + } + } + + public Response toResponse(SendResponse other) { + Response.ResponseBuilder builder = + Response.status(other.statusCode, string(other.statusCodeDetails)); + if (other.headers != null) { + for (var entry : other.headers.entries()) { + builder = builder.header(entry.getKey(), entry.getValue()); + } } + builder.entity(other.body); + return builder.build(); } } diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFactory.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFactory.java deleted file mode 100644 index b298fc4..0000000 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFactory.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.roastedroot.proxywasm.jaxrs; - -import io.roastedroot.proxywasm.StartException; - -public interface WasmPluginFactory { - WasmPlugin create() throws StartException; -} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFeature.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFeature.java index ac120bf..0ca1b80 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFeature.java +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFeature.java @@ -1,8 +1,11 @@ package io.roastedroot.proxywasm.jaxrs; import io.roastedroot.proxywasm.StartException; -import io.roastedroot.proxywasm.jaxrs.spi.HttpServer; -import io.roastedroot.proxywasm.jaxrs.spi.HttpServerRequest; +import io.roastedroot.proxywasm.plugin.Plugin; +import io.roastedroot.proxywasm.plugin.PluginFactory; +import io.roastedroot.proxywasm.plugin.Pool; +import io.roastedroot.proxywasm.plugin.ServerAdaptor; +import jakarta.annotation.PreDestroy; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Any; import jakarta.enterprise.inject.Instance; @@ -18,34 +21,41 @@ @ApplicationScoped public class WasmPluginFeature implements DynamicFeature { - private final HashMap pluginPools = new HashMap<>(); + private final HashMap pluginPools = new HashMap<>(); - @Inject @Any Instance httpServerRequest; + @Inject @Any Instance httpServerRequest; @Inject - public WasmPluginFeature(Instance factories, @Any HttpServer httpServer) + public WasmPluginFeature(Instance factories, @Any ServerAdaptor httpServer) throws StartException { for (var factory : factories) { - WasmPlugin plugin = null; + Plugin plugin = null; plugin = factory.create(); plugin.setHttpServer(httpServer); String name = plugin.name(); if (this.pluginPools.containsKey(name)) { throw new IllegalArgumentException("Duplicate wasm plugin name: " + name); } - WasmPluginPool pool = + Pool pool = plugin.isShared() - ? new WasmPluginPool.AppScoped(plugin) - : new WasmPluginPool.RequestScoped(factory, plugin); + ? new Pool.AppScoped(plugin) + : new Pool.RequestScoped(factory, plugin); this.pluginPools.put(name, pool); } } - public Collection getPluginPools() { + @PreDestroy + public void destroy() { + for (var pool : pluginPools.values()) { + pool.close(); + } + } + + public Collection getPluginPools() { return pluginPools.values(); } - public WasmPluginPool pool(String name) { + public Pool pool(String name) { return pluginPools.get(name); } @@ -62,7 +72,7 @@ public void configure(ResourceInfo resourceInfo, FeatureContext context) { resourceInfo.getResourceClass().getAnnotation(NamedWasmPlugin.class); } if (pluignNameAnnotation != null) { - WasmPluginPool factory = pluginPools.get(pluignNameAnnotation.value()); + Pool factory = pluginPools.get(pluignNameAnnotation.value()); if (factory != null) { context.register(new ProxyWasmFilter(factory, httpServerRequest)); } diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/servlet/ServletHttpServer.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/servlet/ServletHttpServer.java deleted file mode 100644 index b24afee..0000000 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/servlet/ServletHttpServer.java +++ /dev/null @@ -1,107 +0,0 @@ -package io.roastedroot.proxywasm.jaxrs.servlet; - -import io.roastedroot.proxywasm.ArrayProxyMap; -import io.roastedroot.proxywasm.ProxyMap; -import io.roastedroot.proxywasm.jaxrs.spi.HttpServer; -import jakarta.annotation.Priority; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Alternative; -import jakarta.ws.rs.core.UriBuilder; -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -@Alternative -@Priority(100) -@ApplicationScoped -public class ServletHttpServer implements HttpServer { - - ScheduledExecutorService tickExecutorService = Executors.newScheduledThreadPool(1); - ExecutorService executorService = Executors.newWorkStealingPool(5); - HttpClient client = HttpClient.newHttpClient(); - - @Override - public Runnable scheduleTick(long delay, Runnable task) { - var f = tickExecutorService.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS); - return () -> { - ; - f.cancel(false); - }; - } - - @Override - public Runnable scheduleHttpCall( - String method, - String host, - int port, - URI uri, - ProxyMap headers, - byte[] body, - ProxyMap trailers, - int timeout, - HandlerHttpResponseHandler handler) - throws InterruptedException { - - var f = - executorService - .invokeAll( - List.of( - () -> { - var resp = - httpCall( - method, host, port, uri, headers, body); - handler.call(resp); - return null; - }), - timeout, - TimeUnit.MILLISECONDS) - .get(0); - return () -> { - f.cancel(true); - }; - } - - private HandlerHttpResponse httpCall( - String method, String host, int port, URI uri, ProxyMap headers, byte[] body) - throws IOException, InterruptedException { - - var connectUri = UriBuilder.fromUri(uri).host(host).port(port).build(); - - var builder = HttpRequest.newBuilder().uri(connectUri); - for (var e : headers.entries()) { - builder.header(e.getKey(), e.getValue()); - } - builder.method(method, HttpRequest.BodyPublishers.ofByteArray(body)); - var request = builder.build(); - - HttpResponse response = - client.send(request, HttpResponse.BodyHandlers.ofByteArray()); - response.headers() - .map() - .forEach( - (k, v) -> { - for (var s : v) { - headers.add(k, s); - } - }); - - var h = new ArrayProxyMap(); - response.headers() - .map() - .forEach( - (k, v) -> { - for (var s : v) { - h.add(k, s); - } - }); - - return new HandlerHttpResponse(response.statusCode(), h, response.body()); - } -} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/servlet/ServletHttpServerRequest.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/servlet/ServletHttpServerRequest.java deleted file mode 100644 index 4ca8495..0000000 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/servlet/ServletHttpServerRequest.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.roastedroot.proxywasm.jaxrs.servlet; - -import io.roastedroot.proxywasm.jaxrs.spi.HttpServerRequest; -import jakarta.annotation.Priority; -import jakarta.enterprise.inject.Alternative; -import jakarta.enterprise.inject.Instance; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.core.Context; - -@Alternative -@Priority(100) -public class ServletHttpServerRequest implements HttpServerRequest { - - private final HttpServletRequest request; - - public ServletHttpServerRequest(@Context Instance request) { - this.request = request.get(); - } - - @Override - public String remoteAddress() { - return request.getRemoteAddr(); - } - - @Override - public String remotePort() { - return "" + request.getRemotePort(); - } - - @Override - public String localAddress() { - return request.getLocalAddr(); - } - - @Override - public String localPort() { - return "" + request.getLocalPort(); - } -} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/spi/HttpServerRequest.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/spi/HttpServerRequest.java deleted file mode 100644 index 65c1ff7..0000000 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/spi/HttpServerRequest.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.roastedroot.proxywasm.jaxrs.spi; - -/** - * This interface will help us deal with differences in the http server impl. - */ -public interface HttpServerRequest { - - String remoteAddress(); - - String remotePort(); - - String localAddress(); - - String localPort(); -} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/VertxHttpServerRequest.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/VertxHttpRequestAdaptor.java similarity index 61% rename from proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/VertxHttpServerRequest.java rename to proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/VertxHttpRequestAdaptor.java index 3ca426e..fde2306 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/VertxHttpServerRequest.java +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/VertxHttpRequestAdaptor.java @@ -1,24 +1,17 @@ package io.roastedroot.proxywasm.jaxrs.vertx; -import io.roastedroot.proxywasm.jaxrs.spi.HttpServerRequest; +import io.roastedroot.proxywasm.jaxrs.JaxrsHttpRequestAdaptor; import jakarta.annotation.Priority; import jakarta.enterprise.context.RequestScoped; import jakarta.enterprise.inject.Alternative; -import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; -import jakarta.ws.rs.core.Context; @Alternative @Priority(200) @RequestScoped -public class VertxHttpServerRequest implements HttpServerRequest { +public class VertxHttpRequestAdaptor extends JaxrsHttpRequestAdaptor { - private final io.vertx.core.http.HttpServerRequest request; - - @Inject - public VertxHttpServerRequest(@Context Instance request) { - this.request = request.get(); - } + @Inject io.vertx.core.http.HttpServerRequest request; @Override public String remoteAddress() { diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/VertxHttpServer.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/VertxHttpServer.java index c6ebe73..b5d5bd2 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/VertxHttpServer.java +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/vertx/VertxHttpServer.java @@ -1,7 +1,7 @@ package io.roastedroot.proxywasm.jaxrs.vertx; import io.roastedroot.proxywasm.ProxyMap; -import io.roastedroot.proxywasm.jaxrs.spi.HttpServer; +import io.roastedroot.proxywasm.plugin.ServerAdaptor; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpClient; @@ -18,7 +18,7 @@ @Alternative @Priority(200) @ApplicationScoped -public class VertxHttpServer implements HttpServer { +public class VertxHttpServer implements ServerAdaptor { @Inject Vertx vertx;