Skip to content

Commit a2016ca

Browse files
committed
Added multiple_dispatches example and test.
Signed-off-by: Hiram Chirino <hiram@hiramchirino.com>
1 parent edff631 commit a2016ca

File tree

12 files changed

+332
-40
lines changed

12 files changed

+332
-40
lines changed

src/main/java/io/roastedroot/proxywasm/impl/Imports.java

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.dylibso.chicory.experimental.hostmodule.annotations.WasmExport;
77
import com.dylibso.chicory.runtime.Instance;
88
import com.dylibso.chicory.runtime.WasmRuntimeException;
9+
import io.roastedroot.proxywasm.v1.Action;
910
import io.roastedroot.proxywasm.v1.BufferType;
1011
import io.roastedroot.proxywasm.v1.Handler;
1112
import io.roastedroot.proxywasm.v1.LogLevel;
@@ -784,24 +785,49 @@ int proxyGetCurrentTimeNanoseconds(int returnTime) {
784785
}
785786
}
786787

788+
/**
789+
* Resumes processing of paused stream_type.
790+
*
791+
* see: https://github.com/proxy-wasm/spec/tree/main/abi-versions/v0.2.1#proxy_continue_stream
792+
*/
787793
@WasmExport
788794
int proxyContinueStream(int arg) {
789795
var streamType = StreamType.fromInt(arg);
790796
if (streamType == null) {
791797
return WasmResult.BAD_ARGUMENT.getValue();
792798
}
793-
switch (streamType) {
794-
case REQUEST:
795-
return handler.continueRequest().getValue();
796-
case RESPONSE:
797-
return handler.continueResponse().getValue();
798-
case DOWNSTREAM:
799-
return handler.continueDownstream().getValue();
800-
case UPSTREAM:
801-
return handler.continueUpstream().getValue();
802-
}
803-
// should never reach here
804-
return WasmResult.INTERNAL_FAILURE.getValue();
799+
WasmResult result = handler.setAction(streamType, Action.CONTINUE);
800+
return result.getValue();
801+
}
802+
803+
/**
804+
* Resumes processing of paused HTTP request.
805+
*
806+
* see: https://github.com/proxy-wasm/spec/blob/main/abi-versions/v0.1.0/README.md#proxy_continue_request
807+
*/
808+
@WasmExport
809+
void proxyContinueRequest() {
810+
handler.setAction(StreamType.REQUEST, Action.CONTINUE);
811+
}
812+
813+
/**
814+
* Resumes processing of paused HTTP response.
815+
*
816+
* see: https://github.com/proxy-wasm/spec/blob/main/abi-versions/v0.1.0/README.md#proxy_continue_response
817+
*/
818+
@WasmExport
819+
void proxyContinueResponse() {
820+
handler.setAction(StreamType.RESPONSE, Action.CONTINUE);
821+
}
822+
823+
/**
824+
* Clears cached HTTP route.
825+
*
826+
* see: https://github.com/proxy-wasm/spec/blob/main/abi-versions/v0.1.0/README.md#proxy_clear_route_cache
827+
*/
828+
@WasmExport
829+
void proxyClearRouteCache() {
830+
handler.clearRouteCache();
805831
}
806832

807833
@WasmExport

src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -236,23 +236,13 @@ public WasmResult setHttpRequestBody(byte[] body) {
236236
}
237237

238238
@Override
239-
public WasmResult continueRequest() {
240-
return next().continueRequest();
239+
public WasmResult setAction(StreamType streamType, Action action) {
240+
return next().setAction(streamType, action);
241241
}
242242

243243
@Override
244-
public WasmResult continueResponse() {
245-
return next().continueResponse();
246-
}
247-
248-
@Override
249-
public WasmResult continueDownstream() {
250-
return next().continueDownstream();
251-
}
252-
253-
@Override
254-
public WasmResult continueUpstream() {
255-
return next().continueUpstream();
244+
public WasmResult clearRouteCache() {
245+
return next().clearRouteCache();
256246
}
257247

258248
@Override

src/main/java/io/roastedroot/proxywasm/v1/Handler.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -371,19 +371,11 @@ default WasmResult setGrpcReceiveTrailerMetaData(Map<String, String> metadata) {
371371
return WasmResult.UNIMPLEMENTED;
372372
}
373373

374-
default WasmResult continueRequest() {
374+
default WasmResult setAction(StreamType streamType, Action action) {
375375
return WasmResult.UNIMPLEMENTED;
376376
}
377377

378-
default WasmResult continueResponse() {
379-
return WasmResult.UNIMPLEMENTED;
380-
}
381-
382-
default WasmResult continueDownstream() {
383-
return WasmResult.UNIMPLEMENTED;
384-
}
385-
386-
default WasmResult continueUpstream() {
378+
default WasmResult clearRouteCache() {
387379
return WasmResult.UNIMPLEMENTED;
388380
}
389381

src/main/java/io/roastedroot/proxywasm/v1/HttpContext.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,18 @@ public Action callOnRequestHeaders(boolean endOfStream) {
2020
var headers = handler.getHttpRequestHeaders();
2121
int result =
2222
proxyWasm.exports().proxyOnRequestHeaders(id, len(headers), endOfStream ? 1 : 0);
23-
return Action.fromInt(result);
23+
Action action = Action.fromInt(result);
24+
handler.setAction(StreamType.REQUEST, action);
25+
return action;
2426
}
2527

2628
public Action callOnResponseHeaders(boolean endOfStream) {
2729
var headers = handler.getHttpResponseHeaders();
2830
int result =
2931
proxyWasm.exports().proxyOnResponseHeaders(id, len(headers), endOfStream ? 1 : 0);
30-
return Action.fromInt(result);
32+
Action action = Action.fromInt(result);
33+
handler.setAction(StreamType.RESPONSE, action);
34+
return action;
3135
}
3236

3337
public Action callOnRequestBody(boolean endOfStream) {
@@ -36,7 +40,9 @@ public Action callOnRequestBody(boolean endOfStream) {
3640
proxyWasm
3741
.exports()
3842
.proxyOnRequestBody(id, Helpers.len(requestBody), endOfStream ? 1 : 0);
39-
return Action.fromInt(result);
43+
Action action = Action.fromInt(result);
44+
handler.setAction(StreamType.REQUEST, action);
45+
return action;
4046
}
4147

4248
public Action callOnResponseBody(boolean endOfStream) {
@@ -45,6 +51,8 @@ public Action callOnResponseBody(boolean endOfStream) {
4551
proxyWasm
4652
.exports()
4753
.proxyOnResponseBody(id, Helpers.len(responseBody), endOfStream ? 1 : 0);
48-
return Action.fromInt(result);
54+
Action action = Action.fromInt(result);
55+
handler.setAction(StreamType.RESPONSE, action);
56+
return action;
4957
}
5058
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Attribution
2+
3+
This example originally came from:
4+
https://github.com/proxy-wasm/proxy-wasm-go-sdk/blob/ab4161dcf9246a828008b539a82a1556cf0f2e24/examples/multiple_dispatches
5+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/multiple_dispatches
2+
3+
go 1.24
4+
5+
require github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924 h1:wTcK6gcyTKJMeDka69AMjZYvisdI8CBXzTEfZ+2pOxI=
6+
github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924/go.mod h1:9mBRvh8I6Td6sg3CwEY+zGFE4DKaIoieCaca1kQnDBE=
7+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
8+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
9+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright 2020-2024 Tetrate
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"strconv"
19+
20+
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm"
21+
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types"
22+
)
23+
24+
const clusterName = "httpbin"
25+
26+
func main() {}
27+
func init() {
28+
proxywasm.SetVMContext(&vmContext{})
29+
}
30+
31+
// vmContext implements types.VMContext.
32+
type vmContext struct {
33+
// Embed the default VM context here,
34+
// so that we don't need to reimplement all the methods.
35+
types.DefaultVMContext
36+
}
37+
38+
// NewPluginContext implements types.VMContext.
39+
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
40+
return &pluginContext{}
41+
}
42+
43+
// pluginContext implements types.PluginContext.
44+
type pluginContext struct {
45+
// Embed the default plugin context here,
46+
// so that we don't need to reimplement all the methods.
47+
types.DefaultPluginContext
48+
}
49+
50+
// NewHttpContext implements types.PluginContext.
51+
func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
52+
return &httpContext{contextID: contextID}
53+
}
54+
55+
// httpContext implements types.HttpContext.
56+
type httpContext struct {
57+
// Embed the default http context here,
58+
// so that we don't need to reimplement all the methods.
59+
types.DefaultHttpContext
60+
// contextID is the unique identifier assigned to each httpContext.
61+
contextID uint32
62+
// pendingDispatchedRequest is the number of pending dispatched requests.
63+
pendingDispatchedRequest int
64+
}
65+
66+
const totalDispatchNum = 10
67+
68+
// OnHttpResponseHeaders implements types.HttpContext.
69+
func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
70+
// On each request response, we dispatch the http calls `totalDispatchNum` times.
71+
// Note: DispatchHttpCall is asynchronously processed, so each loop is non-blocking.
72+
for i := 0; i < totalDispatchNum; i++ {
73+
if _, err := proxywasm.DispatchHttpCall(clusterName, [][2]string{
74+
{":path", "/"},
75+
{":method", "GET"},
76+
{":authority", ""}},
77+
nil, nil, 50000, ctx.dispatchCallback); err != nil {
78+
panic(err)
79+
}
80+
// Now we have made a dispatched request, so we record it.
81+
ctx.pendingDispatchedRequest++
82+
}
83+
return types.ActionPause
84+
}
85+
86+
// dispatchCallback is the callback function called in response to the response arrival from the dispatched request.
87+
func (ctx *httpContext) dispatchCallback(numHeaders, bodySize, numTrailers int) {
88+
// Decrement the pending request counter.
89+
ctx.pendingDispatchedRequest--
90+
if ctx.pendingDispatchedRequest == 0 {
91+
// This case, all the dispatched request was processed.
92+
// Adds a response header to the original response.
93+
_ = proxywasm.AddHttpResponseHeader("total-dispatched", strconv.Itoa(totalDispatchNum))
94+
// And then contniue the original reponse.
95+
_ = proxywasm.ResumeHttpResponse()
96+
proxywasm.LogInfof("response resumed after processed %d dispatched request", totalDispatchNum)
97+
} else {
98+
proxywasm.LogInfof("pending dispatched requests: %d", ctx.pendingDispatchedRequest)
99+
}
100+
}
2.39 MB
Binary file not shown.

src/test/java/io/roastedroot/proxywasm/JsonValidationTest.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,83 @@
1515
import org.junit.jupiter.api.Nested;
1616
import org.junit.jupiter.api.Test;
1717

18+
// This file what the go code was:
19+
//
20+
// func TestOnHTTPRequestHeaders(t *testing.T) {
21+
// type testCase struct {
22+
// contentType string
23+
// expectedAction types.Action
24+
// }
25+
//
26+
// vmTest(t, func(t *testing.T, vm types.VMContext) {
27+
// for name, tCase := range map[string]testCase{
28+
// "fails due to unsupported content type": {
29+
// contentType: "text/html",
30+
// expectedAction: types.ActionPause,
31+
// },
32+
// "success for JSON": {
33+
// contentType: "application/json",
34+
// expectedAction: types.ActionContinue,
35+
// },
36+
// } {
37+
// t.Run(name, func(t *testing.T) {
38+
// opt := proxytest.NewEmulatorOption().WithVMContext(vm)
39+
// host, reset := proxytest.NewHostEmulator(opt)
40+
// defer reset()
41+
//
42+
// require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin())
43+
//
44+
// id := host.InitializeHttpContext()
45+
//
46+
// hs := [][2]string{{"content-type", tCase.contentType}}
47+
//
48+
// action := host.CallOnRequestHeaders(id, hs, false)
49+
// assert.Equal(t, tCase.expectedAction, action)
50+
// })
51+
// }
52+
// })
53+
// }
54+
//
55+
// func TestOnHTTPRequestBody(t *testing.T) {
56+
// type testCase struct {
57+
// body string
58+
// expectedAction types.Action
59+
// }
60+
//
61+
// vmTest(t, func(t *testing.T, vm types.VMContext) {
62+
//
63+
// for name, tCase := range map[string]testCase{
64+
// "pauses due to invalid payload": {
65+
// body: "invalid_payload",
66+
// expectedAction: types.ActionPause,
67+
// },
68+
// "pauses due to unknown keys": {
69+
// body: `{"unknown_key":"unknown_value"}`,
70+
// expectedAction: types.ActionPause,
71+
// },
72+
// "success": {
73+
// body: "{\"my_key\":\"my_value\"}",
74+
// expectedAction: types.ActionContinue,
75+
// },
76+
// } {
77+
// t.Run(name, func(t *testing.T) {
78+
// opt := proxytest.
79+
// NewEmulatorOption().
80+
// WithPluginConfiguration([]byte(`{"requiredKeys": ["my_key"]}`)).
81+
// WithVMContext(vm)
82+
// host, reset := proxytest.NewHostEmulator(opt)
83+
// defer reset()
84+
//
85+
// require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin())
86+
//
87+
// id := host.InitializeHttpContext()
88+
//
89+
// action := host.CallOnRequestBody(id, []byte(tCase.body), true)
90+
// assert.Equal(t, tCase.expectedAction, action)
91+
// })
92+
// }
93+
// })
94+
// }
1895
/**
1996
* Java port of https://github.com/proxy-wasm/proxy-wasm-go-sdk/blob/ab4161dcf9246a828008b539a82a1556cf0f2e24/examples/json_validation/main_test.go
2097
*/

0 commit comments

Comments
 (0)