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

Add support for wasi-http #56

Merged
merged 13 commits into from
Jul 1, 2023
12 changes: 12 additions & 0 deletions cmd/wasirun/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/stealthrocket/wasi-go"
"github.com/stealthrocket/wasi-go/imports"
"github.com/stealthrocket/wasi-go/imports/wasi_http"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/sys"
)
Expand Down Expand Up @@ -55,6 +56,9 @@ OPTIONS:
--non-blocking-stdio
Enable non-blocking stdio

--wasi-http
Enable wasi-http client support

achille-roussel marked this conversation as resolved.
Show resolved Hide resolved
-v, --version
Print the version and exit

Expand All @@ -73,6 +77,7 @@ var (
trace bool
nonBlockingStdio bool
version bool
wasiHttp bool
)

func main() {
Expand All @@ -89,6 +94,7 @@ func main() {
flagSet.BoolVar(&nonBlockingStdio, "non-blocking-stdio", false, "")
flagSet.BoolVar(&version, "version", false, "")
flagSet.BoolVar(&version, "v", false, "")
flagSet.BoolVar(&wasiHttp, "wasi-http", false, "")
flagSet.Parse(os.Args[1:])

if version {
Expand Down Expand Up @@ -158,6 +164,12 @@ func run(wasmFile string, args []string) error {
}
defer system.Close(ctx)

if wasiHttp {
if err := wasi_http.Instantiate(ctx, runtime); err != nil {
return err
}
}

instance, err := runtime.InstantiateModule(ctx, wasmModule, wazero.NewModuleConfig())
if err != nil {
return err
Expand Down
17 changes: 17 additions & 0 deletions imports/wasi_http/common/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package common

import (
"context"
"log"

"github.com/tetratelabs/wazero/api"
)

func Malloc(ctx context.Context, m api.Module, size uint32) (uint32, error) {
malloc := m.ExportedFunction("cabi_realloc")
result, err := malloc.Call(ctx, 0, 0, 4, uint64(size))
if err != nil {
log.Fatalf(err.Error())
}
return uint32(result[0]), err
}
17 changes: 17 additions & 0 deletions imports/wasi_http/default_http/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package default_http

import (
"context"

"github.com/tetratelabs/wazero"
)

const ModuleName = "default-outgoing-HTTP"
achille-roussel marked this conversation as resolved.
Show resolved Hide resolved

func Instantiate(ctx context.Context, r wazero.Runtime) error {
_, err := r.NewHostModuleBuilder(ModuleName).
NewFunctionBuilder().WithFunc(requestFn).Export("request").
NewFunctionBuilder().WithFunc(handleFn).Export("handle").
Instantiate(ctx)
return err
}
36 changes: 36 additions & 0 deletions imports/wasi_http/default_http/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package default_http

import (
"context"
"log"

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

//var request = newHostFunc("request", requestFn,
// []api.ValueType{i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32},
// "a", "b", "c", "d", "e", "f", "g", "h", "j", "k", "l", "m", "n", "o")

func requestFn(_ context.Context, mod api.Module, a, b, c, d, e, f, g, h, j, k, l, m, n, o uint32) int32 {
return 0
}

//var handle = newHostFunc("handle", handleFn,
// []api.ValueType{i32, i32, i32, i32, i32, i32, i32, i32},
// "a", "b", "c", "d", "e", "f", "g", "h")

func handleFn(_ context.Context, mod api.Module, request, b, c, d, e, f, g, h uint32) int32 {
req, ok := types.GetRequest(request)
if !ok {
log.Printf("Failed to get request: %v", request)
return 0
}
r, err := req.MakeRequest()
if err != nil {
log.Printf(err.Error())
return 0
achille-roussel marked this conversation as resolved.
Show resolved Hide resolved
}
types.SetResponse(r)
return 1
}
23 changes: 23 additions & 0 deletions imports/wasi_http/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package wasi_http

import (
"context"

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

func Instantiate(ctx context.Context, rt wazero.Runtime) error {
if err := types.Instantiate(ctx, rt); err != nil {
return err
}
if err := wasi_streams.Instantiate(ctx, rt); err != nil {
return err
}
if err := default_http.Instantiate(ctx, rt); err != nil {
return err
}
return nil
}
25 changes: 25 additions & 0 deletions imports/wasi_http/types/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package types

import (
"context"

"github.com/tetratelabs/wazero"
)

const ModuleName = "types"

func Instantiate(ctx context.Context, r wazero.Runtime) error {
_, err := r.NewHostModuleBuilder(ModuleName).
NewFunctionBuilder().WithFunc(newOutgoingRequestFn).Export("new-outgoing-request").
NewFunctionBuilder().WithFunc(newFieldsFn).Export("new-fields").
NewFunctionBuilder().WithFunc(fieldsEntriesFn).Export("fields-entries").
NewFunctionBuilder().WithFunc(dropOutgoingRequestFn).Export("drop-outgoing-request").
NewFunctionBuilder().WithFunc(outgoingRequestWriteFn).Export("outgoing-request-write").
NewFunctionBuilder().WithFunc(dropIncomingResponseFn).Export("drop-incoming-response").
NewFunctionBuilder().WithFunc(incomingResponseStatusFn).Export("incoming-response-status").
NewFunctionBuilder().WithFunc(incomingResponseHeadersFn).Export("incoming-response-headers").
NewFunctionBuilder().WithFunc(incomingResponseConsumeFn).Export("incoming-response-consume").
NewFunctionBuilder().WithFunc(futureResponseGetFn).Export("future-incoming-response-get").
Instantiate(ctx)
return err
}
136 changes: 136 additions & 0 deletions imports/wasi_http/types/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package types

import (
"bytes"
"context"
"fmt"
"io"
"log"
"net/http"

"github.com/tetratelabs/wazero/api"
)

type Request struct {
Method string
Path string
Query string
Scheme string
Authority string
Body io.Reader
}

func (r Request) Url() string {
return fmt.Sprintf("%s://%s%s%s", r.Scheme, r.Authority, r.Path, r.Query)
}

type requests struct {
requests map[uint32]*Request
request_id_base uint32
}

var r = &requests{make(map[uint32]*Request), 1}

func (r *requests) newRequest() (*Request, uint32) {
request := &Request{}
r.request_id_base++
r.requests[r.request_id_base] = request
return request, r.request_id_base
}

func (r *requests) deleteRequest(handle uint32) {
delete(r.requests, handle)
}

func GetRequest(handle uint32) (*Request, bool) {
r, ok := r.requests[handle]
return r, ok
}

func (request *Request) MakeRequest() (*http.Response, error) {
r, err := http.NewRequest(request.Method, request.Url(), request.Body)
if err != nil {
return nil, err
}

return http.DefaultClient.Do(r)
}

func (request *Request) RequestBody(body []byte) {
request.Body = bytes.NewBuffer(body)
}

func newOutgoingRequestFn(_ context.Context, mod api.Module,
method, method_ptr, method_len,
path_ptr, path_len,
query_ptr, query_len,
scheme_is_some, scheme, scheme_ptr, scheme_len,
authority_ptr, authority_len, header_handle uint32) uint32 {

request, id := r.newRequest()

switch method {
case 0:
request.Method = "GET"
case 1:
request.Method = "HEAD"
case 2:
request.Method = "POST"
case 3:
request.Method = "PUT"
case 4:
request.Method = "DELETE"
case 5:
request.Method = "CONNECT"
case 6:
request.Method = "OPTIONS"
case 7:
request.Method = "TRACE"
case 8:
request.Method = "PATCH"
default:
log.Fatalf("Unknown method: %d", method)
}

path, ok := mod.Memory().Read(uint32(path_ptr), uint32(path_len))
if !ok {
return 0
}
request.Path = string(path)

query, ok := mod.Memory().Read(uint32(query_ptr), uint32(query_len))
if !ok {
return 0
}
request.Query = string(query)

request.Scheme = "https"
if scheme_is_some == 1 {
if scheme == 0 {
request.Scheme = "https"
}
if scheme == 2 {
d, ok := mod.Memory().Read(uint32(scheme_ptr), uint32(scheme_len))
if !ok {
return 0
}
request.Scheme = string(d)
}
}

authority, ok := mod.Memory().Read(uint32(authority_ptr), uint32(authority_len))
if !ok {
return 0
}
request.Authority = string(authority)

return id
}

func dropOutgoingRequestFn(_ context.Context, mod api.Module, handle uint32) {
r.deleteRequest(handle)
}

func outgoingRequestWriteFn(_ context.Context, mod api.Module, handle, ptr uint32) {

}
55 changes: 55 additions & 0 deletions imports/wasi_http/types/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package types

import (
"context"
"encoding/binary"
"net/http"

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

var le = binary.LittleEndian

var response *http.Response

func SetResponse(r *http.Response) {
response = r
}

func ResponseHeaders() http.Header {
return response.Header
}

func dropIncomingResponseFn(_ context.Context, mod api.Module, handle uint32) {
// pass
}

func incomingResponseStatusFn(_ context.Context, mod api.Module, handle uint32) int32 {
return int32(response.StatusCode)
}

func incomingResponseHeadersFn(_ context.Context, mod api.Module, handle uint32) int32 {
return 1
}

func incomingResponseConsumeFn(_ context.Context, mod api.Module, handle, ptr uint32) {
stream := wasi_streams.Streams.NewInputStream(response.Body)
data := []byte{}
// 0 == ok, 1 == is_err
data = le.AppendUint32(data, 0)
// This is the stream number
data = le.AppendUint32(data, stream)
mod.Memory().Write(ptr, data)
}

func futureResponseGetFn(_ context.Context, mod api.Module, handle, ptr uint32) {
data := []byte{}
// 1 == is_some, 0 == none
data = le.AppendUint32(data, 1)
// 0 == ok, 1 == is_err, consistency ftw!
data = le.AppendUint32(data, 0)
// Copy the future into the actual
data = le.AppendUint32(data, handle)
mod.Memory().Write(ptr, data)
}
Loading