Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the 2023_10_18 version of web serving. #95

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cmd/wasirun/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,16 +229,17 @@ func run(wasmFile string, args []string) error {
var wasiHTTP *wasi_http.WasiHTTP = nil
switch wasiHttp {
case "auto":
importWasi = wasi_http.DetectWasiHttp(wasmModule)
importWasi, wasiHttp = wasi_http.DetectWasiHttp(wasmModule)
case "v1":
case "2023_10_18":
importWasi = true
case "none":
importWasi = false
default:
return fmt.Errorf("invalid value for -http '%v', expected 'auto', 'v1' or 'none'", wasiHttp)
return fmt.Errorf("invalid value for -http '%v', expected 'auto', 'v1', '2023_10_18' or 'none'", wasiHttp)
}
if importWasi {
wasiHTTP = wasi_http.MakeWasiHTTP()
wasiHTTP = wasi_http.MakeWasiHTTP(wasiHttp)
if err := wasiHTTP.Instantiate(ctx, runtime); err != nil {
return err
}
Expand Down
30 changes: 26 additions & 4 deletions imports/wasi_http/common/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,44 @@ func ReadString(mod api.Module, ptr, len uint32) (string, bool) {
return string(data), true
}

func WriteString(ctx context.Context, module api.Module, ptr uint32, str string) error {
func writeStringToMemory(ctx context.Context, module api.Module, str string) (uint32, error) {
data := []byte(str)
strPtr, err := Malloc(ctx, module, uint32(len(data)))
if err != nil {
return err
return 0, err
}
if !module.Memory().WriteString(strPtr, str) {
return fmt.Errorf("failed to write string")
return 0, fmt.Errorf("failed to write string")
}
data = []byte{}
return strPtr, nil
}

func WriteOptionalString(ctx context.Context, module api.Module, ptr uint32, str string) error {
strPtr, err := writeStringToMemory(ctx, module, str)
if err != nil {
return err
}
data := []byte{}
data = binary.LittleEndian.AppendUint32(data, 1) // is some
data = binary.LittleEndian.AppendUint32(data, strPtr)
data = binary.LittleEndian.AppendUint32(data, uint32(len(str)))
if !module.Memory().Write(ptr, data) {
return fmt.Errorf("failed to write struct")
}
return nil
}

func WriteString(ctx context.Context, module api.Module, ptr uint32, str string) error {
strPtr, err := writeStringToMemory(ctx, module, str)
if err != nil {
return err
}
data := []byte{}
data = binary.LittleEndian.AppendUint32(data, strPtr)
data = binary.LittleEndian.AppendUint32(data, uint32(len(str)))
if !module.Memory().Write(ptr, data) {
return fmt.Errorf("failed to write struct")
}
return nil
}

Expand Down
30 changes: 24 additions & 6 deletions imports/wasi_http/default_http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,36 @@ package default_http

import (
"context"
"fmt"

"github.com/stealthrocket/wasi-go/imports/wasi_http/types"
"github.com/tetratelabs/wazero"
)

const ModuleName = "default-outgoing-HTTP"
const (
ModuleName = "default-outgoing-HTTP"
ModuleName_2023_10_18 = "wasi:http/outgoing-handler@0.2.0-rc-2023-10-18"
)

func Instantiate(ctx context.Context, r wazero.Runtime, req *types.Requests, res *types.Responses, f *types.FieldsCollection) error {
func Instantiate(ctx context.Context, r wazero.Runtime, req *types.Requests, res *types.Responses, f *types.FieldsCollection, version string) error {
handler := &Handler{req, res, f}
_, err := r.NewHostModuleBuilder(ModuleName).
NewFunctionBuilder().WithFunc(requestFn).Export("request").
NewFunctionBuilder().WithFunc(handler.handleFn).Export("handle").
Instantiate(ctx)
var name string
switch version {
case "v1":
name = ModuleName
case "2023_10_18":
name = ModuleName_2023_10_18
default:
return fmt.Errorf("unknown version: %s", version)
}
builder := r.NewHostModuleBuilder(name).
NewFunctionBuilder().WithFunc(requestFn).Export("request")
switch version {
case "v1":
builder.NewFunctionBuilder().WithFunc(handler.handleFn).Export("handle")
case "2023_10_18":
builder.NewFunctionBuilder().WithFunc(handler.handleFn_2023_10_18).Export("handle")
}
_, err := builder.Instantiate(ctx)
return err
}
43 changes: 43 additions & 0 deletions imports/wasi_http/default_http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package default_http

import (
"context"
"encoding/binary"
"fmt"
"log"

"github.com/stealthrocket/wasi-go/imports/wasi_http/types"
Expand All @@ -19,6 +21,18 @@ func requestFn(_ context.Context, mod api.Module, a, b, c, d, e, f, g, h, j, k,
return 0
}

func doError(mod api.Module, ptr uint32, msg string) {
data := []byte{}
data = binary.LittleEndian.AppendUint32(data, 1) // IsError == 1
data = binary.LittleEndian.AppendUint32(data, 3) // Always "unexpected error" for now
data = binary.LittleEndian.AppendUint32(data, 0) // TODO: pass string here.
data = binary.LittleEndian.AppendUint32(data, 0) // as above

if !mod.Memory().Write(ptr, data) {
panic("Failed to write response!")
}
}

// Handle handles HTTP client calls.
// The remaining parameters (b..h) are for the HTTP Options, currently unimplemented.
func (handler *Handler) handleFn(_ context.Context, mod api.Module, request, b, c, d, e, f, g, h uint32) uint32 {
Expand All @@ -34,3 +48,32 @@ func (handler *Handler) handleFn(_ context.Context, mod api.Module, request, b,
}
return handler.res.MakeResponse(r)
}

// Handle handles HTTP client calls.
// The remaining parameters (b..h) are for the HTTP Options, currently unimplemented.
func (handler *Handler) handleFn_2023_10_18(_ context.Context, mod api.Module, request, b, c, d, e, f, g, h, ptr uint32) {
req, ok := handler.req.GetRequest(request)
if !ok {
msg := fmt.Sprintf("Failed to get request: %v\n", request)
log.Printf(msg)
doError(mod, ptr, msg)
return
Comment on lines +57 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this condition expected to ever happen? Or would it be better suited to panic if we hit it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition will happen if someone loads a WASM binary that doesn't export the right function.

In such a situation, it feels like the right thing to do is have the server return a 500 error, as opposed to crashing via panic()

}
r, err := req.MakeRequest(handler.f)
if err != nil {
log.Println(err.Error())
doError(mod, ptr, err.Error())
return
}
res := handler.res.MakeResponse(r)
data := []byte{}

data = binary.LittleEndian.AppendUint32(data, 0) // IsOk == 0
data = binary.LittleEndian.AppendUint32(data, res)
data = binary.LittleEndian.AppendUint32(data, 0) // Used for errors
data = binary.LittleEndian.AppendUint32(data, 0) // Used for errors

if !mod.Memory().Write(ptr, data) {
panic("Failed to write response!")
}
}
58 changes: 51 additions & 7 deletions imports/wasi_http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package wasi_http

import (
"context"
"fmt"
"net/http"

"github.com/stealthrocket/wasi-go/imports/wasi_http/default_http"
Expand All @@ -18,9 +19,10 @@ type WasiHTTP struct {
r *types.Requests
rs *types.Responses
o *types.OutResponses
v string
}

func MakeWasiHTTP() *WasiHTTP {
func MakeWasiHTTP(version string) *WasiHTTP {
s := streams.MakeStreams()
f := types.MakeFields()
r := types.MakeRequests(s, f)
Expand All @@ -33,45 +35,87 @@ func MakeWasiHTTP() *WasiHTTP {
r: r,
rs: rs,
o: o,
v: version,
}
}

func (w *WasiHTTP) Instantiate(ctx context.Context, rt wazero.Runtime) error {
if err := types.Instantiate(ctx, rt, w.s, w.r, w.rs, w.f, w.o); err != nil {
switch w.v {
case "v1":
return w.instantiateV1(ctx, rt)
case "2023_10_18":
return w.instantiate_2023_10_18(ctx, rt)
default:
return fmt.Errorf("unknown version: %v", w.v)
}
}

func (w *WasiHTTP) instantiateV1(ctx context.Context, rt wazero.Runtime) error {
if err := types.Instantiate_v1(ctx, rt, w.s, w.r, w.rs, w.f, w.o); err != nil {
return err
}
if err := streams.Instantiate(ctx, rt, w.s); err != nil {
if err := streams.Instantiate_v1(ctx, rt, w.s); err != nil {
return err
}
if err := default_http.Instantiate(ctx, rt, w.r, w.rs, w.f); err != nil {
if err := default_http.Instantiate(ctx, rt, w.r, w.rs, w.f, w.v); err != nil {
return err
}
return nil
}

func DetectWasiHttp(module wazero.CompiledModule) bool {
func (w *WasiHTTP) instantiate_2023_10_18(ctx context.Context, rt wazero.Runtime) error {
if err := types.Instantiate_2023_10_18(ctx, rt, w.s, w.r, w.rs, w.f, w.o); err != nil {
return err
}
if err := streams.Instantiate_2023_10_18(ctx, rt, w.s); err != nil {
return err
}
if err := default_http.Instantiate(ctx, rt, w.r, w.rs, w.f, w.v); err != nil {
return err
}
return nil
}

func DetectWasiHttp(module wazero.CompiledModule) (bool, string) {
functions := module.ImportedFunctions()
hasWasiHttp := false
version := ""
for _, f := range functions {
moduleName, name, ok := f.Import()
if !ok || moduleName != default_http.ModuleName {
if !ok || (moduleName != default_http.ModuleName && moduleName != default_http.ModuleName_2023_10_18) {
continue
}
switch name {
case "handle":
hasWasiHttp = true
switch moduleName {
case default_http.ModuleName:
version = "v1"
case default_http.ModuleName_2023_10_18:
version = "2023_10_18"
default:
version = "unknown"
}
}
}
return hasWasiHttp
return hasWasiHttp, version
}

func (w *WasiHTTP) MakeHandler(ctx context.Context, m api.Module) http.Handler {
fnName := ""
switch w.v {
case "v1":
fnName = "HTTP#handle"
case "2023_10_18":
fnName = "exports_wasi_http_0_2_0_rc_2023_10_18_incoming_handler_handle"
}
return server.WasmServer{
Ctx: ctx,
Module: m,
Requests: w.r,
Responses: w.rs,
Fields: w.f,
OutParams: w.o,
HandleFn: fnName,
}
}
4 changes: 2 additions & 2 deletions imports/wasi_http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func TestHttpClient(t *testing.T) {
}
defer system.Close(ctx)

w := MakeWasiHTTP()
w := MakeWasiHTTP("v1")
w.Instantiate(ctx, runtime)

instance, err := runtime.Instantiate(ctx, bytecode)
Expand Down Expand Up @@ -170,7 +170,7 @@ func TestServer(t *testing.T) {
}
defer system.Close(ctx)

w := MakeWasiHTTP()
w := MakeWasiHTTP("v1")
w.Instantiate(ctx, runtime)

instance, err := runtime.Instantiate(ctx, bytecode)
Expand Down
3 changes: 2 additions & 1 deletion imports/wasi_http/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ type WasmServer struct {
Requests *types.Requests
Responses *types.Responses
OutParams *types.OutResponses
HandleFn string
}

func (w WasmServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
fn := w.Module.ExportedFunction("HTTP#handle")
fn := w.Module.ExportedFunction(w.HandleFn)
if fn == nil {
res.WriteHeader(500)
res.Write([]byte("Handler not found"))
Expand Down
19 changes: 16 additions & 3 deletions imports/wasi_http/streams/streams.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import (
"github.com/tetratelabs/wazero"
)

const ModuleName = "streams"
const (
ModuleName = "streams"
ModuleName_2023_10_18 = "wasi:io/streams@0.2.0-rc-2023-10-18"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been trying to get this PR working with https://github.com/dev-wasm/dev-wasm-go/tree/main/http without success. The first issue is that module name should be wasi:io/streams instead that dev-wasm-go client will work with that. However, still after that there is error

panic: wasm: error instantiating guest: "read" is not exported in module "wasi:io/streams"

perhaps some mismatch between versions in dev-wasm-go and wasi-go

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just thinking, could it make sense to make one working combination with wasi-go wasi_http and dev-wasm-go client?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zetaab dev-wasm-go unfortunately hasn't been updated (I will do that soon) there is an example here that will work:

https://github.com/brendandburns/hello-wasi-http-go

I'll try to get dev-wasm-go updated soon.

)

type Stream struct {
reader io.Reader
Expand All @@ -30,11 +33,21 @@ func MakeStreams() *Streams {
}
}

func Instantiate(ctx context.Context, r wazero.Runtime, s *Streams) error {
func Instantiate_v1(ctx context.Context, r wazero.Runtime, s *Streams) error {
_, err := r.NewHostModuleBuilder(ModuleName).
NewFunctionBuilder().WithFunc(s.streamReadFn).Export("read").
NewFunctionBuilder().WithFunc(s.dropInputStreamFn).Export("drop-input-stream").
NewFunctionBuilder().WithFunc(s.writeStreamFn).Export("write").
NewFunctionBuilder().WithFunc(s.blockingWriteAndFlush).Export("write").
Instantiate(ctx)
return err
}

func Instantiate_2023_10_18(ctx context.Context, r wazero.Runtime, s *Streams) error {
_, err := r.NewHostModuleBuilder(ModuleName_2023_10_18).
NewFunctionBuilder().WithFunc(s.streamReadFn).Export("[method]input-stream.read").
NewFunctionBuilder().WithFunc(s.dropInputStreamFn).Export("[resource-drop]input-stream").
NewFunctionBuilder().WithFunc(s.blockingWriteAndFlush).Export("[method]output-stream.blocking-write-and-flush").
//NewFunctionBuilder().WithFunc(s.writeStreamFn).Export("write").
Instantiate(ctx)
return err
}
Expand Down
2 changes: 1 addition & 1 deletion imports/wasi_http/streams/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/tetratelabs/wazero/api"
)

func (s *Streams) writeStreamFn(_ context.Context, mod api.Module, stream, ptr, l, result_ptr uint32) {
func (s *Streams) blockingWriteAndFlush(_ context.Context, mod api.Module, stream, ptr, l, result_ptr uint32) {
data, ok := mod.Memory().Read(ptr, l)
if !ok {
log.Printf("Body read failed!\n")
Expand Down
Loading
Loading