Skip to content

Commit

Permalink
Add support for wasi-http in the wasm binding. (#3007)
Browse files Browse the repository at this point in the history
Co-authored-by: Alessandro (Ale) Segala <[email protected]>
  • Loading branch information
brendandburns and ItalyPaleAle authored Jul 28, 2023
1 parent 13dfbdb commit 387c238
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 8 deletions.
33 changes: 25 additions & 8 deletions bindings/wasm/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"strings"
"sync/atomic"

"github.com/stealthrocket/wasi-go/imports/wasi_http"
"github.com/stealthrocket/wasi-go/imports/wasi_http/default_http"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
Expand Down Expand Up @@ -77,16 +79,27 @@ func (out *outputBinding) Init(ctx context.Context, metadata bindings.Metadata)
return fmt.Errorf("wasm: error compiling binary: %w", err)
}

switch detectImports(out.module.ImportedFunctions()) {
case modeWasiP1:
imports := detectImports(out.module.ImportedFunctions())

if _, found := imports[modeWasiP1]; found {
_, err = wasi_snapshot_preview1.Instantiate(ctx, out.runtime)
}

if err != nil {
_ = out.runtime.Close(context.Background())
return fmt.Errorf("wasm: error instantiating host functions: %w", err)
return fmt.Errorf("wasm: error instantiating host wasi functions: %w", err)
}
return
if _, found := imports[modeWasiHTTP]; found {
if out.meta.StrictSandbox {
_ = out.runtime.Close(context.Background())
return fmt.Errorf("can not instantiate wasi-http with strict sandbox")
}
err = wasi_http.Instantiate(ctx, out.runtime)
}
if err != nil {
_ = out.runtime.Close(context.Background())
return fmt.Errorf("wasm: error instantiating host wasi-http functions: %w", err)
}
return nil
}

func (out *outputBinding) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
Expand Down Expand Up @@ -152,19 +165,23 @@ func (out *outputBinding) Close() error {
const (
modeDefault importMode = iota
modeWasiP1
modeWasiHTTP
)

type importMode uint

func detectImports(imports []api.FunctionDefinition) importMode {
func detectImports(imports []api.FunctionDefinition) map[importMode]bool {
result := make(map[importMode]bool)
for _, f := range imports {
moduleName, _, _ := f.Import()
switch moduleName {
case wasi_snapshot_preview1.ModuleName:
return modeWasiP1
result[modeWasiP1] = true
case default_http.ModuleName:
result[modeWasiHTTP] = true
}
}
return modeDefault
return result
}

// GetComponentMetadata returns the metadata of the component.
Expand Down
89 changes: 89 additions & 0 deletions bindings/wasm/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"context"
_ "embed"
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -31,6 +33,7 @@ const (
urlArgsFile = "file://testdata/args/main.wasm"
urlExampleFile = "file://testdata/example/main.wasm"
urlLoopFile = "file://testdata/loop/main.wasm"
urlHTTPFile = "file://testdata/http/main.wasm"
)

func Test_outputBinding_Init(t *testing.T) {
Expand Down Expand Up @@ -179,3 +182,89 @@ func Test_Invoke(t *testing.T) {
})
}
}

type handler struct{}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/unknown" {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("Not found"))
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(r.URL.Path))
}

func Test_InvokeHttp(t *testing.T) {
type testCase struct {
name string
url string
request *bindings.InvokeRequest
ctx context.Context
reqURL string
expectedData string
expectedErr string
}

s := httptest.NewServer(&handler{})
defer s.Close()

tests := []testCase{
{
name: "http",
url: urlHTTPFile,
request: &bindings.InvokeRequest{
Operation: ExecuteOperation,
},
reqURL: s.URL,
expectedData: "Status: 200\nBody: \n/\n",
},
{
name: "unknown",
url: urlHTTPFile,
request: &bindings.InvokeRequest{
Operation: ExecuteOperation,
},
reqURL: s.URL + "/unknown",
expectedData: "Status: 404\nBody: \nNot found\n",
},
}

for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
l := logger.NewLogger(t.Name())
var buf bytes.Buffer
l.SetOutput(&buf)

meta := metadata.Base{Properties: map[string]string{"url": tc.url}}

output := NewWasmOutput(l)
defer output.(io.Closer).Close()

ctx := context.Background()

err := output.Init(ctx, bindings.Metadata{Base: meta})
require.NoError(t, err)

reqCtx := tc.ctx
if reqCtx == nil {
reqCtx = ctx
}

tc.request.Metadata = map[string]string{"args": tc.reqURL}

if tc.expectedErr == "" {
// execute twice to prove idempotency
for i := 0; i < 2; i++ {
resp, outputErr := output.Invoke(reqCtx, tc.request)
require.NoError(t, outputErr)
require.Equal(t, tc.expectedData, string(resp.Data))
}
} else {
_, err = output.Invoke(reqCtx, tc.request)
require.EqualError(t, err, tc.expectedErr)
}
})
}
}
4 changes: 4 additions & 0 deletions bindings/wasm/testdata/http/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
default: main.wasm

main.wasm: main.go
tinygo build -target wasi -o main.wasm main.go
35 changes: 35 additions & 0 deletions bindings/wasm/testdata/http/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

// Building main.wasm:
// go get github.com/dev-wasm/dev-wasm-go/http/client
// tinygo build -target wasi -o main.wasm main.go

import (
"fmt"
"io/ioutil"
"net/http"
"os"

wasiclient "github.com/dev-wasm/dev-wasm-go/http/client"
)

func printResponse(r *http.Response) {
fmt.Printf("Status: %d\n", r.StatusCode)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err.Error())
}
fmt.Printf("Body: \n%s\n", body)
}

func main() {
client := http.Client{
Transport: wasiclient.WasiRoundTripper{},
}
res, err := client.Get(os.Args[1])
if err != nil {
panic(err.Error())
}
defer res.Body.Close()
printResponse(res)
}
Binary file added bindings/wasm/testdata/http/main.wasm
Binary file not shown.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ require (
github.com/sendgrid/sendgrid-go v3.12.0+incompatible
github.com/sijms/go-ora/v2 v2.7.9
github.com/spf13/cast v1.5.1
github.com/stealthrocket/wasi-go v0.7.6-0.20230718231108-c3d30af59057
github.com/stretchr/testify v1.8.4
github.com/supplyon/gremcos v0.1.40
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.608
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1861,6 +1861,9 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/stealthrocket/wasi-go v0.7.6-0.20230718231108-c3d30af59057 h1:BaBBX206PM1+qF5WQx7Ug7mbKqzizBONDMv4ST5EVNg=
github.com/stealthrocket/wasi-go v0.7.6-0.20230718231108-c3d30af59057/go.mod h1:PJ5oVs2E1ciOJnsTnav4nvTtEcJ4D1jUZAewS9pzuZg=
github.com/stealthrocket/wazergo v0.19.1 h1:BPrITETPgSFwiytwmToO0MbUC/+RGC39JScz1JmmG6c=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
Expand Down

0 comments on commit 387c238

Please sign in to comment.