for WasmPluginFeature
+ resourceConfig.register(
+ new WasmPluginFeature(
+ new io.roastedroot.proxywasm.jaxrs.ServerAdaptor(),
+ App.headerTests(),
+ App.headerTestsNotShared(),
+ App.tickTests(),
+ App.ffiTests(),
+ App.httpCallTests()));
+
+ ServletHolder jerseyServlet = new ServletHolder(new ServletContainer(resourceConfig));
+ jerseyServlet.setInitOrder(0);
+ context.addServlet(jerseyServlet, "/*");
+
+ server.setHandler(context);
+ server.start();
+ }
+
+ @AfterEach
+ public void tearDown() throws Exception {
+ if (server != null) {
+ server.stop();
+ }
+ }
+}
diff --git a/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/FFITest.java b/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/FFITest.java
new file mode 100644
index 0000000..b7b231a
--- /dev/null
+++ b/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/FFITest.java
@@ -0,0 +1,18 @@
+package io.roastedroot.proxywasm.jaxrs.example.tests;
+
+import static org.hamcrest.Matchers.equalTo;
+
+import org.junit.jupiter.api.Test;
+
+public class FFITest extends BaseTest {
+
+ @Test
+ public void reverse() throws InterruptedException {
+ given().body("My Test")
+ .when()
+ .post("/ffiTests/reverse")
+ .then()
+ .statusCode(200)
+ .body(equalTo("tseT yM"));
+ }
+}
diff --git a/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HeadersTest.java b/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HeadersTest.java
new file mode 100644
index 0000000..0023347
--- /dev/null
+++ b/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HeadersTest.java
@@ -0,0 +1,48 @@
+package io.roastedroot.proxywasm.jaxrs.example.tests;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.equalTo;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * This test verifies that the plugin can modify request and response headers.
+ *
+ * It also verifies that the plugin state is shared between requests or can be isolated using configuration.
+ */
+public class HeadersTest extends BaseTest {
+
+ @Test
+ public void testShared() {
+ given().when()
+ .get("/headerTests")
+ .then()
+ .statusCode(200)
+ .header("x-response-counter", "1")
+ .body(equalTo("counter: 1"));
+
+ given().when()
+ .get("/headerTests")
+ .then()
+ .statusCode(200)
+ .header("x-response-counter", "2")
+ .body(equalTo("counter: 2"));
+ }
+
+ @Test
+ public void testNotShared() {
+ given().when()
+ .get("/headerTestsNotShared")
+ .then()
+ .statusCode(200)
+ .header("x-response-counter", "1")
+ .body(equalTo("counter: 1"));
+
+ given().when()
+ .get("/headerTestsNotShared")
+ .then()
+ .statusCode(200)
+ .header("x-response-counter", "1")
+ .body(equalTo("counter: 1"));
+ }
+}
diff --git a/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java b/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java
new file mode 100644
index 0000000..fb41721
--- /dev/null
+++ b/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java
@@ -0,0 +1,21 @@
+package io.roastedroot.proxywasm.jaxrs.example.tests;
+
+import static org.hamcrest.Matchers.equalTo;
+
+import io.roastedroot.proxywasm.StartException;
+import org.junit.jupiter.api.Test;
+
+public class HttpCallTest extends BaseTest {
+
+ @Test
+ public void test() throws InterruptedException, StartException {
+ // the wasm plugin will forward the request to the /ok endpoint
+ given().header("test", "ok")
+ .when()
+ .get("/httpCallTests")
+ .then()
+ .statusCode(200)
+ .body(equalTo("ok"))
+ .header("echo-test", "ok");
+ }
+}
diff --git a/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/TickTest.java b/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/TickTest.java
new file mode 100644
index 0000000..6e66789
--- /dev/null
+++ b/proxy-wasm-jaxrs-jersey/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/TickTest.java
@@ -0,0 +1,44 @@
+package io.roastedroot.proxywasm.jaxrs.example.tests;
+
+import static io.restassured.RestAssured.given;
+import static io.roastedroot.proxywasm.jaxrs.example.Helpers.isTrue;
+import static org.hamcrest.Matchers.equalTo;
+
+import org.junit.jupiter.api.Test;
+
+public class TickTest extends BaseTest {
+
+ @Test
+ public void tick() throws InterruptedException {
+ // plugin should not have received any ticks.
+ given().when().get("/tickTests/get").then().statusCode(200).body(equalTo("0"));
+
+ // ask the plugin to enable tick events..
+ given().when().get("/tickTests/enable").then().statusCode(200).body(equalTo("ok"));
+
+ // wait a little to allow the plugin to receive some ticks. (every 100 ms)
+ Thread.sleep(300);
+
+ // stop getting ticks.
+ given().when().get("/tickTests/disable").then().statusCode(200).body(equalTo("ok"));
+
+ var counter = new String[] {"0"};
+
+ // plugin should have received at least 1 tick.
+ given().when()
+ .get("/tickTests/get")
+ .then()
+ .statusCode(200)
+ .body(
+ isTrue(
+ (String x) -> {
+ counter[0] = x;
+ return Integer.parseInt(x) >= 1;
+ }));
+
+ // since ticks were disabled the tick counter should not have changed.
+ Thread.sleep(300);
+
+ given().when().get("/tickTests/get").then().statusCode(200).body(equalTo(counter[0]));
+ }
+}
diff --git a/proxy-wasm-jaxrs-quarkus/src/main/java/io/roastedroot/proxywasm/jaxrs/quarkus/deployment/ProxyWasmJaxrsQuarkusProcessor.java b/proxy-wasm-jaxrs-quarkus/src/main/java/io/roastedroot/proxywasm/jaxrs/quarkus/deployment/ProxyWasmJaxrsQuarkusProcessor.java
index 7bd66a5..6c7cbcf 100644
--- a/proxy-wasm-jaxrs-quarkus/src/main/java/io/roastedroot/proxywasm/jaxrs/quarkus/deployment/ProxyWasmJaxrsQuarkusProcessor.java
+++ b/proxy-wasm-jaxrs-quarkus/src/main/java/io/roastedroot/proxywasm/jaxrs/quarkus/deployment/ProxyWasmJaxrsQuarkusProcessor.java
@@ -5,7 +5,7 @@
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.jaxrs.spi.deployment.AdditionalJaxRsResourceMethodAnnotationsBuildItem;
import io.roastedroot.proxywasm.jaxrs.WasmPlugin;
-import io.roastedroot.proxywasm.jaxrs.WasmPluginFeature;
+import io.roastedroot.proxywasm.jaxrs.cdi.WasmPluginFeature;
import java.util.List;
import org.jboss.jandex.DotName;
diff --git a/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java
index 44b9479..0c35873 100644
--- a/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java
+++ b/proxy-wasm-jaxrs-quarkus/src/test/java/io/roastedroot/proxywasm/jaxrs/example/tests/HttpCallTest.java
@@ -5,15 +5,11 @@
import io.quarkus.test.junit.QuarkusTest;
import io.roastedroot.proxywasm.StartException;
-import io.roastedroot.proxywasm.jaxrs.WasmPluginFeature;
-import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
@QuarkusTest
public class HttpCallTest {
- @Inject WasmPluginFeature feature;
-
@Test
public void test() throws InterruptedException, StartException {
diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/AbstractWasmPluginFeature.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/AbstractWasmPluginFeature.java
new file mode 100644
index 0000000..092d935
--- /dev/null
+++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/AbstractWasmPluginFeature.java
@@ -0,0 +1,73 @@
+package io.roastedroot.proxywasm.jaxrs;
+
+import io.roastedroot.proxywasm.StartException;
+import io.roastedroot.proxywasm.plugin.Plugin;
+import io.roastedroot.proxywasm.plugin.PluginFactory;
+import io.roastedroot.proxywasm.plugin.Pool;
+import io.roastedroot.proxywasm.plugin.ServerAdaptor;
+import jakarta.ws.rs.container.DynamicFeature;
+import jakarta.ws.rs.container.ResourceInfo;
+import jakarta.ws.rs.core.FeatureContext;
+import java.util.Collection;
+import java.util.HashMap;
+
+public abstract class AbstractWasmPluginFeature implements DynamicFeature {
+
+ private final HashMap pluginPools = new HashMap<>();
+
+ public void init(Iterable factories, ServerAdaptor serverAdaptor)
+ throws StartException {
+
+ if (!pluginPools.isEmpty()) {
+ return;
+ }
+
+ for (var factory : factories) {
+ Plugin plugin = factory.create();
+ String name = plugin.name();
+ if (this.pluginPools.containsKey(name)) {
+ throw new IllegalArgumentException("Duplicate wasm plugin name: " + name);
+ }
+ Pool pool =
+ plugin.isShared()
+ ? new Pool.SharedPlugin(serverAdaptor, plugin)
+ : new Pool.PluginPerRequest(serverAdaptor, factory, plugin);
+ this.pluginPools.put(name, pool);
+ }
+ }
+
+ public void destroy() {
+ for (var pool : pluginPools.values()) {
+ pool.close();
+ }
+ pluginPools.clear();
+ }
+
+ public Collection getPluginPools() {
+ return pluginPools.values();
+ }
+
+ public Pool pool(String name) {
+ return pluginPools.get(name);
+ }
+
+ @Override
+ public void configure(ResourceInfo resourceInfo, FeatureContext context) {
+
+ var resourceMethod = resourceInfo.getResourceMethod();
+ if (resourceMethod != null) {
+ WasmPlugin pluignNameAnnotation = resourceMethod.getAnnotation(WasmPlugin.class);
+ if (pluignNameAnnotation == null) {
+ // If no annotation on method, check the class level
+ pluignNameAnnotation =
+ resourceInfo.getResourceClass().getAnnotation(WasmPlugin.class);
+ }
+ if (pluignNameAnnotation != null) {
+ Pool pool = pluginPools.get(pluignNameAnnotation.value());
+ if (pool != null) {
+ context.register(new WasmPluginFilter(pool));
+ }
+ }
+ }
+ }
+}
diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHttpRequestAdaptor.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHttpRequestAdaptor.java
index 47a141d..1b64dab 100644
--- a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHttpRequestAdaptor.java
+++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsHttpRequestAdaptor.java
@@ -55,7 +55,7 @@
import java.util.Date;
import java.util.List;
-public abstract class JaxrsHttpRequestAdaptor implements HttpRequestAdaptor {
+public class JaxrsHttpRequestAdaptor implements HttpRequestAdaptor {
private ContainerRequestContext requestContext;
private ContainerResponseContext responseContext;
@@ -78,6 +78,26 @@ public void setResponseContext(ContainerResponseContext responseContext) {
this.responseContext = responseContext;
}
+ @Override
+ public String remoteAddress() {
+ return "";
+ }
+
+ @Override
+ public String remotePort() {
+ return "";
+ }
+
+ @Override
+ public String localAddress() {
+ return "";
+ }
+
+ @Override
+ public String localPort() {
+ return "";
+ }
+
// //////////////////////////////////////////////////////////////////////
// HTTP fields
// //////////////////////////////////////////////////////////////////////
diff --git a/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/ServerAdaptor.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/ServerAdaptor.java
new file mode 100644
index 0000000..b1c34c5
--- /dev/null
+++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/ServerAdaptor.java
@@ -0,0 +1,118 @@
+package io.roastedroot.proxywasm.jaxrs;
+
+import io.roastedroot.proxywasm.ArrayProxyMap;
+import io.roastedroot.proxywasm.ProxyMap;
+import io.roastedroot.proxywasm.plugin.HttpCallResponse;
+import io.roastedroot.proxywasm.plugin.HttpCallResponseHandler;
+import io.roastedroot.proxywasm.plugin.HttpRequestAdaptor;
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Alternative;
+import jakarta.ws.rs.core.UriBuilder;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Alternative
+@Priority(100)
+@ApplicationScoped
+public class ServerAdaptor implements io.roastedroot.proxywasm.plugin.ServerAdaptor {
+
+ ScheduledExecutorService tickExecutorService = Executors.newScheduledThreadPool(1);
+ ExecutorService executorService = Executors.newWorkStealingPool(5);
+ HttpClient client = HttpClient.newHttpClient();
+
+ @Override
+ public Runnable scheduleTick(long delay, Runnable task) {
+ var f = tickExecutorService.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS);
+ return () -> {
+ f.cancel(false);
+ };
+ }
+
+ @Override
+ public HttpRequestAdaptor httpRequestAdaptor(Object context) {
+ return new JaxrsHttpRequestAdaptor();
+ }
+
+ @Override
+ public Runnable scheduleHttpCall(
+ String method,
+ String host,
+ int port,
+ URI uri,
+ ProxyMap headers,
+ byte[] body,
+ ProxyMap trailers,
+ int timeout,
+ HttpCallResponseHandler handler)
+ throws InterruptedException {
+
+ Callable