Skip to content

Commit

Permalink
Address comments, add a simple test.
Browse files Browse the repository at this point in the history
  • Loading branch information
brendandburns committed Jun 22, 2023
1 parent 2096ae2 commit d844ef5
Show file tree
Hide file tree
Showing 13 changed files with 1,983 additions and 11 deletions.
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ wasirun.src = $(wasi-go.src)
testdata.c.src = $(wildcard testdata/c/*.c)
testdata.c.wasm = $(testdata.c.src:.c=.wasm)

testdata.http.src = $(wildcard testdata/c/http/http*.c)
testdata.http.wasm = $(testdata.http.src:.c=.wasm)

testdata.go.src = $(wildcard testdata/go/*.go)
testdata.go.wasm = $(testdata.go.src:.go=.wasm)

Expand All @@ -18,6 +21,7 @@ testdata.tinygo.wasm = $(testdata.tinygo.src:.go=.wasm)

testdata.files = \
$(testdata.c.wasm) \
$(testdata.http.wasm) \
$(testdata.go.wasm) \
$(testdata.tinygo.wasm)

Expand Down Expand Up @@ -49,7 +53,10 @@ testdata/.sysroot/lib/wasm32-wasi/libc.a: testdata/.wasi-libc

testdata/c/%.c: wasi-libc
testdata/c/%.wasm: testdata/c/%.c
clang $< -o $@ -Wall -Os -target wasm32-unknown-wasi --sysroot testdata/.sysroot
clang $< -o $@ -Wall -Os -target wasm32-unknown-wasi

testdata/c/http/http.wasm: testdata/c/http/http.c
clang $< -o $@ -Wall -Os -target wasm32-unknown-wasi testdata/c/http/proxy.c testdata/c/http/proxy_component_type.o

testdata/go/%.wasm: testdata/go/%.go
GOARCH=wasm GOOS=wasip1 gotip build -o $@ $<
Expand Down
22 changes: 17 additions & 5 deletions cmd/wasirun/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ OPTIONS:
--non-blocking-stdio
Enable non-blocking stdio
--wasi-http
Enable wasi-http client support
--http <MODE>
Optionally enable wasi-http client support and select a
version {none, auto, v1}
-v, --version
Print the version and exit
Expand All @@ -74,10 +75,10 @@ var (
dials stringList
socketExt string
pprofAddr string
wasiHttp string
trace bool
nonBlockingStdio bool
version bool
wasiHttp bool
)

func main() {
Expand All @@ -90,11 +91,11 @@ func main() {
flagSet.Var(&dials, "dial", "")
flagSet.StringVar(&socketExt, "sockets", "auto", "")
flagSet.StringVar(&pprofAddr, "pprof-addr", "", "")
flagSet.StringVar(&wasiHttp, "http", "auto", "")
flagSet.BoolVar(&trace, "trace", false, "")
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 @@ -164,7 +165,18 @@ func run(wasmFile string, args []string) error {
}
defer system.Close(ctx)

if wasiHttp {
importWasi := false
switch wasiHttp {
case "auto":
importWasi = wasi_http.DetectWasiHttp(wasmModule)
case "v1":
importWasi = true
case "none":
importWasi = false
default:
return fmt.Errorf("invalid value for -http '%v', expected 'auto', 'v1' or 'none'", wasiHttp)
}
if importWasi {
if err := wasi_http.Instantiate(ctx, runtime); err != nil {
return err
}
Expand Down
16 changes: 16 additions & 0 deletions imports/wasi_http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,19 @@ func Instantiate(ctx context.Context, rt wazero.Runtime) error {
}
return nil
}

func DetectWasiHttp(module wazero.CompiledModule) bool {
functions := module.ImportedFunctions()
hasWasiHttp := false
for _, f := range functions {
moduleName, name, ok := f.Import()
if !ok || moduleName != default_http.ModuleName {
continue
}
switch name {
case "handle":
hasWasiHttp = true
}
}
return hasWasiHttp
}
112 changes: 112 additions & 0 deletions imports/wasi_http/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package wasi_http

import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"testing"

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

type handler struct {
urls []string
}

func (h *handler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(200)
res.Write([]byte("Response"))

h.urls = append(h.urls, req.URL.String())
}

func TestHttp(t *testing.T) {
filePaths, _ := filepath.Glob("../../testdata/c/http/*.wasm")
for _, file := range filePaths {
fmt.Printf("%v\n", file)
}
if len(filePaths) == 0 {
t.Log("nothing to test")
}

h := handler{}
s := &http.Server{
Addr: "127.0.0.1:8080",
Handler: &h,
}
go s.ListenAndServe()
defer s.Shutdown(context.TODO())

expectedPaths := [][]string{
[]string{"/get?some=arg&goes=here"},
}

for testIx, test := range filePaths {
name := test
for strings.HasPrefix(name, "../") {
name = name[3:]
}

t.Run(name, func(t *testing.T) {
bytecode, err := os.ReadFile(test)
if err != nil {
t.Fatal(err)
}

ctx := context.Background()

runtime := wazero.NewRuntime(ctx)
defer runtime.Close(ctx)

builder := imports.NewBuilder().
WithName("http").
WithArgs()
var system wasi.System
ctx, system, err = builder.Instantiate(ctx, runtime)
if err != nil {
t.Error("Failed to build WASI module: ", err)
}
defer system.Close(ctx)

Instantiate(ctx, runtime)

instance, err := runtime.Instantiate(ctx, bytecode)
if err != nil {
switch e := err.(type) {
case *sys.ExitError:
if exitCode := e.ExitCode(); exitCode != 0 {
t.Error("exit code:", exitCode)
}
default:
t.Error("instantiating wasm module instance:", err)
}
}
if instance != nil {
if err := instance.Close(ctx); err != nil {
t.Error("closing wasm module instance:", err)
}
}
ok := true
if len(h.urls) != len(expectedPaths[testIx]) {
ok = false
} else {
for ix := range h.urls {
if h.urls[ix] != expectedPaths[testIx][ix] {
ok = false
break
}
}
}
if !ok {
t.Errorf("Unexpected paths: %v vs %v", h.urls, expectedPaths[testIx])
}
h.urls = []string{}
})
}
}
8 changes: 4 additions & 4 deletions imports/wasi_http/streams/streams.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ func (s *streams) NewOutputStream(writer io.Writer) uint32 {
func (s *streams) Read(handle uint32, data []byte) (int, bool, error) {
stream, found := s.streams[handle]
if !found {
return 0, false, fmt.Errorf("Stream not found", handle)
return 0, false, fmt.Errorf("stream not found: %d", handle)
}
if stream.reader == nil {
return 0, false, fmt.Errorf("Not a readable stream")
return 0, false, fmt.Errorf("not a readable stream: %d", handle)
}

n, err := stream.reader.Read(data)
Expand All @@ -75,10 +75,10 @@ func (s *streams) Read(handle uint32, data []byte) (int, bool, error) {
func (s *streams) Write(handle uint32, data []byte) (int, error) {
stream, found := s.streams[handle]
if !found {
return 0, fmt.Errorf("Stream not found", handle)
return 0, fmt.Errorf("stream not found: %d", handle)
}
if stream.writer == nil {
return 0, fmt.Errorf("Not a writeable stream")
return 0, fmt.Errorf("not a writeable stream: %d", handle)
}
return stream.writer.Write(data)
}
2 changes: 1 addition & 1 deletion imports/wasi_http/types/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func newOutgoingRequestFn(_ context.Context, mod api.Module,
request.Scheme = "https"
if scheme_is_some == 1 {
if scheme == 0 {
request.Scheme = "https"
request.Scheme = "http"
}
if scheme == 2 {
d, ok := mod.Memory().Read(uint32(scheme_ptr), uint32(scheme_len))
Expand Down
Binary file modified testdata/c/hello_world.wasm
Binary file not shown.
4 changes: 4 additions & 0 deletions testdata/c/http/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# HTTP WASI tests
The proxy* files are created using the [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen) tool.

See [here](https://github.com/dev-wasm/dev-wasm-c/blob/main/http/Makefile#L9) for details about how to regenerate.
117 changes: 117 additions & 0 deletions testdata/c/http/http.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#include "proxy.h"
#include <stdio.h>

void http_handle(uint32_t arg, uint32_t arg0) {

}

int request(uint8_t method_tag, uint8_t scheme_tag, const char * authority_str, const char* path_str, const char* query_str, const char* body) {
types_tuple2_string_string_t content_type[] = {{
.f0 = { .ptr = "User-agent", .len = 10 },
.f1 = { .ptr = "WASI-HTTP/0.0.1", .len = 15},
},
{
.f0 = { .ptr = "Content-type", .len = 12 },
.f1 = { .ptr = "application/json", .len = 16},
}};
types_list_tuple2_string_string_t headers_list = {
.ptr = &content_type[0],
.len = 2,
};
types_fields_t headers = types_new_fields(&headers_list);
types_method_t method = { .tag = method_tag };
types_scheme_t scheme = { .tag = scheme_tag };
proxy_string_t path, authority, query;
proxy_string_set(&path, path_str);
proxy_string_set(&authority, authority_str);
proxy_string_set(&query, query_str);

default_outgoing_http_outgoing_request_t req = types_new_outgoing_request(&method, &path, &query, &scheme, &authority, headers);
default_outgoing_http_future_incoming_response_t res;

if (req == 0) {
printf("Error creating request\n");
return 4;
}
if (body != NULL) {
types_outgoing_stream_t ret;
if (!types_outgoing_request_write(req, &ret)) {
printf("Error getting output stream\n");
return 7;
}
streams_list_u8_t buf = {
.ptr = (uint8_t *) body,
.len = strlen(body),
};
uint64_t ret_val;
streams_write(ret, &buf, &ret_val, NULL);
}

res = default_outgoing_http_handle(req, NULL);
if (res == 0) {
printf("Error sending request\n");
return 5;
}

types_result_incoming_response_error_t result;
if (!types_future_incoming_response_get(res, &result)) {
printf("failed to get value for incoming request\n");
return 1;
}

if (result.is_err) {
printf("response is error!\n");
return 2;
}
// poll_drop_pollable(res);

types_status_code_t code = types_incoming_response_status(result.val.ok);
printf("STATUS: %d\n", code);

types_headers_t header_handle = types_incoming_response_headers(result.val.ok);
types_list_tuple2_string_string_t header_list;
types_fields_entries(header_handle, &header_list);

for (int i = 0; i < header_list.len; i++) {
char name[128];
char value[128];
strncpy(name, header_list.ptr[i].f0.ptr, header_list.ptr[i].f0.len);
name[header_list.ptr[i].f0.len] = 0;
strncpy(value, header_list.ptr[i].f1.ptr, header_list.ptr[i].f1.len);
value[header_list.ptr[i].f1.len] = 0;
printf("%s: %s\n", name, value);
}


types_incoming_stream_t stream;
if (!types_incoming_response_consume(result.val.ok, &stream)) {
printf("stream is error!\n");
return 3;
}

printf("Stream is %d\n", stream);

int32_t len = 64 * 1024;
streams_tuple2_list_u8_bool_t body_res;
streams_stream_error_t err;
if (!streams_read(stream, len, &body_res, &err)) {
printf("BODY read is error!\n");
return 6;
}
printf("data from read: %s\n", body_res.f0.ptr);
streams_tuple2_list_u8_bool_free(&body_res);


types_drop_outgoing_request(req);
streams_drop_input_stream(stream);
types_drop_incoming_response(result.val.ok);

return 0;
}

int main() {
request(TYPES_METHOD_GET, TYPES_SCHEME_HTTP, "localhost:8080", "/get", "?some=arg&goes=here", NULL);
// request(TYPES_METHOD_POST, TYPES_SCHEME_HTTPS, "postman-echo.com", "/post", "", "{\"foo\": \"bar\"}");
// request(TYPES_METHOD_PUT, TYPES_SCHEME_HTTP, "postman-echo.com", "/put", "", NULL);
return 0;
}
Binary file added testdata/c/http/http.wasm
Binary file not shown.
Loading

0 comments on commit d844ef5

Please sign in to comment.