Skip to content

Commit c1e8b7f

Browse files
committed
Add support for doing http calls from the plugin /w the jaxrs filter.
Signed-off-by: Hiram Chirino <hiram@hiramchirino.com>
1 parent 17c7894 commit c1e8b7f

File tree

11 files changed

+418
-87
lines changed

11 files changed

+418
-87
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.roastedroot.proxywasm;
2+
3+
/**
4+
* Holds constants for the well-known header keys.
5+
*/
6+
public final class WellKnownHeaders {
7+
private WellKnownHeaders() {}
8+
9+
public static final String SCHEME = ":scheme";
10+
public static final String AUTHORITY = ":authority";
11+
public static final String PATH = ":path";
12+
public static final String METHOD = ":method";
13+
}

proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/App.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,14 @@ public WasmPluginFactory httpHeaders() throws StartException {
4949
.withPluginConfig("{\"header\": \"x-wasm-header\", \"value\": \"foo\"}")
5050
.build(parseTestModule("/go-examples/http_headers/main.wasm"));
5151
}
52+
53+
@Produces
54+
public WasmPluginFactory dispatchCallOnTickTest() throws StartException {
55+
return () ->
56+
WasmPlugin.builder()
57+
.withName("dispatchCallOnTickTest")
58+
.withLogger(new MockLogger())
59+
.withUpstreams(Map.of("web_service", "localhost:8081"))
60+
.build(parseTestModule("/go-examples/dispatch_call_on_tick/main.wasm"));
61+
}
5262
}

proxy-wasm-jaxrs-quarkus-example/src/main/java/io/roastedroot/proxywasm/jaxrs/example/Resources.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,46 @@
33
import io.roastedroot.proxywasm.jaxrs.NamedWasmPlugin;
44
import jakarta.ws.rs.GET;
55
import jakarta.ws.rs.Path;
6+
import jakarta.ws.rs.container.ContainerRequestContext;
7+
import jakarta.ws.rs.core.Context;
8+
import jakarta.ws.rs.core.Response;
69

7-
@Path("/test")
10+
@Path("/")
811
public class Resources {
912

10-
@Path("/httpHeaders")
13+
@Context ContainerRequestContext requestContext;
14+
15+
@Path("/test/httpHeaders")
1116
@GET
1217
@NamedWasmPlugin("httpHeaders")
1318
public String httpHeaders() {
1419
return "hello world";
1520
}
1621

17-
@Path("/notSharedHttpHeaders")
22+
@Path("/test/notSharedHttpHeaders")
1823
@GET
1924
@NamedWasmPlugin("notSharedHttpHeaders")
2025
public String notSharedHttpHeaders() {
2126
return "hello world";
2227
}
28+
29+
@Path("/fail")
30+
@GET
31+
public Response fail() {
32+
Response.ResponseBuilder builder = Response.status(Response.Status.BAD_REQUEST);
33+
for (String header : requestContext.getHeaders().keySet()) {
34+
builder.header("echo-" + header, requestContext.getHeaderString(header));
35+
}
36+
return builder.build();
37+
}
38+
39+
@Path("/ok")
40+
@GET
41+
public Response ok() {
42+
Response.ResponseBuilder builder = Response.status(Response.Status.OK);
43+
for (String header : requestContext.getHeaders().keySet()) {
44+
builder.header("echo-" + header, requestContext.getHeaderString(header));
45+
}
46+
return builder.entity("ok").build();
47+
}
2348
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.roastedroot.proxywasm.jaxrs.example;
2+
3+
import static io.roastedroot.proxywasm.jaxrs.example.Helpers.assertLogsContain;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
6+
import io.quarkus.test.junit.QuarkusTest;
7+
import io.roastedroot.proxywasm.StartException;
8+
import io.roastedroot.proxywasm.jaxrs.WasmPlugin;
9+
import io.roastedroot.proxywasm.jaxrs.WasmPluginFeature;
10+
import jakarta.inject.Inject;
11+
import org.junit.jupiter.api.Test;
12+
13+
@QuarkusTest
14+
public class DispatchCallOnTickTest {
15+
16+
@Inject WasmPluginFeature feature;
17+
18+
@Test
19+
public void test() throws InterruptedException, StartException {
20+
WasmPlugin plugin = feature.pool("dispatchCallOnTickTest").borrow();
21+
assertNotNull(plugin);
22+
23+
var logger = (MockLogger) plugin.logger();
24+
Thread.sleep(300);
25+
26+
// for (var l : logger.loggedMessages()) {
27+
// System.out.println(l);
28+
// }
29+
assertLogsContain(
30+
logger.loggedMessages(),
31+
"set tick period milliseconds: 100",
32+
"called 1 for contextID=1",
33+
"called 2 for contextID=1",
34+
"response header for the dispatched call: Content-Type: text/plain;charset=UTF-8",
35+
"response header for the dispatched call: echo-accept: */*",
36+
"response header for the dispatched call: echo-content-length: 0",
37+
"response header for the dispatched call: echo-Host: some_authority");
38+
plugin.close(); // so that the ticks don't keep running in the background.
39+
}
40+
}

proxy-wasm-jaxrs-quarkus-example/src/test/java/io/roastedroot/proxywasm/jaxrs/example/ForeignCallOnTickTest.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package io.roastedroot.proxywasm.jaxrs.example;
22

3+
import static io.roastedroot.proxywasm.jaxrs.example.Helpers.assertLogsContain;
34
import static org.junit.jupiter.api.Assertions.assertNotNull;
45

56
import io.quarkus.test.junit.QuarkusTest;
67
import io.roastedroot.proxywasm.StartException;
78
import io.roastedroot.proxywasm.jaxrs.WasmPlugin;
89
import io.roastedroot.proxywasm.jaxrs.WasmPluginFeature;
910
import jakarta.inject.Inject;
10-
import java.util.ArrayList;
11-
import org.junit.jupiter.api.Assertions;
1211
import org.junit.jupiter.api.Test;
1312

1413
@QuarkusTest
@@ -30,12 +29,4 @@ public void testRequest() throws InterruptedException, StartException {
3029
1, "68656c6c6f20776f726c6421"));
3130
plugin.close(); // so that the ticks don't keep running in the background.
3231
}
33-
34-
public synchronized void assertLogsContain(
35-
ArrayList<String> loggedMessages, String... message) {
36-
for (String m : message) {
37-
Assertions.assertTrue(
38-
loggedMessages.contains(m), "logged messages does not contain: " + m);
39-
}
40-
}
4132
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.roastedroot.proxywasm.jaxrs.example;
2+
3+
import java.util.ArrayList;
4+
import org.junit.jupiter.api.Assertions;
5+
6+
public class Helpers {
7+
private Helpers() {}
8+
9+
public static void assertLogsContain(ArrayList<String> loggedMessages, String... message) {
10+
for (String m : message) {
11+
Assertions.assertTrue(
12+
loggedMessages.contains(m), "logged messages does not contain: " + m);
13+
}
14+
}
15+
}

proxy-wasm-jaxrs/src/main/java/io/roastedroot/proxywasm/jaxrs/PluginHandler.java

Lines changed: 107 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package io.roastedroot.proxywasm.jaxrs;
22

33
import static io.roastedroot.proxywasm.Helpers.bytes;
4+
import static io.roastedroot.proxywasm.WellKnownHeaders.AUTHORITY;
5+
import static io.roastedroot.proxywasm.WellKnownHeaders.METHOD;
6+
import static io.roastedroot.proxywasm.WellKnownHeaders.PATH;
7+
import static io.roastedroot.proxywasm.WellKnownHeaders.SCHEME;
48
import static io.roastedroot.proxywasm.WellKnownProperties.PLUGIN_NAME;
59
import static io.roastedroot.proxywasm.WellKnownProperties.PLUGIN_VM_ID;
610

11+
import io.roastedroot.proxywasm.ArrayProxyMap;
712
import io.roastedroot.proxywasm.ChainedHandler;
813
import io.roastedroot.proxywasm.ForeignFunction;
914
import io.roastedroot.proxywasm.Handler;
@@ -12,6 +17,8 @@
1217
import io.roastedroot.proxywasm.ProxyMap;
1318
import io.roastedroot.proxywasm.WasmException;
1419
import io.roastedroot.proxywasm.WasmResult;
20+
import jakarta.ws.rs.core.UriBuilder;
21+
import java.net.URI;
1522
import java.util.HashMap;
1623
import java.util.List;
1724
import java.util.Objects;
@@ -46,6 +53,10 @@ public void close() {
4653
cancelTick.run();
4754
cancelTick = null;
4855
}
56+
for (var cancelHttpCall : httpCalls.values()) {
57+
cancelHttpCall.run();
58+
}
59+
httpCalls.clear();
4960
}
5061

5162
// //////////////////////////////////////////////////////////////////////
@@ -200,61 +211,105 @@ public void setPlugin(WasmPlugin plugin) {
200211
// HTTP calls
201212
// //////////////////////////////////////////////////////////////////////
202213

203-
public static class HttpCall {
204-
public enum Type {
205-
REGULAR,
206-
DISPATCH
207-
}
208-
209-
public final int id;
210-
public final Type callType;
211-
public final String uri;
212-
public final Object headers;
213-
public final byte[] body;
214-
public final ProxyMap trailers;
215-
public final int timeoutMilliseconds;
216-
217-
public HttpCall(
218-
int id,
219-
Type callType,
220-
String uri,
221-
ProxyMap headers,
222-
byte[] body,
223-
ProxyMap trailers,
224-
int timeoutMilliseconds) {
225-
this.id = id;
226-
this.callType = callType;
227-
this.uri = uri;
228-
this.headers = headers;
229-
this.body = body;
230-
this.trailers = trailers;
231-
this.timeoutMilliseconds = timeoutMilliseconds;
232-
}
233-
}
234-
235214
private final AtomicInteger lastCallId = new AtomicInteger(0);
236-
private final HashMap<Integer, HttpCall> httpCalls = new HashMap();
215+
private final HashMap<Integer, Runnable> httpCalls = new HashMap<>();
237216

238-
public HashMap<Integer, HttpCall> getHttpCalls() {
239-
return httpCalls;
240-
}
217+
HashMap<String, String> upstreams = new HashMap<>();
218+
boolean strictUpstreams;
241219

242220
@Override
243221
public int httpCall(
244-
String uri, ProxyMap headers, byte[] body, ProxyMap trailers, int timeoutMilliseconds)
222+
String upstreamName,
223+
ProxyMap headers,
224+
byte[] body,
225+
ProxyMap trailers,
226+
int timeoutMilliseconds)
245227
throws WasmException {
246-
var id = lastCallId.incrementAndGet();
247-
HttpCall value =
248-
new HttpCall(
249-
id,
250-
HttpCall.Type.REGULAR,
251-
uri,
252-
headers,
253-
body,
254-
trailers,
255-
timeoutMilliseconds);
256-
httpCalls.put(id, value);
257-
return id;
228+
229+
var method = headers.get(METHOD);
230+
if (method == null) {
231+
throw new WasmException(WasmResult.BAD_ARGUMENT);
232+
}
233+
234+
var scheme = headers.get(SCHEME);
235+
if (scheme == null) {
236+
scheme = "http";
237+
}
238+
var authority = headers.get(AUTHORITY);
239+
if (authority == null) {
240+
throw new WasmException(WasmResult.BAD_ARGUMENT);
241+
}
242+
headers.put("Host", authority);
243+
244+
var connectHostPort = upstreams.get(upstreamName);
245+
if (connectHostPort == null && strictUpstreams) {
246+
throw new WasmException(WasmResult.BAD_ARGUMENT);
247+
}
248+
if (connectHostPort == null) {
249+
connectHostPort = authority;
250+
}
251+
252+
var connectUri = UriBuilder.newInstance().scheme(scheme).host(connectHostPort).build();
253+
var connectHost = connectUri.getHost();
254+
var connectPort = connectUri.getPort();
255+
if (connectPort == -1) {
256+
connectPort = "https".equals(scheme) ? 443 : 80;
257+
}
258+
259+
var path = headers.get(PATH);
260+
if (path == null) {
261+
throw new WasmException(WasmResult.BAD_ARGUMENT);
262+
}
263+
if (!path.isEmpty() && !path.startsWith("/")) {
264+
path = "/" + path;
265+
}
266+
267+
var uri =
268+
URI.create(
269+
UriBuilder.newInstance()
270+
.scheme(scheme)
271+
.host(authority)
272+
.port(connectPort)
273+
.build()
274+
.toString()
275+
+ path);
276+
277+
// Remove all the pseudo headers
278+
for (var r : new ArrayProxyMap(headers).entries()) {
279+
if (r.getKey().startsWith(":")) {
280+
headers.remove(r.getKey());
281+
}
282+
}
283+
284+
try {
285+
var id = lastCallId.incrementAndGet();
286+
var future =
287+
this.plugin.httpServer.scheduleHttpCall(
288+
method,
289+
connectHost,
290+
connectPort,
291+
uri,
292+
headers,
293+
body,
294+
trailers,
295+
timeoutMilliseconds,
296+
(resp) -> {
297+
this.plugin.lock();
298+
try {
299+
if (httpCalls.remove(id) == null) {
300+
return; // the call could have already been cancelled
301+
}
302+
this.plugin.wasm.sendHttpCallResponse(
303+
id, resp.headers, new ArrayProxyMap(), resp.body);
304+
} finally {
305+
this.plugin.unlock();
306+
}
307+
});
308+
httpCalls.put(id, future);
309+
return id;
310+
} catch (InterruptedException e) {
311+
throw new WasmException(WasmResult.INTERNAL_FAILURE);
312+
}
258313
}
259314

260315
@Override
@@ -265,18 +320,7 @@ public int dispatchHttpCall(
265320
ProxyMap trailers,
266321
int timeoutMilliseconds)
267322
throws WasmException {
268-
var id = lastCallId.incrementAndGet();
269-
HttpCall value =
270-
new HttpCall(
271-
id,
272-
HttpCall.Type.DISPATCH,
273-
upstreamName,
274-
headers,
275-
body,
276-
trailers,
277-
timeoutMilliseconds);
278-
httpCalls.put(id, value);
279-
return id;
323+
return httpCall(upstreamName, headers, body, trailers, timeoutMilliseconds);
280324
}
281325

282326
// //////////////////////////////////////////////////////////////////////
@@ -298,8 +342,8 @@ public Metric(int id, MetricType type, String name) {
298342
}
299343

300344
private final AtomicInteger lastMetricId = new AtomicInteger(0);
301-
private HashMap<Integer, Metric> metrics = new HashMap();
302-
private HashMap<String, Metric> metricsByName = new HashMap();
345+
private HashMap<Integer, Metric> metrics = new HashMap<>();
346+
private HashMap<String, Metric> metricsByName = new HashMap<>();
303347

304348
@Override
305349
public int defineMetric(MetricType type, String name) throws WasmException {

0 commit comments

Comments
 (0)