From 9dda1687466bd83768bd403cf262f005c51aab41 Mon Sep 17 00:00:00 2001 From: Hiram Chirino Date: Mon, 17 Mar 2025 18:58:39 -0400 Subject: [PATCH 1/5] feat: initial structure of the proxy-wasm-jaxrs module. --- pom.xml | 8 + proxy-wasm-java-host/pom.xml | 1 - proxy-wasm-jaxrs/.gitignore | 45 ++++++ proxy-wasm-jaxrs/pom.xml | 141 ++++++++++++++++++ .../proxywasm/jaxrs/JaxrsHandler.java | 5 + .../proxywasm/jaxrs/NamedWasmPlugin.java | 14 ++ .../proxywasm/jaxrs/ProxyWasmFilter.java | 23 +++ .../proxywasm/jaxrs/WasmPlugin.java | 81 ++++++++++ .../proxywasm/jaxrs/WasmPluginFeature.java | 47 ++++++ .../io/roastedroot/proxywasm/jaxrs/App.java | 26 ++++ .../proxywasm/jaxrs/ExampleResource.java | 22 +++ .../proxywasm/jaxrs/ExampleResourceTest.java | 16 ++ .../src/test/resources/application.properties | 0 13 files changed, 428 insertions(+), 1 deletion(-) create mode 100644 proxy-wasm-jaxrs/.gitignore create mode 100644 proxy-wasm-jaxrs/pom.xml create mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java create mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/NamedWasmPlugin.java create mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/ProxyWasmFilter.java create mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java create mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFeature.java create mode 100644 proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java create mode 100644 proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResource.java create mode 100644 proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResourceTest.java create mode 100644 proxy-wasm-jaxrs/src/test/resources/application.properties diff --git a/pom.xml b/pom.xml index 7e415c4..f051be0 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,7 @@ proxy-wasm-java-host + proxy-wasm-jaxrs @@ -32,18 +33,24 @@ true true 2023-01-01T00:00:00Z + true 10.21.4 3.6.0 2.44.3 3.14.0 + 3.5.2 5.12.0 1.1.0 + quarkus-bom + io.quarkus.platform + 3.19.3 + @@ -188,6 +195,7 @@ maven-compiler-plugin ${maven.compiler.version} + true 11 11 true diff --git a/proxy-wasm-java-host/pom.xml b/proxy-wasm-java-host/pom.xml index b972aa1..3692360 100644 --- a/proxy-wasm-java-host/pom.xml +++ b/proxy-wasm-java-host/pom.xml @@ -8,7 +8,6 @@ 1.0-SNAPSHOT ../pom.xml - proxy-wasm-java-host jar diff --git a/proxy-wasm-jaxrs/.gitignore b/proxy-wasm-jaxrs/.gitignore new file mode 100644 index 0000000..91a800a --- /dev/null +++ b/proxy-wasm-jaxrs/.gitignore @@ -0,0 +1,45 @@ +#Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties +.flattened-pom.xml + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ +# TLS Certificates +.certs/ diff --git a/proxy-wasm-jaxrs/pom.xml b/proxy-wasm-jaxrs/pom.xml new file mode 100644 index 0000000..921259b --- /dev/null +++ b/proxy-wasm-jaxrs/pom.xml @@ -0,0 +1,141 @@ + + + 4.0.0 + + + io.roastedroot + proxy-wasm-java-host-parent + 1.0-SNAPSHOT + ../pom.xml + + + proxy-wasm-jaxrs + jar + proxy-wasm-jaxrs + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + + io.roastedroot + proxy-wasm-java-host + 1.0-SNAPSHOT + + + jakarta.enterprise + jakarta.enterprise.cdi-api + provided + + + jakarta.inject + jakarta.inject-api + provided + + + + + jakarta.ws.rs + jakarta.ws.rs-api + provided + + + + + io.quarkus + quarkus-arc + test + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-rest-jackson + test + + + io.rest-assured + rest-assured + test + + + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + native-image-agent + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + integration-test + verify + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + native + + + + false + true + + + + diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java new file mode 100644 index 0000000..5235ed1 --- /dev/null +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java @@ -0,0 +1,5 @@ +package io.roastedroot.proxywasm.jaxrs; + +import io.roastedroot.proxywasm.Handler; + +class JaxrsHandler implements Handler {} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/NamedWasmPlugin.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/NamedWasmPlugin.java new file mode 100644 index 0000000..89de27f --- /dev/null +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/NamedWasmPlugin.java @@ -0,0 +1,14 @@ +package io.roastedroot.proxywasm.jaxrs; + +import jakarta.ws.rs.NameBinding; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@NameBinding // Marks this annotation as being used for JAX-RS filtering +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface NamedWasmPlugin { + String value(); +} 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 new file mode 100644 index 0000000..633f793 --- /dev/null +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/ProxyWasmFilter.java @@ -0,0 +1,23 @@ +package io.roastedroot.proxywasm.jaxrs; + +import jakarta.inject.Inject; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; +import java.io.IOException; + +@PreMatching +public class ProxyWasmFilter implements ContainerRequestFilter { + + private final WasmPlugin plugin; + + @Inject + public ProxyWasmFilter(WasmPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void filter(ContainerRequestContext containerRequestContext) throws IOException { + System.out.println("Filtering request with plugin: " + plugin.name()); + } +} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java new file mode 100644 index 0000000..3c72bd8 --- /dev/null +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java @@ -0,0 +1,81 @@ +package io.roastedroot.proxywasm.jaxrs; + +import com.dylibso.chicory.runtime.ImportMemory; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.wasm.WasmModule; +import io.roastedroot.proxywasm.ProxyWasm; +import io.roastedroot.proxywasm.StartException; + +public class WasmPlugin { + + private final String name; + private final ProxyWasm proxyWasm; + private final JaxrsHandler handler; + + public WasmPlugin(String name, ProxyWasm proxyWasm, JaxrsHandler handler) { + this.name = name; + this.proxyWasm = proxyWasm; + this.handler = handler; + } + + public String name() { + return name; + } + + public static WasmPlugin.Builder builder() { + return new WasmPlugin.Builder(); + } + + public static class Builder implements Cloneable { + + JaxrsHandler handler = new JaxrsHandler(); + String name = "default"; + ProxyWasm.Builder proxyWasmBuilder = ProxyWasm.builder().withPluginHandler(handler); + + public WasmPlugin.Builder withName(String name) { + this.name = name; + return this; + } + + public WasmPlugin.Builder withVmConfig(byte[] vmConfig) { + proxyWasmBuilder = proxyWasmBuilder.withVmConfig(vmConfig); + return this; + } + + public WasmPlugin.Builder withVmConfig(String vmConfig) { + proxyWasmBuilder = proxyWasmBuilder.withVmConfig(vmConfig); + return this; + } + + public WasmPlugin.Builder withPluginConfig(byte[] pluginConfig) { + proxyWasmBuilder = proxyWasmBuilder.withPluginConfig(pluginConfig); + return this; + } + + public WasmPlugin.Builder withPluginConfig(String pluginConfig) { + proxyWasmBuilder = proxyWasmBuilder.withPluginConfig(pluginConfig); + return this; + } + + public WasmPlugin.Builder withImportMemory(ImportMemory memory) { + proxyWasmBuilder = proxyWasmBuilder.withImportMemory(memory); + return this; + } + + public WasmPlugin build(WasmModule module) throws StartException { + return build(proxyWasmBuilder.build(module)); + } + + public WasmPlugin build(Instance.Builder instanceBuilder) throws StartException { + return build(proxyWasmBuilder.build(instanceBuilder)); + } + + public WasmPlugin build(Instance instance) throws StartException { + return build(proxyWasmBuilder.build(instance)); + } + + public WasmPlugin build(ProxyWasm proxyWasm) throws StartException { + return new WasmPlugin(name, proxyWasm, handler); + } + } +} 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 new file mode 100644 index 0000000..10f52a1 --- /dev/null +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFeature.java @@ -0,0 +1,47 @@ +package io.roastedroot.proxywasm.jaxrs; + +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.ext.Provider; +import java.util.HashMap; + +@Provider +public class WasmPluginFeature implements DynamicFeature { + + private HashMap plugins = new HashMap<>(); + + @Inject + public WasmPluginFeature(@Any Instance plugins) { + for (WasmPlugin plugin : plugins) { + if (this.plugins.containsKey(plugin.name())) { + throw new IllegalArgumentException("Duplicate wasm plugin name: " + plugin.name()); + } + this.plugins.put(plugin.name(), plugin); + } + } + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext context) { + + var resourceMethod = resourceInfo.getResourceMethod(); + if (resourceMethod != null) { + NamedWasmPlugin pluignNameAnnotation = + resourceMethod.getAnnotation(NamedWasmPlugin.class); + if (pluignNameAnnotation == null) { + // If no annotation on method, check the class level + pluignNameAnnotation = + resourceInfo.getResourceClass().getAnnotation(NamedWasmPlugin.class); + } + if (pluignNameAnnotation != null) { + WasmPlugin plugin = plugins.get(pluignNameAnnotation.value()); + if (plugin != null) { + context.register(new ProxyWasmFilter(plugin)); + } + } + } + } +} diff --git a/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java new file mode 100644 index 0000000..4b3ee4c --- /dev/null +++ b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java @@ -0,0 +1,26 @@ +package io.roastedroot.proxywasm.jaxrs; + +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.WasmModule; +import io.roastedroot.proxywasm.StartException; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import java.nio.file.Path; + +@ApplicationScoped +public class App { + + private static final WasmModule httpHeadersModule = + Parser.parse( + Path.of("../proxy-wasm-java-host/src/test/go-examples/http_headers/main.wasm")); + + @Produces + public WasmPlugin createFoo() throws StartException { + return WasmPlugin.builder().withName("foo").build(httpHeadersModule); + } + + @Produces + public WasmPlugin createBar() throws StartException { + return WasmPlugin.builder().withName("bar").build(httpHeadersModule); + } +} diff --git a/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResource.java b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResource.java new file mode 100644 index 0000000..c07393b --- /dev/null +++ b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResource.java @@ -0,0 +1,22 @@ +package io.roastedroot.proxywasm.jaxrs; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +@Path("/example") +public class ExampleResource { + + @GET + @NamedWasmPlugin("bar") + @Path("/bar") + public String bar() { + return "bar"; + } + + @GET + @NamedWasmPlugin("foo") + @Path("/foo") + public String foo() { + return "foo"; + } +} diff --git a/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResourceTest.java b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResourceTest.java new file mode 100644 index 0000000..b8804e7 --- /dev/null +++ b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResourceTest.java @@ -0,0 +1,16 @@ +package io.roastedroot.proxywasm.jaxrs; + +import static io.restassured.RestAssured.given; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +@QuarkusTest +public class ExampleResourceTest { + + @Test + public void testFooBar() { + given().when().get("/example/foo").then().statusCode(200); + given().when().get("/example/bar").then().statusCode(200); + } +} diff --git a/proxy-wasm-jaxrs/src/test/resources/application.properties b/proxy-wasm-jaxrs/src/test/resources/application.properties new file mode 100644 index 0000000..e69de29 From f0ed741aeafff3ab398be9104280ef6476f108f4 Mon Sep 17 00:00:00 2001 From: Hiram Chirino Date: Mon, 17 Mar 2025 19:49:30 -0400 Subject: [PATCH 2/5] Support using a per request plugin. Signed-off-by: Hiram Chirino --- .../proxywasm/jaxrs/ProxyWasmFilter.java | 13 +++++++++---- .../proxywasm/jaxrs/WasmPluginFactory.java | 7 +++++++ .../proxywasm/jaxrs/WasmPluginFeature.java | 16 +++++++++------- .../java/io/roastedroot/proxywasm/jaxrs/App.java | 8 ++++---- 4 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFactory.java 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 633f793..7f11ee3 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,5 +1,6 @@ package io.roastedroot.proxywasm.jaxrs; +import io.roastedroot.proxywasm.StartException; import jakarta.inject.Inject; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; @@ -9,15 +10,19 @@ @PreMatching public class ProxyWasmFilter implements ContainerRequestFilter { - private final WasmPlugin plugin; + private final WasmPluginFactory pluginFactory; @Inject - public ProxyWasmFilter(WasmPlugin plugin) { - this.plugin = plugin; + public ProxyWasmFilter(WasmPluginFactory pluginFactory) { + this.pluginFactory = pluginFactory; } @Override public void filter(ContainerRequestContext containerRequestContext) throws IOException { - System.out.println("Filtering request with plugin: " + plugin.name()); + try { + var plugin = pluginFactory.create(); + System.out.println("Filtering request with plugin: " + plugin.name()); + } catch (StartException ignored) { + } } } 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 new file mode 100644 index 0000000..b298fc4 --- /dev/null +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFactory.java @@ -0,0 +1,7 @@ +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 10f52a1..0220db2 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,5 +1,6 @@ package io.roastedroot.proxywasm.jaxrs; +import io.roastedroot.proxywasm.StartException; import jakarta.enterprise.inject.Any; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -12,15 +13,16 @@ @Provider public class WasmPluginFeature implements DynamicFeature { - private HashMap plugins = new HashMap<>(); + private HashMap plugins = new HashMap<>(); @Inject - public WasmPluginFeature(@Any Instance plugins) { - for (WasmPlugin plugin : plugins) { + public WasmPluginFeature(@Any Instance factories) throws StartException { + for (var factory : factories) { + var plugin = factory.create(); if (this.plugins.containsKey(plugin.name())) { throw new IllegalArgumentException("Duplicate wasm plugin name: " + plugin.name()); } - this.plugins.put(plugin.name(), plugin); + this.plugins.put(plugin.name(), factory); } } @@ -37,9 +39,9 @@ public void configure(ResourceInfo resourceInfo, FeatureContext context) { resourceInfo.getResourceClass().getAnnotation(NamedWasmPlugin.class); } if (pluignNameAnnotation != null) { - WasmPlugin plugin = plugins.get(pluignNameAnnotation.value()); - if (plugin != null) { - context.register(new ProxyWasmFilter(plugin)); + WasmPluginFactory factory = plugins.get(pluignNameAnnotation.value()); + if (factory != null) { + context.register(new ProxyWasmFilter(factory)); } } } diff --git a/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java index 4b3ee4c..3dae5c1 100644 --- a/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java +++ b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java @@ -15,12 +15,12 @@ public class App { Path.of("../proxy-wasm-java-host/src/test/go-examples/http_headers/main.wasm")); @Produces - public WasmPlugin createFoo() throws StartException { - return WasmPlugin.builder().withName("foo").build(httpHeadersModule); + public WasmPluginFactory createFoo() throws StartException { + return () -> WasmPlugin.builder().withName("foo").build(httpHeadersModule); } @Produces - public WasmPlugin createBar() throws StartException { - return WasmPlugin.builder().withName("bar").build(httpHeadersModule); + public WasmPluginFactory createBar() throws StartException { + return () -> WasmPlugin.builder().withName("bar").build(httpHeadersModule); } } From d26e4ead3f23284b22b5ee4cd6344674464ad723 Mon Sep 17 00:00:00 2001 From: Hiram Chirino Date: Mon, 17 Mar 2025 22:40:24 -0400 Subject: [PATCH 3/5] Implementing more of the JAXRS filter. Signed-off-by: Hiram Chirino --- .../proxywasm/WellKnownProperties.java | 67 +++ .../proxywasm/jaxrs/JaxrsHandler.java | 435 +++++++++++++++++- .../proxywasm/jaxrs/JaxrsProxyMap.java | 88 ++++ .../proxywasm/jaxrs/ProxyWasmFilter.java | 29 +- .../proxywasm/jaxrs/WasmPlugin.java | 17 + 5 files changed, 630 insertions(+), 6 deletions(-) create mode 100644 proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/WellKnownProperties.java create mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsProxyMap.java diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/WellKnownProperties.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/WellKnownProperties.java new file mode 100644 index 0000000..b944eaa --- /dev/null +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/WellKnownProperties.java @@ -0,0 +1,67 @@ +package io.roastedroot.proxywasm; + +public final class WellKnownProperties { + private WellKnownProperties() {} + + // Proxy-Wasm properties + public static final String PLUGIN_NAME = "plugin_name"; + public static final String PLUGIN_ROOT_ID = "plugin_root_id"; + public static final String PLUGIN_VM_ID = "plugin_vm_id"; + + // Downstream connection properties + public static final String CONNECTION_ID = "connection.id"; + public static final String SOURCE_ADDRESS = "source.address"; + public static final String SOURCE_PORT = "source.port"; + public static final String DESTINATION_ADDRESS = "destination.address"; + public static final String DESTINATION_PORT = "destination.port"; + public static final String CONNECTION_TLS_VERSION = "connection.tls_version"; + public static final String CONNECTION_REQUESTED_SERVER_NAME = + "connection.requested_server_name"; + public static final String CONNECTION_MTLS = "connection.mtls"; + public static final String CONNECTION_SUBJECT_LOCAL_CERTIFICATE = + "connection.subject_local_certificate"; + public static final String CONNECTION_SUBJECT_PEER_CERTIFICATE = + "connection.subject_peer_certificate"; + public static final String CONNECTION_DNS_SAN_LOCAL_CERTIFICATE = + "connection.dns_san_local_certificate"; + public static final String CONNECTION_DNS_SAN_PEER_CERTIFICATE = + "connection.dns_san_peer_certificate"; + public static final String CONNECTION_URI_SAN_LOCAL_CERTIFICATE = + "connection.uri_san_local_certificate"; + public static final String CONNECTION_URI_SAN_PEER_CERTIFICATE = + "connection.uri_san_peer_certificate"; + public static final String CONNECTION_SHA256_PEER_CERTIFICATE_DIGEST = + "connection.sha256_peer_certificate_digest"; + + // Upstream connection properties + public static final String UPSTREAM_ADDRESS = "upstream.address"; + public static final String UPSTREAM_PORT = "upstream.port"; + public static final String UPSTREAM_LOCAL_ADDRESS = "upstream.local_address"; + public static final String UPSTREAM_LOCAL_PORT = "upstream.local_port"; + public static final String UPSTREAM_TLS_VERSION = "upstream.tls_version"; + public static final String UPSTREAM_SUBJECT_LOCAL_CERTIFICATE = + "upstream.subject_local_certificate"; + public static final String UPSTREAM_SUBJECT_PEER_CERTIFICATE = + "upstream.subject_peer_certificate"; + public static final String UPSTREAM_DNS_SAN_LOCAL_CERTIFICATE = + "upstream.dns_san_local_certificate"; + public static final String UPSTREAM_DNS_SAN_PEER_CERTIFICATE = + "upstream.dns_san_peer_certificate"; + public static final String UPSTREAM_URI_SAN_LOCAL_CERTIFICATE = + "upstream.uri_san_local_certificate"; + public static final String UPSTREAM_URI_SAN_PEER_CERTIFICATE = + "upstream.uri_san_peer_certificate"; + public static final String UPSTREAM_SHA256_PEER_CERTIFICATE_DIGEST = + "upstream.sha256_peer_certificate_digest"; + + // HTTP request properties + public static final String REQUEST_PROTOCOL = "request.protocol"; + public static final String REQUEST_TIME = "request.time"; + public static final String REQUEST_DURATION = "request.duration"; + public static final String REQUEST_SIZE = "request.size"; + public static final String REQUEST_TOTAL_SIZE = "request.total_size"; + + // HTTP response properties + public static final String RESPONSE_SIZE = "response.size"; + public static final String RESPONSE_TOTAL_SIZE = "response.total_size"; +} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java index 5235ed1..7150dae 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java @@ -1,5 +1,438 @@ package io.roastedroot.proxywasm.jaxrs; +import io.roastedroot.proxywasm.Action; +import io.roastedroot.proxywasm.ChainedHandler; import io.roastedroot.proxywasm.Handler; +import io.roastedroot.proxywasm.Helpers; +import io.roastedroot.proxywasm.LogLevel; +import io.roastedroot.proxywasm.MetricType; +import io.roastedroot.proxywasm.ProxyMap; +import io.roastedroot.proxywasm.StreamType; +import io.roastedroot.proxywasm.WasmException; +import io.roastedroot.proxywasm.WasmResult; +import jakarta.ws.rs.core.MultivaluedMap; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; -class JaxrsHandler implements Handler {} +class JaxrsHandler extends ChainedHandler { + + private Handler next; + + 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; + } + } + + private int tickPeriodMilliseconds; + private ProxyMap httpRequestHeaders = new JaxrsProxyMap(); + private ProxyMap httpRequestTrailers = new JaxrsProxyMap(); + private ProxyMap httpResponseHeaders = new JaxrsProxyMap(); + private ProxyMap httpResponseTrailers = new JaxrsProxyMap(); + private ProxyMap grpcReceiveInitialMetadata = new JaxrsProxyMap(); + private ProxyMap grpcReceiveTrailerMetadata = new JaxrsProxyMap(); + private HttpResponse senthttpResponse; + + private byte[] funcCallData = new byte[0]; + private byte[] httpRequestBody = new byte[0]; + private byte[] httpResponseBody = new byte[0]; + private byte[] downStreamData = new byte[0]; + private byte[] upstreamData = new byte[0]; + private byte[] grpcReceiveBuffer = new byte[0]; + + static final boolean DEBUG = "true".equals(System.getenv("DEBUG")); + + JaxrsHandler() { + this(new Handler() {}); + } + + JaxrsHandler(Handler next) { + this.next = next; + } + + @Override + protected Handler next() { + return next; + } + + @Override + public void log(LogLevel level, String message) throws WasmException { + if (DEBUG) { + System.out.println(level + ": " + message); + } + } + + @Override + public WasmResult setTickPeriodMilliseconds(int tickPeriodMilliseconds) { + this.tickPeriodMilliseconds = tickPeriodMilliseconds; + return WasmResult.OK; + } + + public int getTickPeriodMilliseconds() { + return tickPeriodMilliseconds; + } + + @Override + public ProxyMap getHttpRequestHeaders() { + return httpRequestHeaders; + } + + @Override + public ProxyMap getHttpRequestTrailers() { + return httpRequestTrailers; + } + + @Override + public ProxyMap getHttpResponseHeaders() { + return httpResponseHeaders; + } + + @Override + public ProxyMap getHttpResponseTrailers() { + return httpResponseTrailers; + } + + @Override + public ProxyMap getGrpcReceiveInitialMetaData() { + return grpcReceiveInitialMetadata; + } + + @Override + public ProxyMap getGrpcReceiveTrailerMetaData() { + return grpcReceiveTrailerMetadata; + } + + @Override + public WasmResult setHttpRequestHeaders(ProxyMap headers) { + this.httpRequestHeaders = headers; + return WasmResult.OK; + } + + public WasmResult setHttpRequestHeaders(MultivaluedMap headers) { + return this.setHttpRequestHeaders(new JaxrsProxyMap(headers)); + } + + @Override + public WasmResult setHttpRequestTrailers(ProxyMap trailers) { + this.httpRequestTrailers = trailers; + return WasmResult.OK; + } + + public WasmResult setHttpRequestTrailers(MultivaluedMap headers) { + return this.setHttpRequestTrailers(new JaxrsProxyMap(headers)); + } + + @Override + public WasmResult setHttpResponseHeaders(ProxyMap headers) { + this.httpResponseHeaders = headers; + return WasmResult.OK; + } + + public WasmResult setHttpResponseHeaders(MultivaluedMap headers) { + return this.setHttpResponseHeaders(new JaxrsProxyMap(headers)); + } + + @Override + public WasmResult setHttpResponseTrailers(ProxyMap trailers) { + this.httpResponseTrailers = trailers; + return WasmResult.OK; + } + + @Override + public WasmResult setGrpcReceiveInitialMetaData(ProxyMap metadata) { + this.grpcReceiveInitialMetadata = metadata; + return WasmResult.OK; + } + + @Override + public WasmResult setGrpcReceiveTrailerMetaData(ProxyMap metadata) { + this.grpcReceiveTrailerMetadata = metadata; + return WasmResult.OK; + } + + @Override + public byte[] getFuncCallData() { + return this.funcCallData; + } + + @Override + public byte[] getGrpcReceiveBuffer() { + return this.grpcReceiveBuffer; + } + + @Override + public byte[] getUpstreamData() { + return this.upstreamData; + } + + @Override + public byte[] getDownStreamData() { + return this.downStreamData; + } + + @Override + public byte[] getHttpResponseBody() { + return this.httpResponseBody; + } + + @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); + } + + @Override + public WasmResult setHttpResponseBody(byte[] body) { + this.httpResponseBody = body; + return WasmResult.OK; + } + + public void appendHttpResponseBody(byte[] body) { + this.httpResponseBody = Helpers.append(this.httpResponseBody, body); + } + + @Override + public WasmResult setDownStreamData(byte[] data) { + this.downStreamData = data; + return WasmResult.OK; + } + + @Override + public WasmResult setUpstreamData(byte[] data) { + this.upstreamData = data; + return WasmResult.OK; + } + + @Override + public WasmResult setGrpcReceiveBuffer(byte[] buffer) { + this.grpcReceiveBuffer = buffer; + return WasmResult.OK; + } + + @Override + public WasmResult setFuncCallData(byte[] data) { + this.funcCallData = data; + return WasmResult.OK; + } + + @Override + public WasmResult sendHttpResponse( + int responseCode, + byte[] responseCodeDetails, + byte[] responseBody, + ProxyMap additionalHeaders, + int grpcStatus) { + this.senthttpResponse = + new HttpResponse( + responseCode, + responseCodeDetails, + responseBody, + additionalHeaders, + grpcStatus); + return WasmResult.OK; + } + + public HttpResponse getSentHttpResponse() { + return senthttpResponse; + } + + public static class HttpCall { + public enum Type { + REGULAR, + DISPATCH + } + + public final int id; + public final Type callType; + public final String uri; + public final Object headers; + public final byte[] body; + public final ProxyMap trailers; + public final int timeoutMilliseconds; + + public HttpCall( + int id, + Type callType, + String uri, + ProxyMap headers, + byte[] body, + ProxyMap trailers, + int timeoutMilliseconds) { + this.id = id; + this.callType = callType; + this.uri = uri; + this.headers = headers; + this.body = body; + this.trailers = trailers; + this.timeoutMilliseconds = timeoutMilliseconds; + } + } + + private final AtomicInteger lastCallId = new AtomicInteger(0); + private final HashMap httpCalls = new HashMap(); + + public HashMap getHttpCalls() { + return httpCalls; + } + + @Override + public int httpCall( + String uri, ProxyMap headers, byte[] body, ProxyMap trailers, int timeoutMilliseconds) + throws WasmException { + var id = lastCallId.incrementAndGet(); + HttpCall value = + new HttpCall( + id, + HttpCall.Type.REGULAR, + uri, + headers, + body, + trailers, + timeoutMilliseconds); + httpCalls.put(id, value); + return id; + } + + @Override + public int dispatchHttpCall( + String upstreamName, + ProxyMap headers, + byte[] body, + ProxyMap trailers, + int timeoutMilliseconds) + throws WasmException { + var id = lastCallId.incrementAndGet(); + HttpCall value = + new HttpCall( + id, + HttpCall.Type.DISPATCH, + upstreamName, + headers, + body, + trailers, + timeoutMilliseconds); + httpCalls.put(id, value); + return id; + } + + public static class Metric { + + public final int id; + public final MetricType type; + public final String name; + public long value; + + public Metric(int id, MetricType type, String name) { + this.id = id; + this.type = type; + this.name = name; + } + } + + private final AtomicInteger lastMetricId = new AtomicInteger(0); + private HashMap metrics = new HashMap(); + private HashMap metricsByName = new HashMap(); + + @Override + public int defineMetric(MetricType type, String name) throws WasmException { + var id = lastMetricId.incrementAndGet(); + Metric value = new Metric(id, type, name); + metrics.put(id, value); + metricsByName.put(name, value); + return id; + } + + @Override + public long getMetric(int metricId) throws WasmException { + var metric = metrics.get(metricId); + if (metric == null) { + throw new WasmException(WasmResult.NOT_FOUND); + } + return metric.value; + } + + public Metric getMetric(String name) { + return metricsByName.get(name); + } + + @Override + public WasmResult incrementMetric(int metricId, long value) { + var metric = metrics.get(metricId); + if (metric == null) { + return WasmResult.NOT_FOUND; + } + metric.value += value; + return WasmResult.OK; + } + + @Override + public WasmResult recordMetric(int metricId, long value) { + var metric = metrics.get(metricId); + if (metric == null) { + return WasmResult.NOT_FOUND; + } + metric.value = value; + return WasmResult.OK; + } + + @Override + public WasmResult removeMetric(int metricId) { + Metric metric = metrics.remove(metricId); + if (metric == null) { + return WasmResult.NOT_FOUND; + } + metricsByName.remove(metric.name); + return WasmResult.OK; + } + + private Action action; + + @Override + public WasmResult setAction(StreamType streamType, Action action) { + this.action = action; + return WasmResult.OK; + } + + public Action getAction() { + return action; + } + + final HashMap, byte[]> properties = new HashMap<>(); + + @Override + public byte[] getProperty(List path) throws WasmException { + return properties.get(path); + } + + @Override + public WasmResult setProperty(List path, byte[] value) { + 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/JaxrsProxyMap.java new file mode 100644 index 0000000..6d95437 --- /dev/null +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsProxyMap.java @@ -0,0 +1,88 @@ +package io.roastedroot.proxywasm.jaxrs; + +import io.roastedroot.proxywasm.ProxyMap; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +public class JaxrsProxyMap implements ProxyMap { + + final MultivaluedMap entries; + + public JaxrsProxyMap() { + this.entries = new MultivaluedHashMap<>(); + } + + public JaxrsProxyMap(int mapSize) { + this.entries = new MultivaluedHashMap<>(); + } + + public JaxrsProxyMap(ProxyMap other) { + this(other.size()); + for (Map.Entry entry : other.entries()) { + add(entry.getKey(), entry.getValue()); + } + } + + public JaxrsProxyMap(MultivaluedMap other) { + this.entries = other; + } + + @Override + public int size() { + return entries.size(); + } + + @Override + public void add(String key, String value) { + entries.add(key, value); + } + + @Override + public void put(String key, String value) { + entries.put(key, List.of(value)); + } + + static Iterable toIterable(Stream stream) { + return stream::iterator; + } + + @Override + public Iterable> entries() { + return toIterable( + entries.entrySet().stream() + .flatMap(x -> x.getValue().stream().map(y -> Map.entry(x.getKey(), y)))); + } + + @Override + public String get(String key) { + return entries.get(key).stream().findFirst().orElse(null); + } + + @Override + public void remove(String key) { + entries.remove(key); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + JaxrsProxyMap that = (JaxrsProxyMap) o; + return Objects.equals(entries, that.entries); + } + + @Override + public int hashCode() { + return Objects.hashCode(entries); + } + + @Override + public String toString() { + return entries.toString(); + } +} 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 7f11ee3..e14cfde 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 @@ -4,11 +4,14 @@ import jakarta.inject.Inject; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.core.Response; import java.io.IOException; @PreMatching -public class ProxyWasmFilter implements ContainerRequestFilter { +public class ProxyWasmFilter implements ContainerRequestFilter, ContainerResponseFilter { private final WasmPluginFactory pluginFactory; @@ -18,11 +21,27 @@ public ProxyWasmFilter(WasmPluginFactory pluginFactory) { } @Override - public void filter(ContainerRequestContext containerRequestContext) throws IOException { + public void filter(ContainerRequestContext requestContext) throws IOException { + + WasmPlugin plugin = null; try { - var plugin = pluginFactory.create(); - System.out.println("Filtering request with plugin: " + plugin.name()); - } catch (StartException ignored) { + plugin = pluginFactory.create(); + } catch (StartException e) { + requestContext.abortWith( + Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()); } + + // so we can continue to use the plugin in the other filter methods. + requestContext.setProperty("WasmPlugin", plugin); + var wasmHttpContext = plugin.createHttpContext(); + requestContext.setProperty("WasmHttpContext", wasmHttpContext); + + plugin.handler().setHttpRequestHeaders(requestContext.getHeaders()); + wasmHttpContext.callOnRequestHeaders(false); } + + @Override + public void filter( + ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException {} } diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java index 3c72bd8..0ffb440 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java @@ -3,8 +3,10 @@ import com.dylibso.chicory.runtime.ImportMemory; import com.dylibso.chicory.runtime.Instance; import com.dylibso.chicory.wasm.WasmModule; +import io.roastedroot.proxywasm.HttpContext; import io.roastedroot.proxywasm.ProxyWasm; import io.roastedroot.proxywasm.StartException; +import java.util.Objects; public class WasmPlugin { @@ -13,6 +15,9 @@ public class WasmPlugin { private final JaxrsHandler handler; public WasmPlugin(String name, ProxyWasm proxyWasm, JaxrsHandler handler) { + Objects.requireNonNull(name); + Objects.requireNonNull(proxyWasm); + Objects.requireNonNull(handler); this.name = name; this.proxyWasm = proxyWasm; this.handler = handler; @@ -22,6 +27,18 @@ public String name() { return name; } + ProxyWasm proxyWasm() { + return proxyWasm; + } + + HttpContext createHttpContext() { + return proxyWasm.createHttpContext(this.handler); + } + + JaxrsHandler handler() { + return handler; + } + public static WasmPlugin.Builder builder() { return new WasmPlugin.Builder(); } From 7334d91a061aba53e86908f6e82010db3b974c54 Mon Sep 17 00:00:00 2001 From: Hiram Chirino Date: Tue, 18 Mar 2025 12:41:20 -0400 Subject: [PATCH 4/5] Implement the simple request/response plugin filtering path. Signed-off-by: Hiram Chirino --- .../java/io/roastedroot/proxywasm/ABI.java | 124 +++-- .../roastedroot/proxywasm/ChainedHandler.java | 40 -- .../io/roastedroot/proxywasm/Context.java | 8 + .../io/roastedroot/proxywasm/Handler.java | 80 ---- .../io/roastedroot/proxywasm/HttpContext.java | 16 + .../proxywasm/WellKnownProperties.java | 106 ++--- .../proxywasm/examples/MockHandler.java | 6 - .../proxywasm/jaxrs/HttpHandler.java | 252 ++++++++++ .../proxywasm/jaxrs/JaxrsHandler.java | 438 ------------------ .../proxywasm/jaxrs/JaxrsProxyMap.java | 31 +- .../proxywasm/jaxrs/PluginHandler.java | 283 +++++++++++ .../proxywasm/jaxrs/ProxyWasmFilter.java | 181 +++++++- .../proxywasm/jaxrs/WasmPlugin.java | 23 +- .../io/roastedroot/proxywasm/jaxrs/App.java | 12 +- .../proxywasm/jaxrs/ExampleResourceTest.java | 14 +- 15 files changed, 893 insertions(+), 721 deletions(-) create mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/HttpHandler.java delete mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java create mode 100644 proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/PluginHandler.java diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/ABI.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/ABI.java index ff9003e..0002c09 100644 --- a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/ABI.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/ABI.java @@ -21,35 +21,35 @@ class ABI { private Handler handler; private Memory memory; - private ExportFunction initializeFn; - private ExportFunction mainFn; - private ExportFunction startFn; - private ExportFunction proxyOnContextCreateFn; - private ExportFunction proxyOnDoneFn; - private ExportFunction mallocFn; - private ExportFunction proxyOnLogFn; - private ExportFunction proxyOnDeleteFn; - private ExportFunction proxyOnVmStartFn; - private ExportFunction proxyOnConfigureFn; - private ExportFunction proxyOnTickFn; - private ExportFunction proxyOnNewConnectionFn; - private ExportFunction proxyOnDownstreamDataFn; - private ExportFunction proxyOnDownstreamConnectionCloseFn; - private ExportFunction proxyOnUpstreamDataFn; - private ExportFunction proxyOnUpstreamConnectionCloseFn; - private ExportFunction proxyOnRequestHeadersFn; - private ExportFunction proxyOnRequestBodyFn; - private ExportFunction proxyOnRequestTrailersFn; - private ExportFunction proxyOnResponseHeadersFn; - private ExportFunction proxyOnResponseBodyFn; - private ExportFunction proxyOnResponseTrailersFn; - private ExportFunction proxyOnHttpCallResponseFn; - private ExportFunction proxyOnGrpcReceiveInitialMetadataFn; - private ExportFunction proxyOnGrpcReceiveFn; - private ExportFunction proxyOnGrpcReceiveTrailingMetadataFn; - private ExportFunction proxyOnGrpcCloseFn; - private ExportFunction proxyOnQueueReadyFn; - private ExportFunction proxyOnForeignFunctionFn; + ExportFunction initializeFn; + ExportFunction mainFn; + ExportFunction startFn; + ExportFunction proxyOnContextCreateFn; + ExportFunction proxyOnDoneFn; + ExportFunction mallocFn; + ExportFunction proxyOnLogFn; + ExportFunction proxyOnDeleteFn; + ExportFunction proxyOnVmStartFn; + ExportFunction proxyOnConfigureFn; + ExportFunction proxyOnTickFn; + ExportFunction proxyOnNewConnectionFn; + ExportFunction proxyOnDownstreamDataFn; + ExportFunction proxyOnDownstreamConnectionCloseFn; + ExportFunction proxyOnUpstreamDataFn; + ExportFunction proxyOnUpstreamConnectionCloseFn; + ExportFunction proxyOnRequestHeadersFn; + ExportFunction proxyOnRequestBodyFn; + ExportFunction proxyOnRequestTrailersFn; + ExportFunction proxyOnResponseHeadersFn; + ExportFunction proxyOnResponseBodyFn; + ExportFunction proxyOnResponseTrailersFn; + ExportFunction proxyOnHttpCallResponseFn; + ExportFunction proxyOnGrpcReceiveInitialMetadataFn; + ExportFunction proxyOnGrpcReceiveFn; + ExportFunction proxyOnGrpcReceiveTrailingMetadataFn; + ExportFunction proxyOnGrpcCloseFn; + ExportFunction proxyOnQueueReadyFn; + ExportFunction proxyOnForeignFunctionFn; Handler getHandler() { return handler; @@ -866,7 +866,29 @@ int proxyGetHeaderMapValue( @WasmExport int proxyAddHeaderMapValue( int mapType, int keyDataPtr, int keySize, int valueDataPtr, int valueSize) { - return proxyReplaceHeaderMapValue(mapType, keyDataPtr, keySize, valueDataPtr, valueSize); + try { + // Get the header map based on the map type + ProxyMap headerMap = getMap(mapType); + if (headerMap == null) { + return WasmResult.BAD_ARGUMENT.getValue(); + } + + // Get key from memory + String key = string(readMemory(keyDataPtr, keySize)); + + // Get value from memory + String value = string(readMemory(valueDataPtr, valueSize)); + + // Add value in map + headerMap.add(key, value); + + return WasmResult.OK.getValue(); + + } catch (WasmRuntimeException e) { + return WasmResult.INVALID_MEMORY_ACCESS.getValue(); + } catch (WasmException e) { + return e.result().getValue(); + } } /** @@ -897,9 +919,7 @@ int proxyReplaceHeaderMapValue( String value = string(readMemory(valueDataPtr, valueSize)); // Replace value in map - var copy = new ArrayProxyMap(headerMap); - copy.put(key, value); - setMap(mapType, copy); + headerMap.put(key, value); return WasmResult.OK.getValue(); @@ -935,9 +955,7 @@ int proxyRemoveHeaderMapValue(int mapType, int keyDataPtr, int keySize) { } // Remove key from map - var copy = new ArrayProxyMap(headerMap); - copy.remove(key); - setMap(mapType, copy); + headerMap.remove(key); return WasmResult.OK.getValue(); @@ -982,40 +1000,6 @@ private ProxyMap getMap(int mapType) { return null; } - /** - * Set a header map based on the map type. - * - * @param mapType The type of map to set - * @param map The header map to set - * @return WasmResult indicating success or failure - */ - private WasmResult setMap(int mapType, ProxyMap map) { - var knownType = MapType.fromInt(mapType); - if (knownType == null) { - return handler.setCustomHeaders(mapType, map); - } - - switch (knownType) { - case HTTP_REQUEST_HEADERS: - return handler.setHttpRequestHeaders(map); - case HTTP_REQUEST_TRAILERS: - return handler.setHttpRequestTrailers(map); - case HTTP_RESPONSE_HEADERS: - return handler.setHttpResponseHeaders(map); - case HTTP_RESPONSE_TRAILERS: - return handler.setHttpResponseTrailers(map); - case HTTP_CALL_RESPONSE_HEADERS: - return handler.setHttpCallResponseHeaders(map); - case HTTP_CALL_RESPONSE_TRAILERS: - return handler.setHttpCallResponseTrailers(map); - case GRPC_RECEIVE_INITIAL_METADATA: - return handler.setGrpcReceiveInitialMetaData(map); - case GRPC_RECEIVE_TRAILING_METADATA: - return handler.setGrpcReceiveTrailerMetaData(map); - } - return WasmResult.NOT_FOUND; - } - /** * Decodes a byte array containing map data into a Map of String key-value pairs. *

diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/ChainedHandler.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/ChainedHandler.java index ddb68da..b1946eb 100644 --- a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/ChainedHandler.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/ChainedHandler.java @@ -64,46 +64,6 @@ public WasmResult setCustomHeaders(int mapType, ProxyMap map) { return next().setCustomHeaders(mapType, map); } - @Override - public WasmResult setHttpRequestHeaders(ProxyMap headers) { - return next().setHttpRequestHeaders(headers); - } - - @Override - public WasmResult setHttpRequestTrailers(ProxyMap trailers) { - return next().setHttpRequestTrailers(trailers); - } - - @Override - public WasmResult setHttpResponseHeaders(ProxyMap headers) { - return next().setHttpResponseHeaders(headers); - } - - @Override - public WasmResult setHttpResponseTrailers(ProxyMap trailers) { - return next().setHttpResponseTrailers(trailers); - } - - @Override - public WasmResult setHttpCallResponseHeaders(ProxyMap headers) { - return next().setHttpCallResponseHeaders(headers); - } - - @Override - public WasmResult setHttpCallResponseTrailers(ProxyMap trailers) { - return next().setHttpCallResponseTrailers(trailers); - } - - @Override - public WasmResult setGrpcReceiveInitialMetaData(ProxyMap metadata) { - return next().setGrpcReceiveInitialMetaData(metadata); - } - - @Override - public WasmResult setGrpcReceiveTrailerMetaData(ProxyMap metadata) { - return next().setGrpcReceiveTrailerMetaData(metadata); - } - @Override public byte[] getProperty(List key) throws WasmException { return next().getProperty(key); diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/Context.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/Context.java index d6559dc..e44554d 100644 --- a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/Context.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/Context.java @@ -20,6 +20,14 @@ public int id() { return id; } + public ProxyWasm getProxyWasm() { + return proxyWasm; + } + + ProxyWasm proxyWasm() { + return proxyWasm; + } + public void close() { if (closeStarted) { return; diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/Handler.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/Handler.java index 1557aec..232d2db 100644 --- a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/Handler.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/Handler.java @@ -298,86 +298,6 @@ default WasmResult setCustomHeaders(int mapType, ProxyMap map) { return WasmResult.UNIMPLEMENTED; } - /** - * Set the HTTP request headers. - * - * @param headers The headers to set - * @return WasmResult indicating success or failure - */ - default WasmResult setHttpRequestHeaders(ProxyMap headers) { - return WasmResult.UNIMPLEMENTED; - } - - /** - * Set the HTTP request trailers. - * - * @param trailers The trailers to set - * @return WasmResult indicating success or failure - */ - default WasmResult setHttpRequestTrailers(ProxyMap trailers) { - return WasmResult.UNIMPLEMENTED; - } - - /** - * Set the HTTP response headers. - * - * @param headers The headers to set - * @return WasmResult indicating success or failure - */ - default WasmResult setHttpResponseHeaders(ProxyMap headers) { - return WasmResult.UNIMPLEMENTED; - } - - /** - * Set the HTTP response trailers. - * - * @param trailers The trailers to set - * @return WasmResult indicating success or failure - */ - default WasmResult setHttpResponseTrailers(ProxyMap trailers) { - return WasmResult.UNIMPLEMENTED; - } - - /** - * Set the HTTP call response headers. - * - * @param headers The headers to set - * @return WasmResult indicating success or failure - */ - default WasmResult setHttpCallResponseHeaders(ProxyMap headers) { - return WasmResult.UNIMPLEMENTED; - } - - /** - * Set the HTTP call response trailers. - * - * @param trailers The trailers to set - * @return WasmResult indicating success or failure - */ - default WasmResult setHttpCallResponseTrailers(ProxyMap trailers) { - return WasmResult.UNIMPLEMENTED; - } - - /** - * Set the gRPC receive initial metadata. - * - * @param metadata The metadata to set - * @return WasmResult indicating success or failure - */ - default WasmResult setGrpcReceiveInitialMetaData(ProxyMap metadata) { - return WasmResult.UNIMPLEMENTED; - } - - /** - * Set the gRPC receive trailer metadata. - * - * @param metadata The metadata to set - * @return WasmResult indicating success or failure - */ - default WasmResult setGrpcReceiveTrailerMetaData(ProxyMap metadata) { - return WasmResult.UNIMPLEMENTED; - } - default WasmResult setAction(StreamType streamType, Action action) { return WasmResult.UNIMPLEMENTED; } diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/HttpContext.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/HttpContext.java index 54ab2d0..defdb93 100644 --- a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/HttpContext.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/HttpContext.java @@ -15,6 +15,10 @@ Handler handler() { return handler; } + public boolean hasOnRequestHeaders() { + return proxyWasm.abi().proxyOnRequestHeadersFn != null; + } + public Action callOnRequestHeaders(boolean endOfStream) { var headers = handler.getHttpRequestHeaders(); int result = proxyWasm.abi().proxyOnRequestHeaders(id, len(headers), endOfStream ? 1 : 0); @@ -23,6 +27,10 @@ public Action callOnRequestHeaders(boolean endOfStream) { return action; } + public boolean hasOnResponseHeaders() { + return proxyWasm.abi().proxyOnResponseHeadersFn != null; + } + public Action callOnResponseHeaders(boolean endOfStream) { var headers = handler.getHttpResponseHeaders(); int result = proxyWasm.abi().proxyOnResponseHeaders(id, len(headers), endOfStream ? 1 : 0); @@ -31,6 +39,10 @@ public Action callOnResponseHeaders(boolean endOfStream) { return action; } + public boolean hasOnRequestBody() { + return proxyWasm.abi().proxyOnRequestBodyFn != null; + } + public Action callOnRequestBody(boolean endOfStream) { var requestBody = handler.getHttpRequestBody(); int result = @@ -42,6 +54,10 @@ public Action callOnRequestBody(boolean endOfStream) { return action; } + public boolean hasOnResponseBody() { + return proxyWasm.abi().proxyOnResponseBodyFn != null; + } + public Action callOnResponseBody(boolean endOfStream) { var responseBody = handler.getHttpResponseBody(); int result = diff --git a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/WellKnownProperties.java b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/WellKnownProperties.java index b944eaa..9d479a3 100644 --- a/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/WellKnownProperties.java +++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/WellKnownProperties.java @@ -1,67 +1,69 @@ package io.roastedroot.proxywasm; +import java.util.List; + public final class WellKnownProperties { private WellKnownProperties() {} // Proxy-Wasm properties - public static final String PLUGIN_NAME = "plugin_name"; - public static final String PLUGIN_ROOT_ID = "plugin_root_id"; - public static final String PLUGIN_VM_ID = "plugin_vm_id"; + public static final List PLUGIN_NAME = List.of("plugin_name"); + public static final List PLUGIN_ROOT_ID = List.of("plugin_root_id"); + public static final List PLUGIN_VM_ID = List.of("plugin_vm_id"); // Downstream connection properties - public static final String CONNECTION_ID = "connection.id"; - public static final String SOURCE_ADDRESS = "source.address"; - public static final String SOURCE_PORT = "source.port"; - public static final String DESTINATION_ADDRESS = "destination.address"; - public static final String DESTINATION_PORT = "destination.port"; - public static final String CONNECTION_TLS_VERSION = "connection.tls_version"; - public static final String CONNECTION_REQUESTED_SERVER_NAME = - "connection.requested_server_name"; - public static final String CONNECTION_MTLS = "connection.mtls"; - public static final String CONNECTION_SUBJECT_LOCAL_CERTIFICATE = - "connection.subject_local_certificate"; - public static final String CONNECTION_SUBJECT_PEER_CERTIFICATE = - "connection.subject_peer_certificate"; - public static final String CONNECTION_DNS_SAN_LOCAL_CERTIFICATE = - "connection.dns_san_local_certificate"; - public static final String CONNECTION_DNS_SAN_PEER_CERTIFICATE = - "connection.dns_san_peer_certificate"; - public static final String CONNECTION_URI_SAN_LOCAL_CERTIFICATE = - "connection.uri_san_local_certificate"; - public static final String CONNECTION_URI_SAN_PEER_CERTIFICATE = - "connection.uri_san_peer_certificate"; - public static final String CONNECTION_SHA256_PEER_CERTIFICATE_DIGEST = - "connection.sha256_peer_certificate_digest"; + public static final List CONNECTION_ID = List.of("connection.id"); + public static final List SOURCE_ADDRESS = List.of("source.address"); + public static final List SOURCE_PORT = List.of("source.port"); + public static final List DESTINATION_ADDRESS = List.of("destination.address"); + public static final List DESTINATION_PORT = List.of("destination.port"); + public static final List CONNECTION_TLS_VERSION = List.of("connection.tls_version"); + public static final List CONNECTION_REQUESTED_SERVER_NAME = + List.of("connection.requested_server_name"); + public static final List CONNECTION_MTLS = List.of("connection.mtls"); + public static final List CONNECTION_SUBJECT_LOCAL_CERTIFICATE = + List.of("connection.subject_local_certificate"); + public static final List CONNECTION_SUBJECT_PEER_CERTIFICATE = + List.of("connection.subject_peer_certificate"); + public static final List CONNECTION_DNS_SAN_LOCAL_CERTIFICATE = + List.of("connection.dns_san_local_certificate"); + public static final List CONNECTION_DNS_SAN_PEER_CERTIFICATE = + List.of("connection.dns_san_peer_certificate"); + public static final List CONNECTION_URI_SAN_LOCAL_CERTIFICATE = + List.of("connection.uri_san_local_certificate"); + public static final List CONNECTION_URI_SAN_PEER_CERTIFICATE = + List.of("connection.uri_san_peer_certificate"); + public static final List CONNECTION_SHA256_PEER_CERTIFICATE_DIGEST = + List.of("connection.sha256_peer_certificate_digest"); // Upstream connection properties - public static final String UPSTREAM_ADDRESS = "upstream.address"; - public static final String UPSTREAM_PORT = "upstream.port"; - public static final String UPSTREAM_LOCAL_ADDRESS = "upstream.local_address"; - public static final String UPSTREAM_LOCAL_PORT = "upstream.local_port"; - public static final String UPSTREAM_TLS_VERSION = "upstream.tls_version"; - public static final String UPSTREAM_SUBJECT_LOCAL_CERTIFICATE = - "upstream.subject_local_certificate"; - public static final String UPSTREAM_SUBJECT_PEER_CERTIFICATE = - "upstream.subject_peer_certificate"; - public static final String UPSTREAM_DNS_SAN_LOCAL_CERTIFICATE = - "upstream.dns_san_local_certificate"; - public static final String UPSTREAM_DNS_SAN_PEER_CERTIFICATE = - "upstream.dns_san_peer_certificate"; - public static final String UPSTREAM_URI_SAN_LOCAL_CERTIFICATE = - "upstream.uri_san_local_certificate"; - public static final String UPSTREAM_URI_SAN_PEER_CERTIFICATE = - "upstream.uri_san_peer_certificate"; - public static final String UPSTREAM_SHA256_PEER_CERTIFICATE_DIGEST = - "upstream.sha256_peer_certificate_digest"; + public static final List UPSTREAM_ADDRESS = List.of("upstream.address"); + public static final List UPSTREAM_PORT = List.of("upstream.port"); + public static final List UPSTREAM_LOCAL_ADDRESS = List.of("upstream.local_address"); + public static final List UPSTREAM_LOCAL_PORT = List.of("upstream.local_port"); + public static final List UPSTREAM_TLS_VERSION = List.of("upstream.tls_version"); + public static final List UPSTREAM_SUBJECT_LOCAL_CERTIFICATE = + List.of("upstream.subject_local_certificate"); + public static final List UPSTREAM_SUBJECT_PEER_CERTIFICATE = + List.of("upstream.subject_peer_certificate"); + public static final List UPSTREAM_DNS_SAN_LOCAL_CERTIFICATE = + List.of("upstream.dns_san_local_certificate"); + public static final List UPSTREAM_DNS_SAN_PEER_CERTIFICATE = + List.of("upstream.dns_san_peer_certificate"); + public static final List UPSTREAM_URI_SAN_LOCAL_CERTIFICATE = + List.of("upstream.uri_san_local_certificate"); + public static final List UPSTREAM_URI_SAN_PEER_CERTIFICATE = + List.of("upstream.uri_san_peer_certificate"); + public static final List UPSTREAM_SHA256_PEER_CERTIFICATE_DIGEST = + List.of("upstream.sha256_peer_certificate_digest"); // HTTP request properties - public static final String REQUEST_PROTOCOL = "request.protocol"; - public static final String REQUEST_TIME = "request.time"; - public static final String REQUEST_DURATION = "request.duration"; - public static final String REQUEST_SIZE = "request.size"; - public static final String REQUEST_TOTAL_SIZE = "request.total_size"; + public static final List REQUEST_PROTOCOL = List.of("request.protocol"); + public static final List REQUEST_TIME = List.of("request.time"); + public static final List REQUEST_DURATION = List.of("request.duration"); + public static final List REQUEST_SIZE = List.of("request.size"); + public static final List REQUEST_TOTAL_SIZE = List.of("request.total_size"); // HTTP response properties - public static final String RESPONSE_SIZE = "response.size"; - public static final String RESPONSE_TOTAL_SIZE = "response.total_size"; + public static final List RESPONSE_SIZE = List.of("response.size"); + public static final List RESPONSE_TOTAL_SIZE = List.of("response.total_size"); } diff --git a/proxy-wasm-java-host/src/test/java/io/roastedroot/proxywasm/examples/MockHandler.java b/proxy-wasm-java-host/src/test/java/io/roastedroot/proxywasm/examples/MockHandler.java index a0a1dd8..cd9ed54 100644 --- a/proxy-wasm-java-host/src/test/java/io/roastedroot/proxywasm/examples/MockHandler.java +++ b/proxy-wasm-java-host/src/test/java/io/roastedroot/proxywasm/examples/MockHandler.java @@ -158,7 +158,6 @@ public ProxyMap getGrpcReceiveTrailerMetaData() { return grpcReceiveTrailerMetadata; } - @Override public WasmResult setHttpRequestHeaders(ProxyMap headers) { this.httpRequestHeaders = headers; return WasmResult.OK; @@ -168,7 +167,6 @@ public WasmResult setHttpRequestHeaders(Map headers) { return this.setHttpRequestHeaders(new ArrayProxyMap(headers)); } - @Override public WasmResult setHttpRequestTrailers(ProxyMap trailers) { this.httpRequestTrailers = trailers; return WasmResult.OK; @@ -178,7 +176,6 @@ public WasmResult setHttpRequestTrailers(Map headers) { return this.setHttpRequestTrailers(new ArrayProxyMap(headers)); } - @Override public WasmResult setHttpResponseHeaders(ProxyMap headers) { this.httpResponseHeaders = headers; return WasmResult.OK; @@ -188,19 +185,16 @@ public WasmResult setHttpResponseHeaders(Map headers) { return this.setHttpResponseHeaders(new ArrayProxyMap(headers)); } - @Override public WasmResult setHttpResponseTrailers(ProxyMap trailers) { this.httpResponseTrailers = trailers; return WasmResult.OK; } - @Override public WasmResult setGrpcReceiveInitialMetaData(ProxyMap metadata) { this.grpcReceiveInitialMetadata = metadata; return WasmResult.OK; } - @Override public WasmResult setGrpcReceiveTrailerMetaData(ProxyMap metadata) { this.grpcReceiveTrailerMetadata = metadata; return WasmResult.OK; 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/HttpHandler.java new file mode 100644 index 0000000..7a39640 --- /dev/null +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/HttpHandler.java @@ -0,0 +1,252 @@ +package io.roastedroot.proxywasm.jaxrs; + +import static io.roastedroot.proxywasm.Helpers.string; + +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 jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.core.Response; +import java.util.HashMap; +import java.util.List; + +class HttpHandler extends ChainedHandler { + + private final PluginHandler next; + + HttpHandler(PluginHandler pluginHandler) { + this.next = pluginHandler; + } + + @Override + protected Handler next() { + return next; + } + + // ////////////////////////////////////////////////////////////////////// + // HTTP fields + // ////////////////////////////////////////////////////////////////////// + + private ContainerRequestContext requestContext; + + public ContainerRequestContext getRequestContext() { + return requestContext; + } + + public void setRequestContext(ContainerRequestContext requestContext) { + this.requestContext = requestContext; + } + + @Override + public ProxyMap getHttpRequestHeaders() { + return new JaxrsProxyMap(requestContext.getHeaders()); + } + + @Override + 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()); + } + + @Override + public ProxyMap getHttpResponseTrailers() { + return null; + } + + @Override + public ProxyMap getGrpcReceiveInitialMetaData() { + return null; + } + + @Override + 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); + return WasmResult.OK; + } + + public HttpResponse getSentHttpResponse() { + return senthttpResponse; + } + + private Action action; + + @Override + public WasmResult setAction(StreamType streamType, Action action) { + this.action = action; + return WasmResult.OK; + } + + public Action getAction() { + return action; + } + + // ////////////////////////////////////////////////////////////////////// + // Properties + // ////////////////////////////////////////////////////////////////////// + + final HashMap, byte[]> properties = new HashMap<>(); + + @Override + public byte[] getProperty(List path) throws WasmException { + byte[] result = properties.get(path); + if (result == null) { + return next().getProperty(path); + } + return result; + } + + @Override + public WasmResult setProperty(List path, byte[] value) { + properties.put(path, value); + return WasmResult.OK; + } +} diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java deleted file mode 100644 index 7150dae..0000000 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHandler.java +++ /dev/null @@ -1,438 +0,0 @@ -package io.roastedroot.proxywasm.jaxrs; - -import io.roastedroot.proxywasm.Action; -import io.roastedroot.proxywasm.ChainedHandler; -import io.roastedroot.proxywasm.Handler; -import io.roastedroot.proxywasm.Helpers; -import io.roastedroot.proxywasm.LogLevel; -import io.roastedroot.proxywasm.MetricType; -import io.roastedroot.proxywasm.ProxyMap; -import io.roastedroot.proxywasm.StreamType; -import io.roastedroot.proxywasm.WasmException; -import io.roastedroot.proxywasm.WasmResult; -import jakarta.ws.rs.core.MultivaluedMap; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -class JaxrsHandler extends ChainedHandler { - - private Handler next; - - 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; - } - } - - private int tickPeriodMilliseconds; - private ProxyMap httpRequestHeaders = new JaxrsProxyMap(); - private ProxyMap httpRequestTrailers = new JaxrsProxyMap(); - private ProxyMap httpResponseHeaders = new JaxrsProxyMap(); - private ProxyMap httpResponseTrailers = new JaxrsProxyMap(); - private ProxyMap grpcReceiveInitialMetadata = new JaxrsProxyMap(); - private ProxyMap grpcReceiveTrailerMetadata = new JaxrsProxyMap(); - private HttpResponse senthttpResponse; - - private byte[] funcCallData = new byte[0]; - private byte[] httpRequestBody = new byte[0]; - private byte[] httpResponseBody = new byte[0]; - private byte[] downStreamData = new byte[0]; - private byte[] upstreamData = new byte[0]; - private byte[] grpcReceiveBuffer = new byte[0]; - - static final boolean DEBUG = "true".equals(System.getenv("DEBUG")); - - JaxrsHandler() { - this(new Handler() {}); - } - - JaxrsHandler(Handler next) { - this.next = next; - } - - @Override - protected Handler next() { - return next; - } - - @Override - public void log(LogLevel level, String message) throws WasmException { - if (DEBUG) { - System.out.println(level + ": " + message); - } - } - - @Override - public WasmResult setTickPeriodMilliseconds(int tickPeriodMilliseconds) { - this.tickPeriodMilliseconds = tickPeriodMilliseconds; - return WasmResult.OK; - } - - public int getTickPeriodMilliseconds() { - return tickPeriodMilliseconds; - } - - @Override - public ProxyMap getHttpRequestHeaders() { - return httpRequestHeaders; - } - - @Override - public ProxyMap getHttpRequestTrailers() { - return httpRequestTrailers; - } - - @Override - public ProxyMap getHttpResponseHeaders() { - return httpResponseHeaders; - } - - @Override - public ProxyMap getHttpResponseTrailers() { - return httpResponseTrailers; - } - - @Override - public ProxyMap getGrpcReceiveInitialMetaData() { - return grpcReceiveInitialMetadata; - } - - @Override - public ProxyMap getGrpcReceiveTrailerMetaData() { - return grpcReceiveTrailerMetadata; - } - - @Override - public WasmResult setHttpRequestHeaders(ProxyMap headers) { - this.httpRequestHeaders = headers; - return WasmResult.OK; - } - - public WasmResult setHttpRequestHeaders(MultivaluedMap headers) { - return this.setHttpRequestHeaders(new JaxrsProxyMap(headers)); - } - - @Override - public WasmResult setHttpRequestTrailers(ProxyMap trailers) { - this.httpRequestTrailers = trailers; - return WasmResult.OK; - } - - public WasmResult setHttpRequestTrailers(MultivaluedMap headers) { - return this.setHttpRequestTrailers(new JaxrsProxyMap(headers)); - } - - @Override - public WasmResult setHttpResponseHeaders(ProxyMap headers) { - this.httpResponseHeaders = headers; - return WasmResult.OK; - } - - public WasmResult setHttpResponseHeaders(MultivaluedMap headers) { - return this.setHttpResponseHeaders(new JaxrsProxyMap(headers)); - } - - @Override - public WasmResult setHttpResponseTrailers(ProxyMap trailers) { - this.httpResponseTrailers = trailers; - return WasmResult.OK; - } - - @Override - public WasmResult setGrpcReceiveInitialMetaData(ProxyMap metadata) { - this.grpcReceiveInitialMetadata = metadata; - return WasmResult.OK; - } - - @Override - public WasmResult setGrpcReceiveTrailerMetaData(ProxyMap metadata) { - this.grpcReceiveTrailerMetadata = metadata; - return WasmResult.OK; - } - - @Override - public byte[] getFuncCallData() { - return this.funcCallData; - } - - @Override - public byte[] getGrpcReceiveBuffer() { - return this.grpcReceiveBuffer; - } - - @Override - public byte[] getUpstreamData() { - return this.upstreamData; - } - - @Override - public byte[] getDownStreamData() { - return this.downStreamData; - } - - @Override - public byte[] getHttpResponseBody() { - return this.httpResponseBody; - } - - @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); - } - - @Override - public WasmResult setHttpResponseBody(byte[] body) { - this.httpResponseBody = body; - return WasmResult.OK; - } - - public void appendHttpResponseBody(byte[] body) { - this.httpResponseBody = Helpers.append(this.httpResponseBody, body); - } - - @Override - public WasmResult setDownStreamData(byte[] data) { - this.downStreamData = data; - return WasmResult.OK; - } - - @Override - public WasmResult setUpstreamData(byte[] data) { - this.upstreamData = data; - return WasmResult.OK; - } - - @Override - public WasmResult setGrpcReceiveBuffer(byte[] buffer) { - this.grpcReceiveBuffer = buffer; - return WasmResult.OK; - } - - @Override - public WasmResult setFuncCallData(byte[] data) { - this.funcCallData = data; - return WasmResult.OK; - } - - @Override - public WasmResult sendHttpResponse( - int responseCode, - byte[] responseCodeDetails, - byte[] responseBody, - ProxyMap additionalHeaders, - int grpcStatus) { - this.senthttpResponse = - new HttpResponse( - responseCode, - responseCodeDetails, - responseBody, - additionalHeaders, - grpcStatus); - return WasmResult.OK; - } - - public HttpResponse getSentHttpResponse() { - return senthttpResponse; - } - - public static class HttpCall { - public enum Type { - REGULAR, - DISPATCH - } - - public final int id; - public final Type callType; - public final String uri; - public final Object headers; - public final byte[] body; - public final ProxyMap trailers; - public final int timeoutMilliseconds; - - public HttpCall( - int id, - Type callType, - String uri, - ProxyMap headers, - byte[] body, - ProxyMap trailers, - int timeoutMilliseconds) { - this.id = id; - this.callType = callType; - this.uri = uri; - this.headers = headers; - this.body = body; - this.trailers = trailers; - this.timeoutMilliseconds = timeoutMilliseconds; - } - } - - private final AtomicInteger lastCallId = new AtomicInteger(0); - private final HashMap httpCalls = new HashMap(); - - public HashMap getHttpCalls() { - return httpCalls; - } - - @Override - public int httpCall( - String uri, ProxyMap headers, byte[] body, ProxyMap trailers, int timeoutMilliseconds) - throws WasmException { - var id = lastCallId.incrementAndGet(); - HttpCall value = - new HttpCall( - id, - HttpCall.Type.REGULAR, - uri, - headers, - body, - trailers, - timeoutMilliseconds); - httpCalls.put(id, value); - return id; - } - - @Override - public int dispatchHttpCall( - String upstreamName, - ProxyMap headers, - byte[] body, - ProxyMap trailers, - int timeoutMilliseconds) - throws WasmException { - var id = lastCallId.incrementAndGet(); - HttpCall value = - new HttpCall( - id, - HttpCall.Type.DISPATCH, - upstreamName, - headers, - body, - trailers, - timeoutMilliseconds); - httpCalls.put(id, value); - return id; - } - - public static class Metric { - - public final int id; - public final MetricType type; - public final String name; - public long value; - - public Metric(int id, MetricType type, String name) { - this.id = id; - this.type = type; - this.name = name; - } - } - - private final AtomicInteger lastMetricId = new AtomicInteger(0); - private HashMap metrics = new HashMap(); - private HashMap metricsByName = new HashMap(); - - @Override - public int defineMetric(MetricType type, String name) throws WasmException { - var id = lastMetricId.incrementAndGet(); - Metric value = new Metric(id, type, name); - metrics.put(id, value); - metricsByName.put(name, value); - return id; - } - - @Override - public long getMetric(int metricId) throws WasmException { - var metric = metrics.get(metricId); - if (metric == null) { - throw new WasmException(WasmResult.NOT_FOUND); - } - return metric.value; - } - - public Metric getMetric(String name) { - return metricsByName.get(name); - } - - @Override - public WasmResult incrementMetric(int metricId, long value) { - var metric = metrics.get(metricId); - if (metric == null) { - return WasmResult.NOT_FOUND; - } - metric.value += value; - return WasmResult.OK; - } - - @Override - public WasmResult recordMetric(int metricId, long value) { - var metric = metrics.get(metricId); - if (metric == null) { - return WasmResult.NOT_FOUND; - } - metric.value = value; - return WasmResult.OK; - } - - @Override - public WasmResult removeMetric(int metricId) { - Metric metric = metrics.remove(metricId); - if (metric == null) { - return WasmResult.NOT_FOUND; - } - metricsByName.remove(metric.name); - return WasmResult.OK; - } - - private Action action; - - @Override - public WasmResult setAction(StreamType streamType, Action action) { - this.action = action; - return WasmResult.OK; - } - - public Action getAction() { - return action; - } - - final HashMap, byte[]> properties = new HashMap<>(); - - @Override - public byte[] getProperty(List path) throws WasmException { - return properties.get(path); - } - - @Override - public WasmResult setProperty(List path, byte[] value) { - 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/JaxrsProxyMap.java index 6d95437..7f6295b 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/JaxrsProxyMap.java @@ -8,9 +8,9 @@ import java.util.Objects; import java.util.stream.Stream; -public class JaxrsProxyMap implements ProxyMap { +public class JaxrsProxyMap implements ProxyMap { - final MultivaluedMap entries; + final MultivaluedMap entries; public JaxrsProxyMap() { this.entries = new MultivaluedHashMap<>(); @@ -27,7 +27,7 @@ public JaxrsProxyMap(ProxyMap other) { } } - public JaxrsProxyMap(MultivaluedMap other) { + public JaxrsProxyMap(MultivaluedMap other) { this.entries = other; } @@ -38,12 +38,12 @@ public int size() { @Override public void add(String key, String value) { - entries.add(key, value); + entries.add(key, (T) value); } @Override public void put(String key, String value) { - entries.put(key, List.of(value)); + entries.put(key, List.of((T) value)); } static Iterable toIterable(Stream stream) { @@ -54,12 +54,29 @@ static Iterable toIterable(Stream stream) { public Iterable> entries() { return toIterable( entries.entrySet().stream() - .flatMap(x -> x.getValue().stream().map(y -> Map.entry(x.getKey(), y)))); + .flatMap( + entry -> + entry.getValue().stream() + .map( + value -> + Map.entry( + entry.getKey(), + asString(value))))); } @Override public String get(String key) { - return entries.get(key).stream().findFirst().orElse(null); + return entries.get(key).stream().findFirst().map(JaxrsProxyMap::asString).orElse(null); + } + + private static String asString(Object x) { + if (x == null) { + return null; + } + if (x.getClass() == String.class) { + return (String) x; + } + return x.toString(); } @Override diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/PluginHandler.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/PluginHandler.java new file mode 100644 index 0000000..cc5bcb3 --- /dev/null +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/PluginHandler.java @@ -0,0 +1,283 @@ +package io.roastedroot.proxywasm.jaxrs; + +import static io.roastedroot.proxywasm.Helpers.bytes; +import static io.roastedroot.proxywasm.WellKnownProperties.PLUGIN_NAME; +import static io.roastedroot.proxywasm.WellKnownProperties.PLUGIN_VM_ID; + +import io.roastedroot.proxywasm.ChainedHandler; +import io.roastedroot.proxywasm.Handler; +import io.roastedroot.proxywasm.LogLevel; +import io.roastedroot.proxywasm.MetricType; +import io.roastedroot.proxywasm.ProxyMap; +import io.roastedroot.proxywasm.WasmException; +import io.roastedroot.proxywasm.WasmResult; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +class PluginHandler extends ChainedHandler { + + // ////////////////////////////////////////////////////////////////////// + // Filter Chain Methods + // ////////////////////////////////////////////////////////////////////// + private Handler next; + + PluginHandler() { + this(new Handler() {}); + } + + PluginHandler(Handler next) { + this.next = next; + } + + @Override + protected Handler next() { + return next; + } + + // ////////////////////////////////////////////////////////////////////// + // Properties + // ////////////////////////////////////////////////////////////////////// + + String name = "default"; + + public String getName() { + return name; + } + + public void setName(String name) { + Objects.requireNonNull(name); + this.name = name; + } + + private final HashMap, byte[]> properties = new HashMap<>(); + + @Override + public byte[] getProperty(List path) throws WasmException { + // TODO: do we need field for vm_id and root_id? + if (PLUGIN_VM_ID.equals(path)) { + return bytes(name); + } + if (PLUGIN_NAME.equals(path)) { + return bytes(name); + } + return properties.get(path); + } + + @Override + public WasmResult setProperty(List path, byte[] value) { + properties.put(path, value); + return WasmResult.OK; + } + + // ////////////////////////////////////////////////////////////////////// + // Logging + // ////////////////////////////////////////////////////////////////////// + + static final boolean DEBUG = "true".equals(System.getenv("DEBUG")); + + @Override + public void log(LogLevel level, String message) throws WasmException { + // TODO: improve + if (DEBUG) { + System.out.println(level + ": " + message); + } + } + + @Override + public LogLevel getLogLevel() throws WasmException { + // TODO: improve + return super.getLogLevel(); + } + + // ////////////////////////////////////////////////////////////////////// + // Timers + // ////////////////////////////////////////////////////////////////////// + + private int tickPeriodMilliseconds; + + public int getTickPeriodMilliseconds() { + return tickPeriodMilliseconds; + } + + @Override + public WasmResult setTickPeriodMilliseconds(int tickPeriodMilliseconds) { + this.tickPeriodMilliseconds = tickPeriodMilliseconds; + return WasmResult.OK; + } + + // ////////////////////////////////////////////////////////////////////// + // Foreign function interface (FFI) + // ////////////////////////////////////////////////////////////////////// + + private byte[] funcCallData = new byte[0]; + + @Override + public byte[] getFuncCallData() { + return this.funcCallData; + } + + @Override + public WasmResult setFuncCallData(byte[] data) { + this.funcCallData = data; + return WasmResult.OK; + } + + // ////////////////////////////////////////////////////////////////////// + // HTTP calls + // ////////////////////////////////////////////////////////////////////// + + public static class HttpCall { + public enum Type { + REGULAR, + DISPATCH + } + + public final int id; + public final Type callType; + public final String uri; + public final Object headers; + public final byte[] body; + public final ProxyMap trailers; + public final int timeoutMilliseconds; + + public HttpCall( + int id, + Type callType, + String uri, + ProxyMap headers, + byte[] body, + ProxyMap trailers, + int timeoutMilliseconds) { + this.id = id; + this.callType = callType; + this.uri = uri; + this.headers = headers; + this.body = body; + this.trailers = trailers; + this.timeoutMilliseconds = timeoutMilliseconds; + } + } + + private final AtomicInteger lastCallId = new AtomicInteger(0); + private final HashMap httpCalls = new HashMap(); + + public HashMap getHttpCalls() { + return httpCalls; + } + + @Override + public int httpCall( + String uri, ProxyMap headers, byte[] body, ProxyMap trailers, int timeoutMilliseconds) + throws WasmException { + var id = lastCallId.incrementAndGet(); + HttpCall value = + new HttpCall( + id, + HttpCall.Type.REGULAR, + uri, + headers, + body, + trailers, + timeoutMilliseconds); + httpCalls.put(id, value); + return id; + } + + @Override + public int dispatchHttpCall( + String upstreamName, + ProxyMap headers, + byte[] body, + ProxyMap trailers, + int timeoutMilliseconds) + throws WasmException { + var id = lastCallId.incrementAndGet(); + HttpCall value = + new HttpCall( + id, + HttpCall.Type.DISPATCH, + upstreamName, + headers, + body, + trailers, + timeoutMilliseconds); + httpCalls.put(id, value); + return id; + } + + // ////////////////////////////////////////////////////////////////////// + // Metrics + // ////////////////////////////////////////////////////////////////////// + + public static class Metric { + + public final int id; + public final MetricType type; + public final String name; + public long value; + + public Metric(int id, MetricType type, String name) { + this.id = id; + this.type = type; + this.name = name; + } + } + + private final AtomicInteger lastMetricId = new AtomicInteger(0); + private HashMap metrics = new HashMap(); + private HashMap metricsByName = new HashMap(); + + @Override + public int defineMetric(MetricType type, String name) throws WasmException { + var id = lastMetricId.incrementAndGet(); + Metric value = new Metric(id, type, name); + metrics.put(id, value); + metricsByName.put(name, value); + return id; + } + + @Override + public long getMetric(int metricId) throws WasmException { + var metric = metrics.get(metricId); + if (metric == null) { + throw new WasmException(WasmResult.NOT_FOUND); + } + return metric.value; + } + + public Metric getMetric(String name) { + return metricsByName.get(name); + } + + @Override + public WasmResult incrementMetric(int metricId, long value) { + var metric = metrics.get(metricId); + if (metric == null) { + return WasmResult.NOT_FOUND; + } + metric.value += value; + return WasmResult.OK; + } + + @Override + public WasmResult recordMetric(int metricId, long value) { + var metric = metrics.get(metricId); + if (metric == null) { + return WasmResult.NOT_FOUND; + } + metric.value = value; + return WasmResult.OK; + } + + @Override + public WasmResult removeMetric(int metricId) { + Metric metric = metrics.remove(metricId); + if (metric == null) { + return WasmResult.NOT_FOUND; + } + metricsByName.remove(metric.name); + return WasmResult.OK; + } +} 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 e14cfde..e1bf473 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,17 +1,30 @@ package io.roastedroot.proxywasm.jaxrs; +import io.roastedroot.proxywasm.Action; +import io.roastedroot.proxywasm.HttpContext; import io.roastedroot.proxywasm.StartException; import jakarta.inject.Inject; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.ContainerResponseContext; import jakarta.ws.rs.container.ContainerResponseFilter; import jakarta.ws.rs.container.PreMatching; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ReaderInterceptor; +import jakarta.ws.rs.ext.ReaderInterceptorContext; +import jakarta.ws.rs.ext.WriterInterceptor; +import jakarta.ws.rs.ext.WriterInterceptorContext; +import java.io.ByteArrayOutputStream; import java.io.IOException; @PreMatching -public class ProxyWasmFilter implements ContainerRequestFilter, ContainerResponseFilter { +public class ProxyWasmFilter + implements ContainerRequestFilter, + ReaderInterceptor, + WriterInterceptor, + ContainerResponseFilter { + private static final String FILTER_CONTEXT_PROPERTY_NAME = "WasmHttpFilterContext"; private final WasmPluginFactory pluginFactory; @@ -20,6 +33,20 @@ public ProxyWasmFilter(WasmPluginFactory pluginFactory) { this.pluginFactory = pluginFactory; } + // 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 PluginHandler pluginHandler; + final HttpHandler handler; + final HttpContext wasm; + + public WasmHttpFilterContext(WasmPlugin plugin) { + this.pluginHandler = plugin.pluginHandler(); + this.handler = new HttpHandler(plugin.pluginHandler()); + this.wasm = plugin.proxyWasm().createHttpContext(this.handler); + } + } + @Override public void filter(ContainerRequestContext requestContext) throws IOException { @@ -31,17 +58,155 @@ public void filter(ContainerRequestContext requestContext) throws IOException { Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()); } - // so we can continue to use the plugin in the other filter methods. - requestContext.setProperty("WasmPlugin", plugin); - var wasmHttpContext = plugin.createHttpContext(); - requestContext.setProperty("WasmHttpContext", wasmHttpContext); + var wasmHttpFilterContext = new WasmHttpFilterContext(plugin); + requestContext.setProperty(FILTER_CONTEXT_PROPERTY_NAME, wasmHttpFilterContext); + + // the plugin may not be interested in the request headers. + if (wasmHttpFilterContext.wasm.hasOnRequestHeaders()) { + + wasmHttpFilterContext.handler.setRequestContext(requestContext); + var action = wasmHttpFilterContext.wasm.callOnRequestHeaders(false); + if (action == Action.CONTINUE) { + // continue means plugin is done reading the headers. + wasmHttpFilterContext.handler.setRequestContext(null); + } + + // does the plugin want to respond early? + HttpHandler.HttpResponse sendResponse = + wasmHttpFilterContext.handler.getSentHttpResponse(); + if (sendResponse != null) { + requestContext.abortWith(sendResponse.toResponse()); + } + } + } + + @Override + public Object aroundReadFrom(ReaderInterceptorContext ctx) + throws IOException, WebApplicationException { + + var wasmHttpFilterContext = + (WasmHttpFilterContext) ctx.getProperty(FILTER_CONTEXT_PROPERTY_NAME); + if (wasmHttpFilterContext == null) { + throw new WebApplicationException( + Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()); + } + + // the plugin may not be interested in the request body. + if (wasmHttpFilterContext.wasm.hasOnRequestBody()) { + // TODO: find out if it's more efficient to read the body in chunks and do multiple + // callOnRequestBody calls. + byte[] bytes = ctx.getInputStream().readAllBytes(); + wasmHttpFilterContext.handler.setHttpRequestBody(bytes); + var action = wasmHttpFilterContext.wasm.callOnRequestBody(true); + bytes = wasmHttpFilterContext.handler.getHttpRequestBody(); + if (action == Action.CONTINUE) { + // continue means plugin is done reading the body. + wasmHttpFilterContext.handler.setHttpRequestBody(null); + } - plugin.handler().setHttpRequestHeaders(requestContext.getHeaders()); - wasmHttpContext.callOnRequestHeaders(false); + // 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.handler.getSentHttpResponse(); + if (sendResponse != null) { + throw new WebApplicationException(sendResponse.toResponse()); + } + + // plugin may have modified the body + ctx.setInputStream(new java.io.ByteArrayInputStream(bytes)); + } + return ctx.proceed(); } @Override public void filter( ContainerRequestContext requestContext, ContainerResponseContext responseContext) - throws IOException {} + throws IOException { + var wasmHttpFilterContext = + (WasmHttpFilterContext) requestContext.getProperty(FILTER_CONTEXT_PROPERTY_NAME); + if (wasmHttpFilterContext == null) { + throw new WebApplicationException( + Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()); + } + + // the plugin may not be interested in the request headers. + if (wasmHttpFilterContext.wasm.hasOnResponseHeaders()) { + + wasmHttpFilterContext.handler.setResponseContext(responseContext); + var action = wasmHttpFilterContext.wasm.callOnResponseHeaders(false); + if (action == Action.CONTINUE) { + // continue means plugin is done reading the headers. + wasmHttpFilterContext.handler.setResponseContext(null); + } + + // does the plugin want to respond early? + HttpHandler.HttpResponse sendResponse = + wasmHttpFilterContext.handler.getSentHttpResponse(); + if (sendResponse != null) { + requestContext.abortWith(sendResponse.toResponse()); + } + } + } + + @Override + public void aroundWriteTo(WriterInterceptorContext ctx) + throws IOException, WebApplicationException { + var wasmHttpFilterContext = + (WasmHttpFilterContext) ctx.getProperty(FILTER_CONTEXT_PROPERTY_NAME); + if (wasmHttpFilterContext == null) { + throw new WebApplicationException( + Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()); + } + + // the plugin may not be interested in the request body. + if (wasmHttpFilterContext.wasm.hasOnResponseBody()) { + + var original = ctx.getOutputStream(); + ctx.setOutputStream( + new ByteArrayOutputStream() { + @Override + public void close() throws IOException { + super.close(); + + // TODO: find out if it's more efficient to read the body in chunks and + // do + // multiple callOnRequestBody calls. + + byte[] bytes = this.toByteArray(); + wasmHttpFilterContext.handler.setHttpResponseBody(bytes); + var action = wasmHttpFilterContext.wasm.callOnResponseBody(true); + bytes = wasmHttpFilterContext.handler.getHttpResponseBody(); + if (action == Action.CONTINUE) { + // continue means plugin is done reading the body. + wasmHttpFilterContext.handler.setHttpResponseBody(null); + } + + // does the plugin want to respond early? + HttpHandler.HttpResponse sendResponse = + wasmHttpFilterContext.handler.getSentHttpResponse(); + if (sendResponse != null) { + throw new WebApplicationException(sendResponse.toResponse()); + } + + // plugin may have modified the body + original.write(bytes); + original.close(); + + // clean up... + // wasmHttpFilterContext.wasm.close(); + // + // wasmHttpFilterContext.wasm.getProxyWasm().close(); + } + }); + } else { + // clean up... + // wasmHttpFilterContext.wasm.close(); + // wasmHttpFilterContext.wasm.getProxyWasm().close(); + } + + ctx.proceed(); + } } diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java index 0ffb440..d76d7c7 100644 --- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java +++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java @@ -3,39 +3,31 @@ import com.dylibso.chicory.runtime.ImportMemory; import com.dylibso.chicory.runtime.Instance; import com.dylibso.chicory.wasm.WasmModule; -import io.roastedroot.proxywasm.HttpContext; import io.roastedroot.proxywasm.ProxyWasm; import io.roastedroot.proxywasm.StartException; import java.util.Objects; public class WasmPlugin { - private final String name; private final ProxyWasm proxyWasm; - private final JaxrsHandler handler; + private final PluginHandler handler; - public WasmPlugin(String name, ProxyWasm proxyWasm, JaxrsHandler handler) { - Objects.requireNonNull(name); + public WasmPlugin(ProxyWasm proxyWasm, PluginHandler handler) { Objects.requireNonNull(proxyWasm); Objects.requireNonNull(handler); - this.name = name; this.proxyWasm = proxyWasm; this.handler = handler; } public String name() { - return name; + return handler.getName(); } ProxyWasm proxyWasm() { return proxyWasm; } - HttpContext createHttpContext() { - return proxyWasm.createHttpContext(this.handler); - } - - JaxrsHandler handler() { + PluginHandler pluginHandler() { return handler; } @@ -45,12 +37,11 @@ public static WasmPlugin.Builder builder() { public static class Builder implements Cloneable { - JaxrsHandler handler = new JaxrsHandler(); - String name = "default"; + PluginHandler handler = new PluginHandler(); ProxyWasm.Builder proxyWasmBuilder = ProxyWasm.builder().withPluginHandler(handler); public WasmPlugin.Builder withName(String name) { - this.name = name; + this.handler.name = name; return this; } @@ -92,7 +83,7 @@ public WasmPlugin build(Instance instance) throws StartException { } public WasmPlugin build(ProxyWasm proxyWasm) throws StartException { - return new WasmPlugin(name, proxyWasm, handler); + return new WasmPlugin(proxyWasm, handler); } } } diff --git a/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java index 3dae5c1..fd8d574 100644 --- a/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java +++ b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java @@ -16,11 +16,19 @@ public class App { @Produces public WasmPluginFactory createFoo() throws StartException { - return () -> WasmPlugin.builder().withName("foo").build(httpHeadersModule); + return () -> + WasmPlugin.builder() + .withName("foo") + .withPluginConfig("{\"header\": \"x-wasm-header\", \"value\": \"foo\"}") + .build(httpHeadersModule); } @Produces public WasmPluginFactory createBar() throws StartException { - return () -> WasmPlugin.builder().withName("bar").build(httpHeadersModule); + return () -> + WasmPlugin.builder() + .withName("bar") + .withPluginConfig("{\"header\": \"x-wasm-header\", \"value\": \"bar\"}") + .build(httpHeadersModule); } } diff --git a/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResourceTest.java b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResourceTest.java index b8804e7..d5398b8 100644 --- a/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResourceTest.java +++ b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResourceTest.java @@ -10,7 +10,17 @@ public class ExampleResourceTest { @Test public void testFooBar() { - given().when().get("/example/foo").then().statusCode(200); - given().when().get("/example/bar").then().statusCode(200); + given().when() + .get("/example/foo") + .then() + .statusCode(200) + .header("x-proxy-wasm-go-sdk-example", "http_headers") + .header("x-wasm-header", "foo"); + given().when() + .get("/example/bar") + .then() + .statusCode(200) + .header("x-proxy-wasm-go-sdk-example", "http_headers") + .header("x-wasm-header", "bar"); } } From 3a25bb05804dcff5c939c6f773ce8d997c7eb6d7 Mon Sep 17 00:00:00 2001 From: Hiram Chirino Date: Tue, 18 Mar 2025 12:54:43 -0400 Subject: [PATCH 5/5] =?UTF-8?q?Only=20run=20the=20jaxrs=20tests=20on=20JDK?= =?UTF-8?q?=2017=20or=20newer=20since=20that=E2=80=99s=20where=20Quarkus?= =?UTF-8?q?=20is=20supported.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hiram Chirino --- proxy-wasm-jaxrs/pom.xml | 122 +++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/proxy-wasm-jaxrs/pom.xml b/proxy-wasm-jaxrs/pom.xml index 921259b..938a7a8 100644 --- a/proxy-wasm-jaxrs/pom.xml +++ b/proxy-wasm-jaxrs/pom.xml @@ -71,60 +71,79 @@ rest-assured test - - - - - ${quarkus.platform.group-id} - quarkus-maven-plugin - ${quarkus.platform.version} - true - - - - build - generate-code - generate-code-tests - native-image-agent - - - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - ${maven.home} - - - - - - integration-test - verify - - - - - - maven-surefire-plugin - ${surefire-plugin.version} - - - org.jboss.logmanager.LogManager - ${maven.home} - - - - - - + + + + jdk-lower-than-17 + + (,17] + + + true + true + + + + + + jdk-17-and-newer + + [17,) + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + native-image-agent + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + integration-test + verify + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + native @@ -138,4 +157,5 @@ +