+ 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-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
new file mode 100644
index 0000000..9d479a3
--- /dev/null
+++ b/proxy-wasm-java-host/src/main/java/io/roastedroot/proxywasm/WellKnownProperties.java
@@ -0,0 +1,69 @@
+package io.roastedroot.proxywasm;
+
+import java.util.List;
+
+public final class WellKnownProperties {
+ private WellKnownProperties() {}
+
+ // Proxy-Wasm properties
+ 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 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 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 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 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/.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..938a7a8
--- /dev/null
+++ b/proxy-wasm-jaxrs/pom.xml
@@ -0,0 +1,161 @@
+
+
+ 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
+
+
+
+
+
+
+
+ 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
+
+
+ native
+
+
+
+ false
+ true
+
+
+
+
+
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/JaxrsProxyMap.java b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsProxyMap.java
new file mode 100644
index 0000000..7f6295b
--- /dev/null
+++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/JaxrsProxyMap.java
@@ -0,0 +1,105 @@
+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, (T) value);
+ }
+
+ @Override
+ public void put(String key, String value) {
+ entries.put(key, List.of((T) value));
+ }
+
+ static Iterable toIterable(Stream stream) {
+ return stream::iterator;
+ }
+
+ @Override
+ public Iterable extends Map.Entry> entries() {
+ return toIterable(
+ entries.entrySet().stream()
+ .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().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
+ 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/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/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
new file mode 100644
index 0000000..e1bf473
--- /dev/null
+++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/ProxyWasmFilter.java
@@ -0,0 +1,212 @@
+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,
+ ReaderInterceptor,
+ WriterInterceptor,
+ ContainerResponseFilter {
+ private static final String FILTER_CONTEXT_PROPERTY_NAME = "WasmHttpFilterContext";
+
+ private final WasmPluginFactory pluginFactory;
+
+ @Inject
+ 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 {
+
+ WasmPlugin plugin = null;
+ try {
+ plugin = pluginFactory.create();
+ } catch (StartException e) {
+ requestContext.abortWith(
+ Response.status(Response.Status.INTERNAL_SERVER_ERROR).build());
+ }
+
+ 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);
+ }
+
+ // 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 {
+ 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
new file mode 100644
index 0000000..d76d7c7
--- /dev/null
+++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPlugin.java
@@ -0,0 +1,89 @@
+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;
+import java.util.Objects;
+
+public class WasmPlugin {
+
+ private final ProxyWasm proxyWasm;
+ private final PluginHandler handler;
+
+ public WasmPlugin(ProxyWasm proxyWasm, PluginHandler handler) {
+ Objects.requireNonNull(proxyWasm);
+ Objects.requireNonNull(handler);
+ this.proxyWasm = proxyWasm;
+ this.handler = handler;
+ }
+
+ public String name() {
+ return handler.getName();
+ }
+
+ ProxyWasm proxyWasm() {
+ return proxyWasm;
+ }
+
+ PluginHandler pluginHandler() {
+ return handler;
+ }
+
+ public static WasmPlugin.Builder builder() {
+ return new WasmPlugin.Builder();
+ }
+
+ public static class Builder implements Cloneable {
+
+ PluginHandler handler = new PluginHandler();
+ ProxyWasm.Builder proxyWasmBuilder = ProxyWasm.builder().withPluginHandler(handler);
+
+ public WasmPlugin.Builder withName(String name) {
+ this.handler.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(proxyWasm, handler);
+ }
+ }
+}
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
new file mode 100644
index 0000000..0220db2
--- /dev/null
+++ b/proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/WasmPluginFeature.java
@@ -0,0 +1,49 @@
+package io.roastedroot.proxywasm.jaxrs;
+
+import io.roastedroot.proxywasm.StartException;
+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 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(), factory);
+ }
+ }
+
+ @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) {
+ 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
new file mode 100644
index 0000000..fd8d574
--- /dev/null
+++ b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/App.java
@@ -0,0 +1,34 @@
+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 WasmPluginFactory createFoo() throws StartException {
+ 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")
+ .withPluginConfig("{\"header\": \"x-wasm-header\", \"value\": \"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..d5398b8
--- /dev/null
+++ b/proxy-wasm-jaxrs/src/test/java/io/roastedroot/proxywasm/jaxrs/ExampleResourceTest.java
@@ -0,0 +1,26 @@
+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)
+ .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");
+ }
+}
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