Skip to content

Commit

Permalink
Add entrypoints for Plugin, Http, Tcp contexts
Browse files Browse the repository at this point in the history
Fixes #5

Signed-off-by: Matt Leon <[email protected]>
  • Loading branch information
leonm1 committed Jan 21, 2025
1 parent 23e5db5 commit f6dc8ce
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 67 deletions.
19 changes: 8 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,17 @@ import (

func main() {}
func init() {
proxywasm.SetVMContext(&vmContext{})
proxywasm.SetHttpContext(func(contextID uint32) types.HttpContext {
return &httpContext{}
})
}

type vmContext struct {
types.DefaultVMContext
}
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
return &pluginContext{}
type httpContext struct {
types.DefaultHttpContext
}

type pluginContext struct {
types.DefaultPluginContext
func (*httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
proxywasm.LogInfo("Hello, world!")
return types.ActionContinue
}
// pluginContext should implement OnPluginStart, NewHttpContext, NewTcpContext, etc
```

Compile the plugin as follows:
Expand Down
16 changes: 3 additions & 13 deletions examples/helloworld/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,9 @@ const tickMilliseconds uint32 = 1000

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 &helloWorld{}
proxywasm.SetPluginContext(func(contextID uint32) types.PluginContext {
return &helloWorld{}
})
}

// helloWorld implements types.PluginContext.
Expand Down
14 changes: 7 additions & 7 deletions examples/helloworld/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
)

func TestHelloWorld_OnTick(t *testing.T) {
vmTest(t, func(t *testing.T, vm types.VMContext) {
opt := proxytest.NewEmulatorOption().WithVMContext(vm)
vmTest(t, func(t *testing.T, pc types.PluginContextFactory) {
opt := proxytest.NewEmulatorOption().WithPluginContext(pc)
host, reset := proxytest.NewHostEmulator(opt)
defer reset()

Expand All @@ -33,8 +33,8 @@ func TestHelloWorld_OnTick(t *testing.T) {
}

func TestHelloWorld_OnPluginStart(t *testing.T) {
vmTest(t, func(t *testing.T, vm types.VMContext) {
opt := proxytest.NewEmulatorOption().WithVMContext(vm)
vmTest(t, func(t *testing.T, pc types.PluginContextFactory) {
opt := proxytest.NewEmulatorOption().WithPluginContext(pc)
host, reset := proxytest.NewHostEmulator(opt)
defer reset()

Expand All @@ -51,11 +51,11 @@ func TestHelloWorld_OnPluginStart(t *testing.T) {
// vmTest executes f twice, once with a types.VMContext that executes plugin code directly
// in the host, and again by executing the plugin code within the compiled main.wasm binary.
// Execution with main.wasm will be skipped if the file cannot be found.
func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) {
func vmTest(t *testing.T, f func(*testing.T, types.PluginContextFactory)) {
t.Helper()

t.Run("go", func(t *testing.T) {
f(t, &vmContext{})
f(t, func(uint32) types.PluginContext { return &helloWorld{} })
})

t.Run("wasm", func(t *testing.T) {
Expand All @@ -66,6 +66,6 @@ func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) {
v, err := proxytest.NewWasmVMContext(wasm)
require.NoError(t, err)
defer v.Close()
f(t, v)
f(t, v.NewPluginContext)
})
}
27 changes: 3 additions & 24 deletions examples/properties/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,9 @@ import (

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{}
}

type pluginContext struct {
// Embed the default plugin context here,
// so that we don't need to reimplement all the methods.
types.DefaultPluginContext
}

// NewHttpContext implements types.PluginContext.
func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
return &properties{contextID: contextID}
proxywasm.SetHttpContext(func(contextID uint32) types.HttpContext {
return &properties{contextID: contextID}
})
}

// properties implements types.HttpContext.
Expand Down
13 changes: 8 additions & 5 deletions examples/properties/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
)

func TestProperties_OnHttpRequestHeaders(t *testing.T) {
vmTest(t, func(t *testing.T, vm types.VMContext) {
opt := proxytest.NewEmulatorOption().WithVMContext(vm)
vmTest(t, func(t *testing.T, h types.HttpContextFactory) {
opt := proxytest.NewEmulatorOption().WithHttpContext(h)
host, reset := proxytest.NewHostEmulator(opt)
defer reset()

Expand Down Expand Up @@ -90,11 +90,13 @@ func TestProperties_OnHttpRequestHeaders(t *testing.T) {
// vmTest executes f twice, once with a types.VMContext that executes plugin code directly
// in the host, and again by executing the plugin code within the compiled main.wasm binary.
// Execution with main.wasm will be skipped if the file cannot be found.
func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) {
func vmTest(t *testing.T, f func(*testing.T, types.HttpContextFactory)) {
t.Helper()

t.Run("go", func(t *testing.T) {
f(t, &vmContext{})
f(t, func(contextID uint32) types.HttpContext {
return &properties{contextID: contextID}
})
})

t.Run("wasm", func(t *testing.T) {
Expand All @@ -103,8 +105,9 @@ func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) {
t.Skip("wasm not found")
}
v, err := proxytest.NewWasmVMContext(wasm)
p := v.NewPluginContext(1)
require.NoError(t, err)
defer v.Close()
f(t, v)
f(t, p.NewHttpContext)
})
}
54 changes: 51 additions & 3 deletions proxywasm/entrypoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,57 @@ import (
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types"
)

// SetVMContext is the entrypoint for setting up the entire Wasm VM.
// Please make sure to call this entrypoint during "main()" function;
// otherwise, the VM fails.
// SetVMContext is one possible entrypoint for setting up the entire Wasm VM.
//
// Subsequent calls to any entrypoint overwrite previous calls to any
// entrypoint. Be sure to call exactly one entrypoint during `init()`,
// otherwise the VM fails.
func SetVMContext(ctx types.VMContext) {
internal.SetVMContext(ctx)
}

// SetPluginContext is one possible entrypoint for setting up the Wasm VM.
//
// Subsequent calls to any entrypoint overwrite previous calls to any
// entrypoint. Be sure to call exactly one entrypoint during `init()`,
// otherwise the VM fails.
//
// Using SetPluginContext instead of SetVmContext is suitable iff the plugin
// does not make use of the VM configuration provided during `VmContext`'s
// `OnVmStart` call (plugin configuration data is still provided during
// `PluginContext`'s `OnPluginStart` call).
func SetPluginContext(newPluginContext func(contextID uint32) types.PluginContext) {
internal.SetPluginContext(newPluginContext)
}

// SetHttpContext is one possible entrypoint for setting up the Wasm VM. It
// allows plugin authors to provide an Http context implementation without
// writing a VmContext or PluginContext.
//
// Subsequent calls to any entrypoint overwrite previous calls to any
// entrypoint. Be sure to call exactly one entrypoint during `init()`,
// otherwise the VM fails.
//
// SetHttpContext is suitable for stateless plugins that share no state between
// HTTP requests, do not process TCP streams, have no expensive shared setup
// requiring execution during `OnPluginStart`, and do not access the plugin
// configuration data.
func SetHttpContext(newHttpContext func(contextID uint32) types.HttpContext) {
internal.SetHttpContext(newHttpContext)
}

// SetTcpContext is one possible entrypoint for setting up the Wasm VM. It
// allows plugin authors to provide a TCP context implementation without
// writing a VmContext or PluginContext.
//
// Subsequent calls to any entrypoint overwrite previous calls to any
// entrypoint. Be sure to call exactly one entrypoint during `init()`,
// otherwise the VM fails.
//
// SetTcpContext is suitable for stateless plugins that share no state between
// TCP streams, do not process HTTP requests, have no expensive shared setup
// requiring execution during `OnPluginStart`, and do not access the plugin
// configuration data.
func SetTcpContext(newTcpContext func(contextID uint32) types.TcpContext) {
internal.SetTcpContext(newTcpContext)
}
72 changes: 72 additions & 0 deletions proxywasm/internal/entrypoints.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2025 Google LLC
//
// 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 internal

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

// elidedVmContext is registered when the plugin author uses a
// SetPluginContext, SetHttpContext, or SetTcpContext entrypoint. It indicates
// the author did not register a VmContext.
//
// elidedVmContext's primary responsibility is calling the author-provided (or
// elided) thunk to create a new PluginContext.
type elidedVmContext struct {
types.DefaultVMContext
newPluginContext types.PluginContextFactory
}

func (ctx *elidedVmContext) NewPluginContext(contextID uint32) types.PluginContext {
return ctx.newPluginContext(contextID)
}

// elidedPluginContext is registered when the plugin author uses the
// SetHttpContext or SetTcpContext entrypoints. It indicates the author did not
// register a VmContext or PluginContext.
//
// elidedVmContext's primary responsibility is calling the author-provided (or
// elided) thunk to create a new HttpContext or TcpContext.
type elidedPluginContext struct {
types.DefaultPluginContext
newHttpContext types.HttpContextFactory
newTcpContext types.TcpContextFactory
}

func (ctx *elidedPluginContext) NewHttpContext(contextID uint32) types.HttpContext {
return ctx.newHttpContext(contextID)
}

func (ctx *elidedPluginContext) NewTcpContext(contextID uint32) types.TcpContext {
return ctx.newTcpContext(contextID)
}

func SetPluginContext(newPluginContext types.PluginContextFactory) {
SetVMContext(&elidedVmContext{newPluginContext: newPluginContext})
}

func SetHttpContext(newHttpContext types.HttpContextFactory) {
SetVMContext(&elidedVmContext{
newPluginContext: func(uint32) types.PluginContext {
return &elidedPluginContext{newHttpContext: newHttpContext}
},
})
}

func SetTcpContext(newTcpContext types.TcpContextFactory) {
SetVMContext(&elidedVmContext{
newPluginContext: func(uint32) types.PluginContext {
return &elidedPluginContext{newTcpContext: newTcpContext}
},
})
}
95 changes: 95 additions & 0 deletions proxywasm/internal/entrypoints_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2025 Google LLC
//
// 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 internal

import (
"testing"

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

type testPluginContext struct {
types.DefaultPluginContext
contextID uint32
}

func TestSetPluginContext_ReturnsPluginContext(t *testing.T) {
SetPluginContext(func(contextID uint32) types.PluginContext {
return &testPluginContext{contextID: contextID}
})

pluginContext := currentState.vmContext.NewPluginContext(4321)

require.IsType(t, pluginContext, &testPluginContext{})
require.Equal(t, pluginContext.(*testPluginContext).contextID, uint32(4321))
}

type testPluginContextB struct {
types.DefaultPluginContext
}

func TestSetPluginContext_Reentrant(t *testing.T) {
SetPluginContext(func(uint32) types.PluginContext {
return &testPluginContext{}
})
SetPluginContext(func(uint32) types.PluginContext {
return &testPluginContextB{}
})

require.IsType(t, currentState.vmContext.NewPluginContext(1), &testPluginContextB{})
}

type (
testHttpContext struct {
types.DefaultHttpContext
contextID uint32
}
testTcpContext struct{ types.DefaultTcpContext }
)

func TestSetHttpContext(t *testing.T) {
SetHttpContext(func(contextID uint32) types.HttpContext {
return &testHttpContext{contextID: contextID}
})

pluginContext := currentState.vmContext.NewPluginContext(4321).NewHttpContext(1234)

require.IsType(t, pluginContext, &testHttpContext{})
require.Equal(t, pluginContext.(*testHttpContext).contextID, uint32(1234))
}

func TestSetTcpContext(t *testing.T) {
SetTcpContext(func(uint32) types.TcpContext {
return &testTcpContext{}
})

pluginContext := currentState.vmContext.NewPluginContext(4321).NewTcpContext(1234)

require.IsType(t, pluginContext, &testTcpContext{})
}

func TestSetTcpContext_ClearsSetHttpContext(t *testing.T) {
SetHttpContext(func(contextID uint32) types.HttpContext {
return &testHttpContext{contextID: contextID}
})
SetTcpContext(func(uint32) types.TcpContext {
return &testTcpContext{}
})

pluginContext := currentState.vmContext.NewPluginContext(4321).NewTcpContext(1234)

require.IsType(t, pluginContext, &testTcpContext{})
}
Loading

0 comments on commit f6dc8ce

Please sign in to comment.