Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/main/java/io/roastedroot/proxywasm/impl/Imports.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.roastedroot.proxywasm.impl;

import static io.roastedroot.proxywasm.v1.Helpers.string;

import com.dylibso.chicory.experimental.hostmodule.annotations.HostModule;
import com.dylibso.chicory.experimental.hostmodule.annotations.WasmExport;
import com.dylibso.chicory.runtime.Instance;
Expand Down Expand Up @@ -800,4 +802,64 @@ int proxyContinueStream(int arg) {
// should never reach here
return WasmResult.INTERNAL_FAILURE.getValue();
}

@WasmExport
int proxyHttpCall(
int uriData,
int uriSize,
int headersData,
int headersSize,
int bodyData,
int bodySize,
int trailersData,
int trailersSize,
int timeout,
int returnCalloutID) {

try {
var uri = string(readMemory(uriData, uriSize));
var headers = decodeMap(headersData, headersSize);
var body = readMemory(bodyData, bodySize);
var trailers = decodeMap(trailersData, trailersSize);

int calloutId = handler.httpCall(uri, headers, body, trailers, timeout);

putUint32(returnCalloutID, calloutId);
return WasmResult.OK.getValue();

} catch (WasmException e) {
return e.result().getValue();
}
}

@WasmExport
int proxyDispatchHttpCall(
int upstreamNameData,
int upstreamNameSize,
int headersData,
int headersSize,
int bodyData,
int bodySize,
int trailersData,
int trailersSize,
int timeoutMilliseconds,
int returnCalloutID) {

try {
var upstreamName = string(readMemory(upstreamNameData, upstreamNameSize));
var headers = decodeMap(headersData, headersSize);
var body = readMemory(bodyData, bodySize);
var trailers = decodeMap(trailersData, trailersSize);

int calloutId =
handler.dispatchHttpCall(
upstreamName, headers, body, trailers, timeoutMilliseconds);

putUint32(returnCalloutID, calloutId);
return WasmResult.OK.getValue();

} catch (WasmException e) {
return e.result().getValue();
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.roastedroot.proxywasm.v1;

import java.util.HashMap;
import java.util.Map;

/**
Expand Down Expand Up @@ -253,4 +254,15 @@ public WasmResult continueDownstream() {
public WasmResult continueUpstream() {
return next().continueUpstream();
}

@Override
public int httpCall(
String uri,
HashMap<String, String> headers,
byte[] body,
HashMap<String, String> trailers,
int timeout)
throws WasmException {
return next().httpCall(uri, headers, body, trailers, timeout);
}
}
2 changes: 0 additions & 2 deletions src/main/java/io/roastedroot/proxywasm/v1/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ public void close() {
}

// plugin is indicating it wants to finish closing
// TODO: need a unit test for this. We likely can't implement the test until we provide tick
// callbacks to the plugin.
WasmResult done() {
if (!closeStarted) {
// spec says: return NOT_FOUND when active context was not pending finalization.
Expand Down
25 changes: 23 additions & 2 deletions src/main/java/io/roastedroot/proxywasm/v1/Handler.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.roastedroot.proxywasm.v1;

import java.util.HashMap;
import java.util.Map;

public interface Handler {
Expand Down Expand Up @@ -160,11 +161,11 @@ default WasmResult done() {
/**
* Sets a low-resolution timer period (tick_period).
*
* When set, the host will call proxy_on_tick every tick_period milliseconds. Setting tick_period to 0 disables the timer.
* When set, the host will call proxy_on_tick every tickPeriodMilliseconds milliseconds. Setting tickPeriodMilliseconds to 0 disables the timer.
*
* @return The current time in nanoseconds
*/
default WasmResult setTickPeriodMilliseconds(int tick_period) {
default WasmResult setTickPeriodMilliseconds(int tickPeriodMilliseconds) {
return WasmResult.UNIMPLEMENTED;
}

Expand Down Expand Up @@ -385,4 +386,24 @@ default WasmResult continueDownstream() {
default WasmResult continueUpstream() {
return WasmResult.UNIMPLEMENTED;
}

default int httpCall(
String uri,
HashMap<String, String> headers,
byte[] body,
HashMap<String, String> trailers,
int timeoutMilliseconds)
throws WasmException {
throw new WasmException(WasmResult.UNIMPLEMENTED);
}

default int dispatchHttpCall(
String upstreamName,
HashMap<String, String> headers,
byte[] body,
HashMap<String, String> trailers,
int timeoutMilliseconds)
throws WasmException {
throw new WasmException(WasmResult.UNIMPLEMENTED);
}
}
37 changes: 37 additions & 0 deletions src/main/java/io/roastedroot/proxywasm/v1/ProxyWasm.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public final class ProxyWasm implements Closeable {
private Context activeContext;

private HashMap<Integer, Context> contexts = new HashMap<>();
private Map<String, String> httpCallResponseHeaders;
private Map<String, String> httpCallResponseTrailers;
private byte[] httpCallResponseBody;

private ProxyWasm(Builder other) throws StartException {
this.vmConfig = other.vmConfig;
Expand Down Expand Up @@ -151,6 +154,21 @@ public WasmResult setEffectiveContextID(int contextID) {
public WasmResult done() {
return activeContext.done();
}

@Override
public Map<String, String> getHttpCallResponseHeaders() {
return httpCallResponseHeaders;
}

@Override
public Map<String, String> getHttpCallResponseTrailers() {
return httpCallResponseTrailers;
}

@Override
public byte[] getHttpCallResponseBody() {
return httpCallResponseBody;
}
};
}

Expand Down Expand Up @@ -229,6 +247,25 @@ public static ProxyWasm.Builder builder() {
return new ProxyWasm.Builder();
}

public void callOnHttpCallResponse(
int calloutID, Map<String, String> headers, Map<String, String> trailers, byte[] body) {

this.httpCallResponseHeaders = headers;
this.httpCallResponseTrailers = trailers;
this.httpCallResponseBody = body;

this.exports.proxyOnHttpCallResponse(
pluginContext.id(), calloutID, len(headers), len(body), len(trailers));

this.httpCallResponseHeaders = null;
this.httpCallResponseTrailers = null;
this.httpCallResponseBody = null;
}

public int contextId() {
return pluginContext.id();
}

public static class Builder implements Cloneable {

private Exports exports;
Expand Down
5 changes: 5 additions & 0 deletions src/test/go-examples/dispatch_call_on_tick/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Attribution

This example originally came from:
https://github.com/proxy-wasm/proxy-wasm-go-sdk/tree/main/examples/dispatch_call_on_tick

5 changes: 5 additions & 0 deletions src/test/go-examples/dispatch_call_on_tick/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/dispatch_call_on_tick

go 1.24

require github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924
10 changes: 10 additions & 0 deletions src/test/go-examples/dispatch_call_on_tick/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924 h1:wTcK6gcyTKJMeDka69AMjZYvisdI8CBXzTEfZ+2pOxI=
github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924/go.mod h1:9mBRvh8I6Td6sg3CwEY+zGFE4DKaIoieCaca1kQnDBE=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
97 changes: 97 additions & 0 deletions src/test/go-examples/dispatch_call_on_tick/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2020-2024 Tetrate
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"crypto/rand"

"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm"
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types"
)

const tickMilliseconds uint32 = 100

func main() {}
func init() {
proxywasm.SetVMContext(&vmContext{})
}

// vmContext implements types.VMContext.
type vmContext struct {
// Embed the default VM context here,
// so that we don't need to reimplement all the methods.
types.DefaultVMContext
}

// NewPluginContext implements types.VMContext.
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
return &pluginContext{contextID: contextID}
}

// pluginContext implements types.PluginContext.
type pluginContext struct {
// Embed the default plugin context here,
// so that we don't need to reimplement all the methods.
types.DefaultPluginContext
contextID uint32
callBack func(numHeaders, bodySize, numTrailers int)
cnt int
}

// OnPluginStart implements types.PluginContext.
func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
if err := proxywasm.SetTickPeriodMilliSeconds(tickMilliseconds); err != nil {
proxywasm.LogCriticalf("failed to set tick period: %v", err)
return types.OnPluginStartStatusFailed
}
proxywasm.LogInfof("set tick period milliseconds: %d", tickMilliseconds)
ctx.callBack = func(numHeaders, bodySize, numTrailers int) {
ctx.cnt++
proxywasm.LogInfof("called %d for contextID=%d", ctx.cnt, ctx.contextID)
headers, err := proxywasm.GetHttpCallResponseHeaders()
if err != nil && err != types.ErrorStatusNotFound {
panic(err)
}
for _, h := range headers {
proxywasm.LogInfof("response header for the dispatched call: %s: %s", h[0], h[1])
}
headers, err = proxywasm.GetHttpCallResponseTrailers()
if err != nil && err != types.ErrorStatusNotFound {
panic(err)
}
for _, h := range headers {
proxywasm.LogInfof("response trailer for the dispatched call: %s: %s", h[0], h[1])
}
}
return types.OnPluginStartStatusOK
}

// OnTick implements types.PluginContext.
func (ctx *pluginContext) OnTick() {
headers := [][2]string{
{":method", "GET"}, {":authority", "some_authority"}, {"accept", "*/*"},
}
// Pick random value to select the request path.
buf := make([]byte, 1)
_, _ = rand.Read(buf)
if buf[0]%2 == 0 {
headers = append(headers, [2]string{":path", "/ok"})
} else {
headers = append(headers, [2]string{":path", "/fail"})
}
if _, err := proxywasm.DispatchHttpCall("web_service", headers, nil, nil, 5000, ctx.callBack); err != nil {
proxywasm.LogCriticalf("dispatch httpcall failed: %v", err)
}
}
Binary file not shown.
2 changes: 1 addition & 1 deletion src/test/go-examples/http_body_chunk/go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/http_body
module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/http_body_chunk

go 1.24

Expand Down
2 changes: 1 addition & 1 deletion src/test/go-examples/http_headers/go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/http_body
module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/http_headers

go 1.24

Expand Down
2 changes: 1 addition & 1 deletion src/test/go-examples/properties/go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/http_body
module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/properties

go 1.24

Expand Down
47 changes: 47 additions & 0 deletions src/test/java/io/roastedroot/proxywasm/DispatchCallOnTickTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.roastedroot.proxywasm;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.dylibso.chicory.wasm.Parser;
import io.roastedroot.proxywasm.v1.ProxyWasm;
import io.roastedroot.proxywasm.v1.StartException;
import java.nio.file.Path;
import java.util.Map;
import org.junit.jupiter.api.Test;

/**
*/
public class DispatchCallOnTickTest {

static int tickMilliseconds = 100;

@Test
public void testOnTick() throws StartException {

var handler = new MockHandler();
var module =
Parser.parse(Path.of("./src/test/go-examples/dispatch_call_on_tick/main.wasm"));
ProxyWasm.Builder builder = ProxyWasm.builder().withPluginHandler(handler);
try (var host = builder.build(module)) {
assertEquals(tickMilliseconds, handler.getTickPeriodMilliseconds());

for (int i = 1; i <= 10; i++) {
host.tick(); // call OnTick
}

assertEquals(10, handler.getHttpCalls().size());
handler.getHttpCalls().entrySet().stream()
.forEach(
entry -> {
host.callOnHttpCallResponse(
entry.getKey(), Map.of(), Map.of(), new byte[0]);
});

// Check Envoy logs.
for (int i = 1; i <= 10; i++) {
handler.assertLogsContain(
String.format("called %d for contextID=%d", i, host.contextId()));
}
}
}
}
Loading