Skip to content

Commit 87055a2

Browse files
committed
Implement Shared Key-Value Store, with example and test.
Signed-off-by: Hiram Chirino <hiram@hiramchirino.com>
1 parent 9ed6862 commit 87055a2

10 files changed

Lines changed: 274 additions & 6 deletions

File tree

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,4 +962,44 @@ int proxyGetMetric(int metricId, int returnValuePtr) {
962962
return e.result().getValue();
963963
}
964964
}
965+
966+
@WasmExport
967+
int proxyGetSharedData(
968+
int keyDataPtr, int keySize, int returnValueData, int returnValueSize, int returnCas) {
969+
try {
970+
// Get key from memory
971+
String key = string(readMemory(keyDataPtr, keySize));
972+
973+
// Get shared data value using handler
974+
Handler.SharedData value = handler.getSharedData(key);
975+
if (value == null) {
976+
return WasmResult.NOT_FOUND.getValue();
977+
}
978+
979+
copyIntoInstance(value.data, returnValueData, returnValueSize);
980+
putUint32(returnCas, value.cas);
981+
return WasmResult.OK.getValue();
982+
983+
} catch (WasmException e) {
984+
return e.result().getValue();
985+
}
986+
}
987+
988+
@WasmExport
989+
int proxySetSharedData(int keyDataPtr, int keySize, int valueDataPtr, int valueSize, int cas) {
990+
try {
991+
// Get key from memory
992+
String key = string(readMemory(keyDataPtr, keySize));
993+
994+
// Get value from memory
995+
byte[] value = readMemory(valueDataPtr, valueSize);
996+
997+
// Set shared data value using handler
998+
WasmResult result = handler.setSharedData(key, value, cas);
999+
return result.getValue();
1000+
1001+
} catch (WasmException e) {
1002+
return e.result().getValue();
1003+
}
1004+
}
9651005
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,14 @@ public WasmResult incrementMetric(int metricId, long value) {
296296
public long getMetric(int metricId) throws WasmException {
297297
return next().getMetric(metricId);
298298
}
299+
300+
@Override
301+
public SharedData getSharedData(String key) throws WasmException {
302+
return next().getSharedData(key);
303+
}
304+
305+
@Override
306+
public WasmResult setSharedData(String key, byte[] value, int cas) {
307+
return next().setSharedData(key, value, cas);
308+
}
299309
}

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

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,11 @@ default int getCurrentTimeNanoseconds() throws WasmException {
183183
/**
184184
* Send an HTTP response.
185185
*
186-
* @param responseCode The HTTP response code
186+
* @param responseCode The HTTP response code
187187
* @param responseCodeDetails The response code details
188-
* @param responseBody The response body
189-
* @param additionalHeaders Additional headers to include
190-
* @param grpcStatus The gRPC status code (-1 for non-gRPC responses)
188+
* @param responseBody The response body
189+
* @param additionalHeaders Additional headers to include
190+
* @param grpcStatus The gRPC status code (-1 for non-gRPC responses)
191191
* @return The result of sending the response
192192
*/
193193
default WasmResult sendHttpResponse(
@@ -273,7 +273,7 @@ default WasmResult setFuncCallData(byte[] data) {
273273
* Set a custom buffer.
274274
*
275275
* @param bufferType The buffer type
276-
* @param buffer The custom buffer as a byte[]
276+
* @param buffer The custom buffer as a byte[]
277277
* @return WasmResult indicating success or failure
278278
*/
279279
default WasmResult setCustomBuffer(int bufferType, byte[] buffer) {
@@ -284,7 +284,7 @@ default WasmResult setCustomBuffer(int bufferType, byte[] buffer) {
284284
* Set a custom header map.
285285
*
286286
* @param mapType The type of map to set
287-
* @param map The header map to set
287+
* @param map The header map to set
288288
* @return WasmResult indicating success or failure
289289
*/
290290
default WasmResult setCustomHeaders(int mapType, Map<String, String> map) {
@@ -422,4 +422,22 @@ default WasmResult incrementMetric(int metricId, long value) {
422422
default long getMetric(int metricId) throws WasmException {
423423
throw new WasmException(WasmResult.UNIMPLEMENTED);
424424
}
425+
426+
class SharedData {
427+
public byte[] data;
428+
public int cas;
429+
430+
public SharedData(byte[] data, int cas) {
431+
this.data = data;
432+
this.cas = cas;
433+
}
434+
}
435+
436+
default SharedData getSharedData(String key) throws WasmException {
437+
throw new WasmException(WasmResult.UNIMPLEMENTED);
438+
}
439+
440+
default WasmResult setSharedData(String key, byte[] value, int cas) {
441+
return WasmResult.UNIMPLEMENTED;
442+
}
425443
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## Attribution
2+
3+
This example originally came from:
4+
https://github.com/proxy-wasm/proxy-wasm-go-sdk/blob/ab4161dcf9246a828008b539a82a1556cf0f2e24/examples/shared_data
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/shared_data
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: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
"encoding/binary"
19+
"errors"
20+
21+
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm"
22+
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types"
23+
)
24+
25+
const (
26+
sharedDataKey = "shared_data_key"
27+
)
28+
29+
func main() {}
30+
func init() {
31+
proxywasm.SetVMContext(&vmContext{})
32+
}
33+
34+
type (
35+
// vmContext implements types.VMContext.
36+
vmContext struct{}
37+
// pluginContext implements types.PluginContext.
38+
pluginContext struct {
39+
// Embed the default plugin context here,
40+
// so that we don't need to reimplement all the methods.
41+
types.DefaultPluginContext
42+
}
43+
// httpContext implements types.HttpContext.
44+
httpContext struct {
45+
// Embed the default http context here,
46+
// so that we don't need to reimplement all the methods.
47+
types.DefaultHttpContext
48+
}
49+
)
50+
51+
// OnVMStart implements types.VMContext.
52+
func (*vmContext) OnVMStart(vmConfigurationSize int) types.OnVMStartStatus {
53+
initialValueBuf := make([]byte, 0) // Empty data to indicate that the data is not initialized.
54+
if err := proxywasm.SetSharedData(sharedDataKey, initialValueBuf, 0); err != nil {
55+
proxywasm.LogWarnf("error setting shared data on OnVMStart: %v", err)
56+
}
57+
return types.OnVMStartStatusOK
58+
}
59+
60+
// NewPluginContext implements types.VMContext.
61+
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
62+
return &pluginContext{}
63+
}
64+
65+
// NewHttpContext implements types.PluginContext.
66+
func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
67+
return &httpContext{}
68+
}
69+
70+
// OnHttpRequestHeaders implements types.HttpContext.
71+
func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
72+
for {
73+
value, err := ctx.incrementData()
74+
if err == nil {
75+
proxywasm.LogInfof("shared value: %d", value)
76+
} else if errors.Is(err, types.ErrorStatusCasMismatch) {
77+
continue
78+
}
79+
break
80+
}
81+
return types.ActionContinue
82+
}
83+
84+
// incrementData increments the shared data value by 1.
85+
func (ctx *httpContext) incrementData() (uint64, error) {
86+
data, cas, err := proxywasm.GetSharedData(sharedDataKey)
87+
if err != nil {
88+
proxywasm.LogWarnf("error getting shared data on OnHttpRequestHeaders: %v", err)
89+
return 0, err
90+
}
91+
92+
var nextValue uint64
93+
if len(data) > 0 {
94+
nextValue = binary.LittleEndian.Uint64(data) + 1
95+
} else {
96+
nextValue = 1
97+
}
98+
99+
buf := make([]byte, 8)
100+
binary.LittleEndian.PutUint64(buf, nextValue)
101+
if err := proxywasm.SetSharedData(sharedDataKey, buf, cas); err != nil {
102+
proxywasm.LogWarnf("error setting shared data on OnHttpRequestHeaders: %v", err)
103+
return 0, err
104+
}
105+
return nextValue, err
106+
}
2.4 MB
Binary file not shown.

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,4 +435,31 @@ public WasmResult setAction(StreamType streamType, Action action) {
435435
public Action getAction() {
436436
return action;
437437
}
438+
439+
private final HashMap<String, SharedData> sharedData = new HashMap<>();
440+
441+
@Override
442+
public SharedData getSharedData(String key) throws WasmException {
443+
return sharedData.get(key);
444+
}
445+
446+
@Override
447+
public WasmResult setSharedData(String key, byte[] value, int cas) {
448+
SharedData prev = sharedData.get(key);
449+
if (prev == null) {
450+
if (cas == 0) {
451+
sharedData.put(key, new SharedData(value, 0));
452+
return WasmResult.OK;
453+
} else {
454+
return WasmResult.CAS_MISMATCH;
455+
}
456+
} else {
457+
if (cas == 0 || prev.cas == cas) {
458+
sharedData.put(key, new SharedData(value, prev.cas + 1));
459+
return WasmResult.OK;
460+
} else {
461+
return WasmResult.CAS_MISMATCH;
462+
}
463+
}
464+
}
438465
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.roastedroot.proxywasm;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import com.dylibso.chicory.wasm.Parser;
6+
import io.roastedroot.proxywasm.v1.Action;
7+
import io.roastedroot.proxywasm.v1.ProxyWasm;
8+
import io.roastedroot.proxywasm.v1.StartException;
9+
import java.nio.file.Path;
10+
import org.junit.jupiter.api.Test;
11+
12+
/**
13+
* Java port of https://github.com/proxy-wasm/proxy-wasm-go-sdk/blob/master/examples/shared_data/main_test.go
14+
*/
15+
public class SharedDataTest {
16+
17+
@Test
18+
public void testSetEffectiveContext() throws StartException {
19+
20+
var handler = new MockHandler();
21+
// Load the WASM module
22+
var module = Parser.parse(Path.of("./src/test/go-examples/shared_data/main.wasm"));
23+
24+
// Create and configure the ProxyWasm instance
25+
try (var host = ProxyWasm.builder().withPluginHandler(handler).build(module)) {
26+
27+
// Initialize context
28+
try (var context = host.createHttpContext(handler)) {
29+
30+
// Call OnHttpRequestHeaders.
31+
Action action = context.callOnRequestHeaders(false);
32+
assertEquals(Action.CONTINUE, action);
33+
34+
// Check Envoy logs.
35+
handler.assertLogsContain("shared value: 1");
36+
37+
// Call OnHttpRequestHeaders again.
38+
action = context.callOnRequestHeaders(false);
39+
assertEquals(Action.CONTINUE, action);
40+
action = context.callOnRequestHeaders(false);
41+
assertEquals(Action.CONTINUE, action);
42+
43+
// Check Envoy logs.
44+
handler.assertLogsContain("shared value: 3");
45+
}
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)