Skip to content

Commit

Permalink
Add support for wasi-http in the wasm binding.
Browse files Browse the repository at this point in the history
  • Loading branch information
brendandburns committed Jul 21, 2023
1 parent 608e4cb commit b97a328
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 7 deletions.
27 changes: 20 additions & 7 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,14 +79,21 @@ 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)
}
if _, found := imports[modeWasiHTTP]; found {
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

Check failure on line 98 in bindings/wasm/output.go

View workflow job for this annotation

GitHub Actions / Build linux_amd64 binaries

naked return in func `Init` with 32 lines of code (nakedret)
}
Expand Down Expand Up @@ -152,19 +161,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
88 changes: 88 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"

Check failure on line 36 in bindings/wasm/output_test.go

View workflow job for this annotation

GitHub Actions / Build linux_amd64 binaries

ST1003: const urlHttpFile should be urlHTTPFile (stylecheck)
)

func Test_outputBinding_Init(t *testing.T) {
Expand Down Expand Up @@ -179,3 +182,88 @@ 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(404)

Check failure on line 190 in bindings/wasm/output_test.go

View workflow job for this annotation

GitHub Actions / Build linux_amd64 binaries

"404" can be replaced by http.StatusNotFound (usestdlibvars)
w.Write([]byte("Not found"))
return
}
w.WriteHeader(200)

Check failure on line 194 in bindings/wasm/output_test.go

View workflow job for this annotation

GitHub Actions / Build linux_amd64 binaries

"200" can be replaced by http.StatusOK (usestdlibvars)
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{})

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)
}
})
}
}
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 @@ -340,6 +340,7 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stealthrocket/wasi-go v0.7.6-0.20230718231108-c3d30af59057 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/tidwall/gjson v1.13.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,8 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/RoaringBitmap/roaring v1.1.0 h1:b10lZrZXaY6Q6EKIRrmOF519FIyQQ5anPgGr3niw2yY=
github.com/RoaringBitmap/roaring v1.1.0/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
github.com/Shopify/sarama v1.37.2 h1:LoBbU0yJPte0cE5TZCGdlzZRmMgMtZU/XgnUKZg9Cv4=
Expand Down Expand Up @@ -1861,6 +1863,10 @@ 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.5 h1:UP5zVXI6ifbGIPEmbM9S28DUHL25jvrvawMRDoYXuVQ=
github.com/stealthrocket/wasi-go v0.7.5/go.mod h1:PJ5oVs2E1ciOJnsTnav4nvTtEcJ4D1jUZAewS9pzuZg=
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/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 b97a328

Please sign in to comment.