diff --git a/api/handler/wasm.go b/api/handler/wasm.go index 48c2e48..ce73569 100644 --- a/api/handler/wasm.go +++ b/api/handler/wasm.go @@ -300,6 +300,9 @@ const ( // TODO: document on http-wasm-abi FuncWriteBody = "write_body" + // FuncAwaitResponse TODO + FuncAwaitResponse = "await_response" + // FuncGetStatusCode returns the status code produced by FuncNext. This // requires FeatureBufferResponse. // diff --git a/examples/log_once.wasm b/examples/log_once.wasm new file mode 100644 index 0000000..038acb5 Binary files /dev/null and b/examples/log_once.wasm differ diff --git a/examples/log_once.wat b/examples/log_once.wat new file mode 100644 index 0000000..11ce026 --- /dev/null +++ b/examples/log_once.wat @@ -0,0 +1,73 @@ +;; This example module is written in WebAssembly Text Format to show the +;; how a handler works when the guest compiler doesn't support function +;; exports, such as GOOS=wasip1 in Go 1.21. +(module $log_once + + ;; log_enabled returns 1 if the $level is enabled. This value may be cached + ;; at request granularity. + (import "http_handler" "log_enabled" (func $log_enabled + (param $level i32) + (result (; 0 or enabled(1) ;) i32))) + + ;; logs a message to the host's logs at the given $level. + (import "http_handler" "log" (func $log + (param $level i32) + (param $buf i32) (param $buf_limit i32))) + +(; begin adapter logic + + Below is generic and can convert any normal handler to a single synchronous + call, provided $handle_request and $handle_response are not exported. ;) + + ;; import $await_response, which blocks until the response is ready. + (import "http_handler" "await_response" (func $await_response + (param $ctx_next i64) + (result (; is_error ;) i32))) + + ;; define a start function that performs a request-response without exports. + ;; note: this logic is generic and can convert any exported $handle_request/ + ;; $handle_response pair to a synchronous call without exports. + (func $start + (local $ctx_next i64) + (local $is_error i32) + (local $ctx i32) + + ;; ctxNext := handleRequest() + (local.set $ctx_next (call $handle_request)) + + ;; isError := awaitResponse(ctxNext) + (local.set $is_error (call $await_response (local.get $ctx_next))) + + ;; ctx := uint32(ctxNext >> 32) + (local.set $ctx + (i32.wrap_i64 (i64.shr_u (local.get $ctx_next) (i64.const 32)))) + + ;; handleResponse(ctx, isError) + (call $handle_response (local.get $ctx) (local.get $is_error)) + ) + +(; end adapter logic ;) + + (memory (export "memory") 1 1 (; 1 page==64KB ;)) + (global $message i32 (i32.const 0)) + (data (i32.const 0) "hello world") + (global $message_len i32 (i32.const 11)) + + (func $handle_request (result (; ctx_next ;) i64) + ;; We expect debug logging to be disabled. Panic otherwise! + (if (i32.eq + (call $log_enabled (i32.const -1)) ;; log_level_debug + (i32.const 1)) ;; true + (then unreachable)) + + (call $log + (i32.const 0) ;; log_level_info + (global.get $message) + (global.get $message_len)) + + ;; uint32(ctx_next) == 1 means proceed to the next handler on the host. + (return (i64.const 1))) + + ;; handle_response is no-op as this is a request-only handler. + (func $handle_response (param $reqCtx i32) (param $is_error i32)) +) diff --git a/handler/middleware.go b/handler/middleware.go index cfdcd37..0abeaa3 100644 --- a/handler/middleware.go +++ b/handler/middleware.go @@ -7,7 +7,6 @@ import ( "io" "strconv" "strings" - "sync" "sync/atomic" "github.com/tetratelabs/wazero" @@ -43,9 +42,9 @@ type Middleware interface { Features() handler.Features api.Closer -} -var _ Middleware = (*middleware)(nil) + instantiateHost(context.Context) error +} type middleware struct { host handler.Host @@ -54,7 +53,6 @@ type middleware struct { moduleConfig wazero.ModuleConfig guestConfig []byte logger api.Logger - pool sync.Pool features handler.Features instanceCounter uint64 } @@ -78,109 +76,103 @@ func NewMiddleware(ctx context.Context, guest []byte, host handler.Host, opts .. return nil, fmt.Errorf("wasm: error creating middleware: %w", err) } - m := &middleware{ - host: host, - runtime: wr, - moduleConfig: o.moduleConfig, - guestConfig: o.guestConfig, - logger: o.logger, + guestModule, err := wr.CompileModule(ctx, guest) + if err != nil { + _ = wr.Close(ctx) + return nil, fmt.Errorf("wasm: error compiling guest: %w", err) } - if m.guestModule, err = m.compileGuest(ctx, guest); err != nil { + once, err := validateGuest(guestModule) + if err != nil { _ = wr.Close(ctx) return nil, err } - // Detect and handle any host imports or lack thereof. - imports := detectImports(m.guestModule.ImportedFunctions()) - switch { - case imports&importWasiP1 != 0: - if _, err = wasi_snapshot_preview1.Instantiate(ctx, m.runtime); err != nil { + // Detect if wasip1 is used and import it. + imports := detectImports(guestModule.ImportedFunctions()) + if imports&importWasiP1 != 0 { + if _, err = wasi_snapshot_preview1.Instantiate(ctx, wr); err != nil { _ = wr.Close(ctx) - return nil, fmt.Errorf("wasm: error instantiating wasi: %w", err) - } - fallthrough // proceed to configure any http_handler imports - case imports&importHttpHandler != 0: - if _, err = m.instantiateHost(ctx); err != nil { - _ = wr.Close(ctx) - return nil, fmt.Errorf("wasm: error instantiating host: %w", err) + return nil, fmt.Errorf("wasm: error instantiating wasip1: %w", err) } } - // Eagerly add one instance to the pool. Doing so helps to fail fast. - if g, err := m.newGuest(ctx); err != nil { + m := middleware{ + host: host, + runtime: wr, + guestModule: guestModule, + moduleConfig: o.moduleConfig, + guestConfig: o.guestConfig, + logger: o.logger, + } + + var ret Middleware + if once { + ret = &onceMiddleware{middleware: m} + } else { + ret = &poolMiddleware{middleware: m} + } + + if err = ret.instantiateHost(ctx); err != nil { _ = wr.Close(ctx) return nil, err - } else { - m.pool.Put(g) } - return m, nil + return ret, nil } -func (m *middleware) compileGuest(ctx context.Context, wasm []byte) (wazero.CompiledModule, error) { - if guest, err := m.runtime.CompileModule(ctx, wasm); err != nil { - return nil, fmt.Errorf("wasm: error compiling guest: %w", err) - } else if handleRequest, ok := guest.ExportedFunctions()[handler.FuncHandleRequest]; !ok { - return nil, fmt.Errorf("wasm: guest doesn't export func[%s]", handler.FuncHandleRequest) - } else if len(handleRequest.ParamTypes()) != 0 || !bytes.Equal(handleRequest.ResultTypes(), []wazeroapi.ValueType{wazeroapi.ValueTypeI64}) { - return nil, fmt.Errorf("wasm: guest exports the wrong signature for func[%s]. should be () -> (i64)", handler.FuncHandleRequest) - } else if handleResponse, ok := guest.ExportedFunctions()[handler.FuncHandleResponse]; !ok { - return nil, fmt.Errorf("wasm: guest doesn't export func[%s]", handler.FuncHandleResponse) - } else if !bytes.Equal(handleResponse.ParamTypes(), []wazeroapi.ValueType{wazeroapi.ValueTypeI32, wazeroapi.ValueTypeI32}) || len(handleResponse.ResultTypes()) != 0 { - return nil, fmt.Errorf("wasm: guest exports the wrong signature for func[%s]. should be (i32, 32) -> ()", handler.FuncHandleResponse) - } else if _, ok = guest.ExportedMemories()[api.Memory]; !ok { - return nil, fmt.Errorf("wasm: guest doesn't export memory[%s]", api.Memory) - } else { - return guest, nil +func validateGuest(guest wazero.CompiledModule) (once bool, err error) { + if _, ok := guest.ExportedMemories()[api.Memory]; !ok { + return false, fmt.Errorf("wasm: guest doesn't export memory[%s]", api.Memory) } -} -// HandleRequest implements Middleware.HandleRequest -func (m *middleware) HandleRequest(ctx context.Context) (outCtx context.Context, ctxNext handler.CtxNext, err error) { - g, guestErr := m.getOrCreateGuest(ctx) - if guestErr != nil { - err = guestErr - return - } + var awaitResponse, handleRequest, handleResponse wazeroapi.FunctionDefinition - s := &requestState{features: m.features, putPool: m.pool.Put, g: g} - defer func() { - if ctxNext != 0 { // will call the next handler - if closeErr := s.closeRequest(); err == nil { - err = closeErr - } - } else { // guest errored or returned the response - if closeErr := s.Close(); err == nil { - err = closeErr + // Look through the imports to see if the guest uses await_response + for _, f := range guest.ImportedFunctions() { + moduleName, name, _ := f.Import() + if moduleName == handler.HostModule && name == handler.FuncAwaitResponse { + awaitResponse = f + if !bytes.Equal(awaitResponse.ParamTypes(), []wazeroapi.ValueType{wazeroapi.ValueTypeI64}) || !bytes.Equal(awaitResponse.ResultTypes(), []wazeroapi.ValueType{wazeroapi.ValueTypeI32}) { + return false, fmt.Errorf("wasm: guest imports the wrong signature for func[%s]. should be (i64) -> (i32)", handler.FuncAwaitResponse) + } else { + once = true + break } } - }() + } - outCtx = context.WithValue(ctx, requestStateKey{}, s) - ctxNext, err = g.handleRequest(outCtx) - return -} + // Look through the exports to see if the guest exports handle_request or handle_response + if handleRequest = guest.ExportedFunctions()[handler.FuncHandleRequest]; handleRequest == nil { + } else if len(handleRequest.ParamTypes()) != 0 || !bytes.Equal(handleRequest.ResultTypes(), []wazeroapi.ValueType{wazeroapi.ValueTypeI64}) { + return false, fmt.Errorf("wasm: guest exports the wrong signature for func[%s]. should be () -> (i64)", handler.FuncHandleRequest) + } -func (m *middleware) getOrCreateGuest(ctx context.Context) (*guest, error) { - poolG := m.pool.Get() - if poolG == nil { - if g, createErr := m.newGuest(ctx); createErr != nil { - return nil, createErr - } else { - poolG = g + if handleResponse = guest.ExportedFunctions()[handler.FuncHandleResponse]; handleResponse == nil { + } else if !bytes.Equal(handleResponse.ParamTypes(), []wazeroapi.ValueType{wazeroapi.ValueTypeI32, wazeroapi.ValueTypeI32}) || len(handleResponse.ResultTypes()) != 0 { + return false, fmt.Errorf("wasm: guest exports the wrong signature for func[%s]. should be (i32, 32) -> ()", handler.FuncHandleResponse) + } + + switch { + case awaitResponse == nil && handleRequest == nil && handleResponse == nil: + return false, fmt.Errorf("wasm: guest should import[%s] or both func[%s] and func[%s] ", + handler.FuncAwaitResponse, handler.FuncHandleRequest, handler.FuncHandleResponse) + case awaitResponse != nil: + if handleRequest != nil || handleResponse != nil { + return false, fmt.Errorf("wasm: guest that imports[%s] shouldn't export func[%s] or func[%s] ", + handler.FuncAwaitResponse, handler.FuncHandleRequest, handler.FuncHandleResponse) } + case handleRequest == nil: + return false, fmt.Errorf("wasm: guest doesn't export func[%s]", handler.FuncHandleRequest) + case handleResponse == nil: + return false, fmt.Errorf("wasm: guest doesn't export func[%s]", handler.FuncHandleResponse) } - return poolG.(*guest), nil + return } -// HandleResponse implements Middleware.HandleResponse -func (m *middleware) HandleResponse(ctx context.Context, reqCtx uint32, hostErr error) error { - s := requestStateFromContext(ctx) - defer s.Close() - s.afterNext = true - - return s.g.handleResponse(ctx, reqCtx, hostErr) +func (m *middleware) requestStateFromContext(ctx context.Context) *requestState { + s, _ := ctx.Value(requestStateKey{}).(*requestState) + return s } // Close implements api.Closer @@ -189,13 +181,7 @@ func (m *middleware) Close(ctx context.Context) error { return m.runtime.Close(ctx) } -type guest struct { - guest wazeroapi.Module - handleRequestFn wazeroapi.Function - handleResponseFn wazeroapi.Function -} - -func (m *middleware) newGuest(ctx context.Context) (*guest, error) { +func (m *middleware) newModule(ctx context.Context) (wazeroapi.Module, error) { moduleName := fmt.Sprintf("%d", atomic.AddUint64(&m.instanceCounter, 1)) g, err := m.runtime.InstantiateModule(ctx, m.guestModule, m.moduleConfig.WithName(moduleName)) @@ -203,32 +189,7 @@ func (m *middleware) newGuest(ctx context.Context) (*guest, error) { _ = m.runtime.Close(ctx) return nil, fmt.Errorf("wasm: error instantiating guest: %w", err) } - - return &guest{ - guest: g, - handleRequestFn: g.ExportedFunction(handler.FuncHandleRequest), - handleResponseFn: g.ExportedFunction(handler.FuncHandleResponse), - }, nil -} - -// handleRequest calls the WebAssembly guest function handler.FuncHandleRequest. -func (g *guest) handleRequest(ctx context.Context) (ctxNext handler.CtxNext, err error) { - if results, guestErr := g.handleRequestFn.Call(ctx); guestErr != nil { - err = guestErr - } else { - ctxNext = handler.CtxNext(results[0]) - } - return -} - -// handleResponse calls the WebAssembly guest function handler.FuncHandleResponse. -func (g *guest) handleResponse(ctx context.Context, reqCtx uint32, err error) error { - wasError := uint64(0) - if err != nil { - wasError = 1 - } - _, err = g.handleResponseFn.Call(ctx, uint64(reqCtx), wasError) - return err + return g, nil } // enableFeatures implements the WebAssembly host function handler.FuncEnableFeatures. @@ -236,7 +197,7 @@ func (m *middleware) enableFeatures(ctx context.Context, stack []uint64) { features := handler.Features(stack[0]) var enabled handler.Features - if s, ok := ctx.Value(requestStateKey{}).(*requestState); ok { + if s := m.requestStateFromContext(ctx); s != nil { s.features = m.host.EnableFeatures(ctx, s.features.WithEnabled(features)) enabled = s.features } else { @@ -299,7 +260,7 @@ func (m *middleware) setMethod(ctx context.Context, mod wazeroapi.Module, params method := uint32(params[0]) methodLen := uint32(params[1]) - _ = mustBeforeNext(ctx, "set", "method") + mustBeforeNext(m.requestStateFromContext(ctx), "set", "method") var p string if methodLen == 0 { @@ -325,7 +286,7 @@ func (m *middleware) setURI(ctx context.Context, mod wazeroapi.Module, params [] uri := uint32(params[0]) uriLen := uint32(params[1]) - _ = mustBeforeNext(ctx, "set", "uri") + mustBeforeNext(m.requestStateFromContext(ctx), "set", "uri") var p string if uriLen > 0 { // overwrite with empty is supported @@ -426,7 +387,7 @@ func (m *middleware) setHeaderValue(ctx context.Context, mod wazeroapi.Module, p if nameLen == 0 { panic("HTTP header name cannot be empty") } - mustHeaderMutable(ctx, "set", kind) + mustHeaderMutable(m.requestStateFromContext(ctx), "set", kind) n := mustReadString(mod.Memory(), "name", name, nameLen) v := mustReadString(mod.Memory(), "value", value, valueLen) @@ -456,7 +417,8 @@ func (m *middleware) addHeaderValue(ctx context.Context, mod wazeroapi.Module, p if nameLen == 0 { panic("HTTP header name cannot be empty") } - mustHeaderMutable(ctx, "add", kind) + + mustHeaderMutable(m.requestStateFromContext(ctx), "add", kind) n := mustReadString(mod.Memory(), "name", name, nameLen) v := mustReadString(mod.Memory(), "value", value, valueLen) @@ -484,7 +446,7 @@ func (m *middleware) removeHeader(ctx context.Context, mod wazeroapi.Module, par if nameLen == 0 { panic("HTTP header name cannot be empty") } - mustHeaderMutable(ctx, "remove", kind) + mustHeaderMutable(m.requestStateFromContext(ctx), "remove", kind) n := mustReadString(mod.Memory(), "name", name, nameLen) switch kind { @@ -507,10 +469,11 @@ func (m *middleware) readBody(ctx context.Context, mod wazeroapi.Module, stack [ buf := uint32(stack[1]) bufLimit := handler.BufLimit(stack[2]) + s := m.requestStateFromContext(ctx) var r io.ReadCloser switch kind { case handler.BodyKindRequest: - s := mustBeforeNextOrFeature(ctx, handler.FeatureBufferRequest, "read", "request body") + mustBeforeNextOrFeature(s, handler.FeatureBufferRequest, "read", "request body") // Lazy create the reader. r = s.requestBodyReader if r == nil { @@ -518,7 +481,7 @@ func (m *middleware) readBody(ctx context.Context, mod wazeroapi.Module, stack [ s.requestBodyReader = r } case handler.BodyKindResponse: - s := mustBeforeNextOrFeature(ctx, handler.FeatureBufferResponse, "read", "response body") + mustBeforeNextOrFeature(s, handler.FeatureBufferResponse, "read", "response body") // Lazy create the reader. r = s.responseBodyReader if r == nil { @@ -540,10 +503,11 @@ func (m *middleware) writeBody(ctx context.Context, mod wazeroapi.Module, params buf := uint32(params[1]) bufLen := uint32(params[2]) + s := m.requestStateFromContext(ctx) var w io.Writer switch kind { case handler.BodyKindRequest: - s := mustBeforeNext(ctx, "write", "request body") + mustBeforeNext(s, "write", "request body") // Lazy create the writer. w = s.requestBodyWriter if w == nil { @@ -551,7 +515,7 @@ func (m *middleware) writeBody(ctx context.Context, mod wazeroapi.Module, params s.requestBodyWriter = w } case handler.BodyKindResponse: - s := mustBeforeNextOrFeature(ctx, handler.FeatureBufferResponse, "write", "response body") + mustBeforeNextOrFeature(s, handler.FeatureBufferResponse, "write", "response body") // Lazy create the writer. w = s.responseBodyWriter if w == nil { @@ -589,7 +553,8 @@ func (m *middleware) getStatusCode(ctx context.Context, results []uint64) { func (m *middleware) setStatusCode(ctx context.Context, params []uint64) { statusCode := uint32(params[0]) - _ = mustBeforeNextOrFeature(ctx, handler.FeatureBufferResponse, "set", "status code") + s := m.requestStateFromContext(ctx) + mustBeforeNextOrFeature(s, handler.FeatureBufferResponse, "set", "status code") m.host.SetStatusCode(ctx, statusCode) } @@ -622,15 +587,14 @@ func readBody(mod wazeroapi.Module, buf uint32, bufLimit handler.BufLimit, r io. } } -func mustBeforeNext(ctx context.Context, op, kind string) (s *requestState) { - if s = requestStateFromContext(ctx); s.afterNext { +func mustBeforeNext(s *requestState, op, kind string) { + if s.afterNext { panic(fmt.Errorf("can't %s %s after next handler", op, kind)) } - return } -func mustBeforeNextOrFeature(ctx context.Context, feature handler.Features, op, kind string) (s *requestState) { - if s = requestStateFromContext(ctx); !s.afterNext { +func mustBeforeNextOrFeature(s *requestState, feature handler.Features, op, kind string) { + if !s.afterNext { // Assume this is serving a response from the guest. } else if s.features.IsEnabled(feature) { // Assume the guest is overwriting the response from next. @@ -638,12 +602,11 @@ func mustBeforeNextOrFeature(ctx context.Context, feature handler.Features, op, panic(fmt.Errorf("can't %s %s after next handler unless %s is enabled", op, kind, feature)) } - return } const i32, i64 = wazeroapi.ValueTypeI32, wazeroapi.ValueTypeI64 -func (m *middleware) instantiateHost(ctx context.Context) (wazeroapi.Module, error) { +func (m *middleware) hostModuleBuilder() wazero.HostModuleBuilder { return m.runtime.NewHostModuleBuilder(handler.HostModule). NewFunctionBuilder(). WithGoFunction(wazeroapi.GoFunc(m.enableFeatures), []wazeroapi.ValueType{i32}, []wazeroapi.ValueType{i32}). @@ -698,20 +661,19 @@ func (m *middleware) instantiateHost(ctx context.Context) (wazeroapi.Module, err WithParameterNames().Export(handler.FuncGetStatusCode). NewFunctionBuilder(). WithGoFunction(wazeroapi.GoFunc(m.setStatusCode), []wazeroapi.ValueType{i32}, []wazeroapi.ValueType{}). - WithParameterNames("status_code").Export(handler.FuncSetStatusCode). - Instantiate(ctx) + WithParameterNames("status_code").Export(handler.FuncSetStatusCode) } -func mustHeaderMutable(ctx context.Context, op string, kind handler.HeaderKind) { +func mustHeaderMutable(s *requestState, op string, kind handler.HeaderKind) { switch kind { case handler.HeaderKindRequest: - _ = mustBeforeNext(ctx, op, "request header") + mustBeforeNext(s, op, "request header") case handler.HeaderKindRequestTrailers: - _ = mustBeforeNext(ctx, op, "request trailer") + mustBeforeNext(s, op, "request trailer") case handler.HeaderKindResponse: - _ = mustBeforeNextOrFeature(ctx, handler.FeatureBufferResponse, op, "response header") + mustBeforeNextOrFeature(s, handler.FeatureBufferResponse, op, "response header") case handler.HeaderKindResponseTrailers: - _ = mustBeforeNextOrFeature(ctx, handler.FeatureBufferResponse, op, "response trailer") + mustBeforeNextOrFeature(s, handler.FeatureBufferResponse, op, "response trailer") default: panic("unsupported header kind: " + strconv.Itoa(int(kind))) } diff --git a/handler/middleware_test.go b/handler/middleware_test.go index 213f90d..f8da68a 100644 --- a/handler/middleware_test.go +++ b/handler/middleware_test.go @@ -163,7 +163,7 @@ func requireGlobals(t *testing.T, mw Middleware, wantGlobals ...uint64) { } func getGlobalVals(mw Middleware) []uint64 { - pool := mw.(*middleware).pool + pool := mw.(*poolMiddleware).pool var guests []*guest var globals []uint64 @@ -193,7 +193,7 @@ func requireHandleRequest(t *testing.T, mw Middleware, ctxNext handler.CtxNext, if want, have := expectedCtx, ctxNext>>32; want != have { t.Errorf("unexpected ctx, want: %d, have: %d", want, have) } - if mw.(*middleware).pool.Get() != nil { + if mw.(*poolMiddleware).pool.Get() != nil { t.Error("expected handler to not return guest to the pool") } } diff --git a/handler/nethttp/benchmark_test.go b/handler/nethttp/benchmark_test.go index 9634451..ace10e5 100644 --- a/handler/nethttp/benchmark_test.go +++ b/handler/nethttp/benchmark_test.go @@ -19,7 +19,7 @@ var ( ) func init() { - noopHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + noopHandler = func(w http.ResponseWriter, r *http.Request) {} smallBody = []byte("hello world") largeSize = 4096 // 2x the read buffer size largeBody = make([]byte, largeSize) @@ -42,11 +42,6 @@ func getWithLargeHeader(url string) (req *http.Request) { return } -func getWithQuery(url string) (req *http.Request) { - req, _ = http.NewRequest(http.MethodGet, url+"/v1.0/hi?name=panda", nil) - return -} - func getWithoutHeaders(url string) (req *http.Request) { req, _ = http.NewRequest(http.MethodGet, url+"/v1.0/hi", nil) req.Header = http.Header{} @@ -109,6 +104,14 @@ var benches = map[string]struct { return }, }, + "example log": { + bin: test.BinExampleLog, + request: get, + }, + "example log once": { + bin: test.BinExampleLogOnce, + request: get, + }, "log": { bin: test.BinBenchLog, request: get, diff --git a/handler/nethttp/middleware_test.go b/handler/nethttp/middleware_test.go index 8ea1b35..4ff8cbd 100644 --- a/handler/nethttp/middleware_test.go +++ b/handler/nethttp/middleware_test.go @@ -218,21 +218,41 @@ func TestHeaderValue(t *testing.T) { // TestHandleResponse uses test.BinE2EHandleResponse which ensures reqCtx // propagates from handler.FuncHandleRequest to handler.FuncHandleResponse. func TestHandleResponse(t *testing.T) { - mw, err := wasm.NewMiddleware(testCtx, test.BinE2EHandleResponse) - if err != nil { - t.Fatal(err) + tests := []struct { + name string + guest []byte + }{ + { + name: "pool", + guest: test.BinE2EHandleResponse, + }, + { + name: "once", + guest: test.BinE2EAwaitResponse, + }, } - defer mw.Close(testCtx) - ts := httptest.NewServer(mw.NewHandler(testCtx, noopHandler)) - defer ts.Close() + for _, tt := range tests { + tc := tt + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + mw, err := wasm.NewMiddleware(testCtx, tc.guest) + if err != nil { + t.Fatal(err) + } + defer mw.Close(testCtx) - resp, err := ts.Client().Get(ts.URL) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != 200 { - t.Fatalf("invalid status code: %d, status message: %s", resp.StatusCode, resp.Status) + ts := httptest.NewServer(mw.NewHandler(testCtx, noopHandler)) + defer ts.Close() + + resp, err := ts.Client().Get(ts.URL) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + t.Fatalf("invalid status code: %d, status message: %s", resp.StatusCode, resp.Status) + } + }) } } diff --git a/handler/once.go b/handler/once.go new file mode 100644 index 0000000..06e2ad1 --- /dev/null +++ b/handler/once.go @@ -0,0 +1,108 @@ +package handler + +import ( + "context" + + wazeroapi "github.com/tetratelabs/wazero/api" + + "github.com/http-wasm/http-wasm-host-go/api/handler" +) + +var _ Middleware = (*onceMiddleware)(nil) + +type onceMiddleware struct { + middleware +} + +type onceRequestStateKey struct{} + +type onceRequestState struct { + requestState + + g wazeroapi.Module + + awaitingResponse chan handler.CtxNext + responseReady, guestDone chan error +} + +func (r *onceRequestState) Close() error { + if g := r.g; g != nil { + _ = g.Close(context.Background()) + } + return r.requestState.Close() +} + +// HandleRequest implements Middleware.HandleRequest +func (m *onceMiddleware) HandleRequest(ctx context.Context) (context.Context, handler.CtxNext, error) { + s := &onceRequestState{ + requestState: requestState{features: m.features}, + awaitingResponse: make(chan handler.CtxNext, 1), + responseReady: make(chan error, 1), + guestDone: make(chan error, 1), + } + ctx = context.WithValue(ctx, requestStateKey{}, &s.requestState) + ctx = context.WithValue(ctx, onceRequestStateKey{}, s) + + // instantiate the module in a new goroutine because it will block on + // awaitResponse. This goroutine might outlive HandleRequest. + go func() { + g, err := m.newModule(ctx) + s.g = g + s.guestDone <- err + }() + + select { + case <-ctx.Done(): // ensure any context timeout applies + _ = s.Close() + return nil, 0, ctx.Err() + case err := <-s.guestDone: + _ = s.Close() + return nil, 0, err + case ctxNext := <-s.awaitingResponse: + if ctxNext != 0 { // will call the next handler + return ctx, ctxNext, s.closeRequest() + } else { // guest returned the response + return nil, 0, s.Close() + } + } +} + +// HandleResponse implements Middleware.HandleResponse +func (m *onceMiddleware) HandleResponse(ctx context.Context, _ uint32, hostErr error) error { + s := ctx.Value(onceRequestStateKey{}).(*onceRequestState) + + s.afterNext = true + s.responseReady <- hostErr // unblock awaitResponse + + // Wait until the goroutine completes. + select { + case <-ctx.Done(): // ensure any context timeout applies + _ = s.Close() + return nil + case err := <-s.guestDone: // block until the guest completes + _ = s.Close() + return err + } +} + +// awaitResponse implements the WebAssembly host function +// handler.FuncAwaitResponse. +func awaitResponse(ctx context.Context, stack []uint64) { + s := ctx.Value(onceRequestStateKey{}).(*onceRequestState) + s.awaitingResponse <- handler.CtxNext(stack[0]) + hostErr := <-s.responseReady + if hostErr != nil { + stack[0] = 1 + } else { + stack[0] = 0 + } +} + +func (m *onceMiddleware) instantiateHost(ctx context.Context) error { + _, err := m.hostModuleBuilder(). + NewFunctionBuilder(). + WithGoFunction(wazeroapi.GoFunc(awaitResponse), []wazeroapi.ValueType{i64}, []wazeroapi.ValueType{i32}). + WithParameterNames().Export(handler.FuncAwaitResponse). + Instantiate(ctx) + return err +} diff --git a/handler/pool.go b/handler/pool.go new file mode 100644 index 0000000..8b2fd9e --- /dev/null +++ b/handler/pool.go @@ -0,0 +1,135 @@ +package handler + +import ( + "context" + "sync" + + wazeroapi "github.com/tetratelabs/wazero/api" + + "github.com/http-wasm/http-wasm-host-go/api/handler" +) + +var _ Middleware = (*poolMiddleware)(nil) + +type poolMiddleware struct { + middleware + pool sync.Pool +} + +func (m *poolMiddleware) instantiateHost(ctx context.Context) error { + if _, err := m.hostModuleBuilder().Instantiate(ctx); err != nil { + return err + } + + // Eagerly add one instance to the pool. Doing so helps to fail fast. + if g, err := m.newGuest(ctx); err != nil { + return err + } else { + m.pool.Put(g) + } + return nil +} + +type poolRequestStateKey struct{} + +type poolRequestState struct { + requestState + + putPool func(x any) + g *guest +} + +func (r *poolRequestState) Close() error { + if g := r.g; g != nil { + r.putPool(r.g) + r.g = nil + } + return r.requestState.Close() +} + +// HandleRequest implements Middleware.HandleRequest +func (m *poolMiddleware) HandleRequest(ctx context.Context) (outCtx context.Context, ctxNext handler.CtxNext, err error) { + g, guestErr := m.getOrCreateGuest(ctx) + if guestErr != nil { + err = guestErr + return + } + + s := &poolRequestState{requestState: requestState{features: m.features}, putPool: m.pool.Put, g: g} + defer func() { + if ctxNext != 0 { // will call the next handler + if closeErr := s.closeRequest(); err == nil { + err = closeErr + } + } else { // guest errored or returned the response + if closeErr := s.Close(); err == nil { + err = closeErr + } + } + }() + + outCtx = context.WithValue(ctx, requestStateKey{}, &s.requestState) + outCtx = context.WithValue(outCtx, poolRequestStateKey{}, s) + ctxNext, err = g.handleRequest(outCtx) + return +} + +// HandleResponse implements Middleware.HandleResponse +func (m *poolMiddleware) HandleResponse(ctx context.Context, reqCtx uint32, hostErr error) error { + s := ctx.Value(poolRequestStateKey{}).(*poolRequestState) + defer s.Close() + s.afterNext = true + + return s.g.handleResponse(ctx, reqCtx, hostErr) +} + +func (m *poolMiddleware) getOrCreateGuest(ctx context.Context) (*guest, error) { + poolG := m.pool.Get() + if poolG == nil { + if g, createErr := m.newGuest(ctx); createErr != nil { + return nil, createErr + } else { + poolG = g + } + } + return poolG.(*guest), nil +} + +type guest struct { + guest wazeroapi.Module + handleRequestFn wazeroapi.Function + handleResponseFn wazeroapi.Function +} + +func (m *poolMiddleware) newGuest(ctx context.Context) (*guest, error) { + g, err := m.newModule(ctx) + if err != nil { + return nil, err + } + + return &guest{ + guest: g, + handleRequestFn: g.ExportedFunction(handler.FuncHandleRequest), + handleResponseFn: g.ExportedFunction(handler.FuncHandleResponse), + }, nil +} + +// handleRequest calls the WebAssembly guest function handler.FuncHandleRequest. +func (g *guest) handleRequest(ctx context.Context) (ctxNext handler.CtxNext, err error) { + if results, guestErr := g.handleRequestFn.Call(ctx); guestErr != nil { + err = guestErr + } else { + ctxNext = handler.CtxNext(results[0]) + } + return +} + +// handleResponse calls the WebAssembly guest function handler.FuncHandleResponse. +func (g *guest) handleResponse(ctx context.Context, reqCtx uint32, err error) error { + wasError := uint64(0) + if err != nil { + wasError = 1 + } + _, err = g.handleResponseFn.Call(ctx, uint64(reqCtx), wasError) + return err +} diff --git a/handler/state.go b/handler/state.go index d619a9b..ec40de6 100644 --- a/handler/state.go +++ b/handler/state.go @@ -1,7 +1,6 @@ package handler import ( - "context" "io" "net/http" @@ -12,10 +11,6 @@ import ( // pointer to the current request. type requestStateKey struct{} -func requestStateFromContext(ctx context.Context) *requestState { - return ctx.Value(requestStateKey{}).(*requestState) -} - type requestState struct { afterNext bool requestBodyReader io.ReadCloser @@ -26,9 +21,6 @@ type requestState struct { // features are the current request's features which may be more than // Middleware.Features. features handler.Features - - putPool func(x any) - g *guest } func (r *requestState) closeRequest() (err error) { @@ -46,14 +38,9 @@ func (r *requestState) closeRequest() (err error) { } // Close releases all resources for the current request, including: -// - putting the guest module back into the pool // - releasing any request body resources // - releasing any response body resources func (r *requestState) Close() (err error) { - if g := r.g; g != nil { - r.putPool(r.g) - r.g = nil - } err = r.closeRequest() if respBW := r.responseBodyWriter; respBW != nil { if f, ok := respBW.(http.Flusher); ok { diff --git a/internal/test/testdata.go b/internal/test/testdata.go index 987be23..f6e8b15 100644 --- a/internal/test/testdata.go +++ b/internal/test/testdata.go @@ -56,6 +56,10 @@ var BinExampleLog = func() []byte { return binExample("log") }() +var BinExampleLogOnce = func() []byte { + return binExample("log_once") +}() + var BinExampleRedact = func() []byte { return binExample("redact") }() @@ -80,6 +84,9 @@ var BinE2EURI []byte //go:embed testdata/e2e/header_value.wasm var BinE2EHeaderValue []byte +//go:embed testdata/e2e/await_response.wasm +var BinE2EAwaitResponse []byte + //go:embed testdata/e2e/handle_response.wasm var BinE2EHandleResponse []byte diff --git a/internal/test/testdata/bench/add_header_value.wasm b/internal/test/testdata/bench/add_header_value.wasm index 6871b74..a768195 100644 Binary files a/internal/test/testdata/bench/add_header_value.wasm and b/internal/test/testdata/bench/add_header_value.wasm differ diff --git a/internal/test/testdata/bench/add_header_value.wat b/internal/test/testdata/bench/add_header_value.wat index e02cb71..0bcf7f8 100644 --- a/internal/test/testdata/bench/add_header_value.wat +++ b/internal/test/testdata/bench/add_header_value.wat @@ -25,6 +25,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/get_header_names.wasm b/internal/test/testdata/bench/get_header_names.wasm index d9eaec8..6be9439 100644 Binary files a/internal/test/testdata/bench/get_header_names.wasm and b/internal/test/testdata/bench/get_header_names.wasm differ diff --git a/internal/test/testdata/bench/get_header_names.wat b/internal/test/testdata/bench/get_header_names.wat index 5478c62..2bc3e32 100644 --- a/internal/test/testdata/bench/get_header_names.wat +++ b/internal/test/testdata/bench/get_header_names.wat @@ -20,7 +20,7 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/get_header_values.wasm b/internal/test/testdata/bench/get_header_values.wasm index 1a59df5..268669e 100644 Binary files a/internal/test/testdata/bench/get_header_values.wasm and b/internal/test/testdata/bench/get_header_values.wasm differ diff --git a/internal/test/testdata/bench/get_header_values.wat b/internal/test/testdata/bench/get_header_values.wat index b53eee3..551a2cf 100644 --- a/internal/test/testdata/bench/get_header_values.wat +++ b/internal/test/testdata/bench/get_header_values.wat @@ -26,6 +26,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/get_uri.wasm b/internal/test/testdata/bench/get_uri.wasm index bf9eebe..62a21a8 100644 Binary files a/internal/test/testdata/bench/get_uri.wasm and b/internal/test/testdata/bench/get_uri.wasm differ diff --git a/internal/test/testdata/bench/get_uri.wat b/internal/test/testdata/bench/get_uri.wat index 66354e8..c510480 100644 --- a/internal/test/testdata/bench/get_uri.wat +++ b/internal/test/testdata/bench/get_uri.wat @@ -17,6 +17,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/log.wasm b/internal/test/testdata/bench/log.wasm index 5b4320f..19c684a 100644 Binary files a/internal/test/testdata/bench/log.wasm and b/internal/test/testdata/bench/log.wasm differ diff --git a/internal/test/testdata/bench/log.wat b/internal/test/testdata/bench/log.wat index 0c7e17e..07d26c8 100644 --- a/internal/test/testdata/bench/log.wat +++ b/internal/test/testdata/bench/log.wat @@ -19,6 +19,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/read_body.wasm b/internal/test/testdata/bench/read_body.wasm index 83b25c0..079a447 100644 Binary files a/internal/test/testdata/bench/read_body.wasm and b/internal/test/testdata/bench/read_body.wasm differ diff --git a/internal/test/testdata/bench/read_body.wat b/internal/test/testdata/bench/read_body.wat index b3ad8a1..b4dc35f 100644 --- a/internal/test/testdata/bench/read_body.wat +++ b/internal/test/testdata/bench/read_body.wat @@ -33,6 +33,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/read_body_stream.wasm b/internal/test/testdata/bench/read_body_stream.wasm index a301fdd..1c6142d 100644 Binary files a/internal/test/testdata/bench/read_body_stream.wasm and b/internal/test/testdata/bench/read_body_stream.wasm differ diff --git a/internal/test/testdata/bench/read_body_stream.wat b/internal/test/testdata/bench/read_body_stream.wat index ca7c795..49c30d7 100644 --- a/internal/test/testdata/bench/read_body_stream.wat +++ b/internal/test/testdata/bench/read_body_stream.wat @@ -29,6 +29,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/remove_header.wasm b/internal/test/testdata/bench/remove_header.wasm index 01844fc..7fea53d 100644 Binary files a/internal/test/testdata/bench/remove_header.wasm and b/internal/test/testdata/bench/remove_header.wasm differ diff --git a/internal/test/testdata/bench/remove_header.wat b/internal/test/testdata/bench/remove_header.wat index 2b170a8..f0a4e1b 100644 --- a/internal/test/testdata/bench/remove_header.wat +++ b/internal/test/testdata/bench/remove_header.wat @@ -17,6 +17,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/set_header_value.wasm b/internal/test/testdata/bench/set_header_value.wasm index 3d819c9..0036fa4 100644 Binary files a/internal/test/testdata/bench/set_header_value.wasm and b/internal/test/testdata/bench/set_header_value.wasm differ diff --git a/internal/test/testdata/bench/set_header_value.wat b/internal/test/testdata/bench/set_header_value.wat index 4fcf304..2d927f4 100644 --- a/internal/test/testdata/bench/set_header_value.wat +++ b/internal/test/testdata/bench/set_header_value.wat @@ -25,6 +25,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/set_status_code.wasm b/internal/test/testdata/bench/set_status_code.wasm index a0eb63a..459a75f 100644 Binary files a/internal/test/testdata/bench/set_status_code.wasm and b/internal/test/testdata/bench/set_status_code.wasm differ diff --git a/internal/test/testdata/bench/set_status_code.wat b/internal/test/testdata/bench/set_status_code.wat index 2c75342..87e7d4e 100644 --- a/internal/test/testdata/bench/set_status_code.wat +++ b/internal/test/testdata/bench/set_status_code.wat @@ -11,6 +11,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/set_uri.wasm b/internal/test/testdata/bench/set_uri.wasm index d7fd996..b122581 100644 Binary files a/internal/test/testdata/bench/set_uri.wasm and b/internal/test/testdata/bench/set_uri.wasm differ diff --git a/internal/test/testdata/bench/set_uri.wat b/internal/test/testdata/bench/set_uri.wat index d9ceb3e..2d20914 100644 --- a/internal/test/testdata/bench/set_uri.wat +++ b/internal/test/testdata/bench/set_uri.wat @@ -16,6 +16,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/bench/write_body.wasm b/internal/test/testdata/bench/write_body.wasm index eb97417..d1fc33c 100644 Binary files a/internal/test/testdata/bench/write_body.wasm and b/internal/test/testdata/bench/write_body.wasm differ diff --git a/internal/test/testdata/bench/write_body.wat b/internal/test/testdata/bench/write_body.wat index c1141c4..4ef6aab 100644 --- a/internal/test/testdata/bench/write_body.wat +++ b/internal/test/testdata/bench/write_body.wat @@ -19,6 +19,6 @@ (return (i64.const 0))) ;; handle_response should not be called as handle_request returns zero. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) (unreachable)) ) diff --git a/internal/test/testdata/e2e/await_response.wasm b/internal/test/testdata/e2e/await_response.wasm new file mode 100644 index 0000000..f195096 Binary files /dev/null and b/internal/test/testdata/e2e/await_response.wasm differ diff --git a/internal/test/testdata/e2e/await_response.wat b/internal/test/testdata/e2e/await_response.wat new file mode 100644 index 0000000..aabcca2 --- /dev/null +++ b/internal/test/testdata/e2e/await_response.wat @@ -0,0 +1,68 @@ +(module $await_response + +(; begin adapter logic + + Below is generic and can convert any normal handler to a single synchronous + call, provided $handle_request and $handle_response are not exported. ;) + + ;; import $await_response, which blocks until the response is ready. + (import "http_handler" "await_response" (func $await_response + (param $ctx_next i64) + (result (; is_error ;) i32))) + + ;; define a start function that performs a request-response without exports. + ;; note: this logic is generic and can convert any exported $handle_request/ + ;; $handle_response pair to a synchronous call without exports. + (func $start + (local $ctx_next i64) + (local $is_error i32) + (local $ctx i32) + + ;; ctxNext := handleRequest() + (local.set $ctx_next (call $handle_request)) + + ;; isError := awaitResponse(ctxNext) + (local.set $is_error (call $await_response (local.get $ctx_next))) + + ;; ctx := uint32(ctxNext >> 32) + (local.set $ctx + (i32.wrap_i64 (i64.shr_u (local.get $ctx_next) (i64.const 32)))) + + ;; handleResponse(ctx, isError) + (call $handle_response (local.get $ctx) (local.get $is_error)) + ) + +(; end adapter logic ;) + + (memory (export "memory") 1 1 (; 1 page==64KB ;)) + + ;; reqCtx is the upper 32-bits of the $ctx_next result the host should + ;; propagate from handle_request to handle_response. + (global $ctx (export "reqCtx") (mut i32) (i32.const 42)) + + ;; handle_request sets the request ID to the global then increments the + ;; global. + (func $handle_request (result (; ctx_next ;) i64) + (local $ctx i32) + + ;; reqCtx := global.reqCtx + (local.set $ctx (global.get $ctx)) + + ;; global.reqCtx++ + (global.set $ctx (i32.add (global.get $ctx) (i32.const 1))) + + ;; return uint64(reqCtx) << 32 | uint64(1) + (return + (i64.or + (i64.shl (i64.extend_i32_u (local.get $ctx)) (i64.const 32)) + (i64.const 1)))) + + ;; If propagation works, the current request ID should be one less than the + ;; global. + (func $handle_response (param $ctx i32) (param $is_error i32) + ;; if reqCtx != global.reqCtx - 1 { panic } + (if (i32.ne + (local.get $ctx) + (i32.sub (global.get $ctx) (i32.const 1))) + (then unreachable))) ;; fail as the host didn't propagate the reqCtx +) diff --git a/internal/test/testdata/e2e/handle_response.wasm b/internal/test/testdata/e2e/handle_response.wasm index 5f9e7b0..c620f52 100644 Binary files a/internal/test/testdata/e2e/handle_response.wasm and b/internal/test/testdata/e2e/handle_response.wasm differ diff --git a/internal/test/testdata/e2e/handle_response.wat b/internal/test/testdata/e2e/handle_response.wat index a83e225..8e1f936 100644 --- a/internal/test/testdata/e2e/handle_response.wat +++ b/internal/test/testdata/e2e/handle_response.wat @@ -4,31 +4,31 @@ ;; reqCtx is the upper 32-bits of the $ctx_next result the host should ;; propagate from handle_request to handle_response. - (global $reqCtx (export "reqCtx") (mut i32) (i32.const 42)) + (global $ctx (export "reqCtx") (mut i32) (i32.const 42)) ;; handle_request sets the request ID to the global then increments the ;; global. (func (export "handle_request") (result (; ctx_next ;) i64) - (local $reqCtx i32) + (local $ctx i32) ;; reqCtx := global.reqCtx - (local.set $reqCtx (global.get $reqCtx)) + (local.set $ctx (global.get $ctx)) ;; global.reqCtx++ - (global.set $reqCtx (i32.add (global.get $reqCtx) (i32.const 1))) + (global.set $ctx (i32.add (global.get $ctx) (i32.const 1))) ;; return uint64(reqCtx) << 32 | uint64(1) (return (i64.or - (i64.shl (i64.extend_i32_u (local.get $reqCtx)) (i64.const 32)) + (i64.shl (i64.extend_i32_u (local.get $ctx)) (i64.const 32)) (i64.const 1)))) ;; If propagation works, the current request ID should be one less than the ;; global. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func (export "handle_response") (param $ctx i32) (param $is_error i32) ;; if reqCtx != global.reqCtx - 1 { panic } (if (i32.ne - (local.get $reqCtx) - (i32.sub (global.get $reqCtx) (i32.const 1))) + (local.get $ctx) + (i32.sub (global.get $ctx) (i32.const 1))) (then unreachable))) ;; fail as the host didn't propagate the reqCtx ) diff --git a/internal/test/testdata/e2e/header_names.wasm b/internal/test/testdata/e2e/header_names.wasm index 2532af5..72e2d60 100644 Binary files a/internal/test/testdata/e2e/header_names.wasm and b/internal/test/testdata/e2e/header_names.wasm differ diff --git a/internal/test/testdata/e2e/header_names.wat b/internal/test/testdata/e2e/header_names.wat index 156cbd8..f4e7c13 100644 --- a/internal/test/testdata/e2e/header_names.wat +++ b/internal/test/testdata/e2e/header_names.wat @@ -60,5 +60,5 @@ (return (i64.const 1))) ;; handle_response is no-op as this is a request-only handler. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32)) + (func (export "handle_response") (param $ctx i32) (param $is_error i32)) ) diff --git a/internal/test/testdata/e2e/header_value.wasm b/internal/test/testdata/e2e/header_value.wasm index 5851071..73658b7 100644 Binary files a/internal/test/testdata/e2e/header_value.wasm and b/internal/test/testdata/e2e/header_value.wasm differ diff --git a/internal/test/testdata/e2e/header_value.wat b/internal/test/testdata/e2e/header_value.wat index 2d9f738..d65ca0c 100644 --- a/internal/test/testdata/e2e/header_value.wat +++ b/internal/test/testdata/e2e/header_value.wat @@ -27,5 +27,5 @@ (return (i64.const 1))) ;; handle_response is no-op as this is a request-only handler. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32)) + (func (export "handle_response") (param $ctx i32) (param $is_error i32)) ) diff --git a/internal/test/testdata/e2e/method.wasm b/internal/test/testdata/e2e/method.wasm index cce533e..14c3086 100644 Binary files a/internal/test/testdata/e2e/method.wasm and b/internal/test/testdata/e2e/method.wasm differ diff --git a/internal/test/testdata/e2e/method.wat b/internal/test/testdata/e2e/method.wat index 2a69658..0d0fba4 100644 --- a/internal/test/testdata/e2e/method.wat +++ b/internal/test/testdata/e2e/method.wat @@ -40,5 +40,5 @@ (return (i64.const 1))) ;; handle_response is no-op as this is a request-only handler. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32)) + (func (export "handle_response") (param $ctx i32) (param $is_error i32)) ) diff --git a/internal/test/testdata/e2e/protocol_version.wasm b/internal/test/testdata/e2e/protocol_version.wasm index 619f333..a79d05a 100644 Binary files a/internal/test/testdata/e2e/protocol_version.wasm and b/internal/test/testdata/e2e/protocol_version.wasm differ diff --git a/internal/test/testdata/e2e/protocol_version.wat b/internal/test/testdata/e2e/protocol_version.wat index 49ea809..b8de457 100644 --- a/internal/test/testdata/e2e/protocol_version.wat +++ b/internal/test/testdata/e2e/protocol_version.wat @@ -27,5 +27,5 @@ (return (i64.const 0))) ;; handle_response is no-op as this is a request-only handler. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32)) + (func (export "handle_response") (param $ctx i32) (param $is_error i32)) ) diff --git a/internal/test/testdata/e2e/uri.wasm b/internal/test/testdata/e2e/uri.wasm index 39bc565..4b0c4ef 100644 Binary files a/internal/test/testdata/e2e/uri.wasm and b/internal/test/testdata/e2e/uri.wasm differ diff --git a/internal/test/testdata/e2e/uri.wat b/internal/test/testdata/e2e/uri.wat index 703b80d..59df3d1 100644 --- a/internal/test/testdata/e2e/uri.wat +++ b/internal/test/testdata/e2e/uri.wat @@ -43,5 +43,5 @@ (return (i64.const 1))) ;; handle_response is no-op as this is a request-only handler. - (func (export "handle_response") (param $reqCtx i32) (param $is_error i32)) + (func (export "handle_response") (param $ctx i32) (param $is_error i32)) ) diff --git a/internal/test/testdata/error/panic_on_handle_request.wasm b/internal/test/testdata/error/panic_on_handle_request.wasm index 14efddc..0e68184 100644 Binary files a/internal/test/testdata/error/panic_on_handle_request.wasm and b/internal/test/testdata/error/panic_on_handle_request.wasm differ diff --git a/internal/test/testdata/error/panic_on_handle_request.wat b/internal/test/testdata/error/panic_on_handle_request.wat index 3305bdd..e862cdc 100644 --- a/internal/test/testdata/error/panic_on_handle_request.wat +++ b/internal/test/testdata/error/panic_on_handle_request.wat @@ -27,5 +27,5 @@ ;; Issue the unreachable instruction instead of returning ctx_next (unreachable)) - (func $handle_response (export "handle_response") (param $reqCtx i32) (param $is_error i32)) + (func $handle_response (export "handle_response") (param $ctx i32) (param $is_error i32)) ) diff --git a/internal/test/testdata/error/panic_on_handle_response.wasm b/internal/test/testdata/error/panic_on_handle_response.wasm index 6e57610..876f485 100644 Binary files a/internal/test/testdata/error/panic_on_handle_response.wasm and b/internal/test/testdata/error/panic_on_handle_response.wasm differ diff --git a/internal/test/testdata/error/panic_on_handle_response.wat b/internal/test/testdata/error/panic_on_handle_response.wat index 17615a2..dafeeb4 100644 --- a/internal/test/testdata/error/panic_on_handle_response.wat +++ b/internal/test/testdata/error/panic_on_handle_response.wat @@ -17,7 +17,7 @@ (return (i64.const 1))) ;; call the next handler ;; On handle_response, write "panic!" to stdout and crash. - (func $handle_response (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func $handle_response (export "handle_response") (param $ctx i32) (param $is_error i32) ;; Write the panic to stdout via its iovec [offset, len]. (call $wasi.fd_write (i32.const 1) ;; stdout diff --git a/internal/test/testdata/error/panic_on_start.wasm b/internal/test/testdata/error/panic_on_start.wasm index 049bde6..9d99af9 100644 Binary files a/internal/test/testdata/error/panic_on_start.wasm and b/internal/test/testdata/error/panic_on_start.wasm differ diff --git a/internal/test/testdata/error/panic_on_start.wat b/internal/test/testdata/error/panic_on_start.wat index 32e5b51..d3cce1e 100644 --- a/internal/test/testdata/error/panic_on_start.wat +++ b/internal/test/testdata/error/panic_on_start.wat @@ -31,5 +31,5 @@ (func $handle_request (export "handle_request") (result (; ctx_next ;) i64) (return (i64.const 0))) ;; don't call the next handler - (func $handle_response (export "handle_response") (param $reqCtx i32) (param $is_error i32)) + (func $handle_response (export "handle_response") (param $ctx i32) (param $is_error i32)) ) diff --git a/internal/test/testdata/error/set_request_header_after_next.wasm b/internal/test/testdata/error/set_request_header_after_next.wasm index e770c08..b9457ca 100644 Binary files a/internal/test/testdata/error/set_request_header_after_next.wasm and b/internal/test/testdata/error/set_request_header_after_next.wasm differ diff --git a/internal/test/testdata/error/set_request_header_after_next.wat b/internal/test/testdata/error/set_request_header_after_next.wat index 98ab516..ba21dae 100644 --- a/internal/test/testdata/error/set_request_header_after_next.wat +++ b/internal/test/testdata/error/set_request_header_after_next.wat @@ -20,7 +20,7 @@ (return (i64.const 1))) ;; handle_response tries to set a request header even though it is too late. - (func $handle_response (export "handle_response") (param $reqCtx i32) (param $is_error i32) + (func $handle_response (export "handle_response") (param $ctx i32) (param $is_error i32) (call $set_header_value (i32.const 0) ;; header_kind_request (global.get $name) (global.get $name_len)