-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #94 from syumai/add-hono-middleware
[experimental] add Hono middleware
- Loading branch information
Showing
5 changed files
with
219 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package hono | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"net/http" | ||
"sync" | ||
"syscall/js" | ||
|
||
"github.com/syumai/workers/internal/jshttp" | ||
"github.com/syumai/workers/internal/jsutil" | ||
"github.com/syumai/workers/internal/runtimecontext" | ||
) | ||
|
||
type Context struct { | ||
ctxObj js.Value | ||
reqFunc func() *http.Request | ||
} | ||
|
||
func newContext(ctxObj js.Value) *Context { | ||
return &Context{ | ||
ctxObj: ctxObj, | ||
reqFunc: sync.OnceValue(func() *http.Request { | ||
reqObj := ctxObj.Get("req").Get("raw") | ||
req, err := jshttp.ToRequest(reqObj) | ||
if err != nil { | ||
panic(err) | ||
} | ||
ctx := runtimecontext.New(context.Background(), reqObj, jsutil.RuntimeContext) | ||
req = req.WithContext(ctx) | ||
return req | ||
}), | ||
} | ||
} | ||
|
||
func (c *Context) Request() *http.Request { | ||
return c.reqFunc() | ||
} | ||
|
||
func (c *Context) SetHeader(key, value string) { | ||
c.ctxObj.Call("header", key, value) | ||
} | ||
|
||
func (c *Context) SetStatus(statusCode int) { | ||
c.ctxObj.Call("status", statusCode) | ||
} | ||
|
||
func (c *Context) RawResponse() js.Value { | ||
return c.ctxObj.Get("res") | ||
} | ||
|
||
func (c *Context) ResponseBody() io.ReadCloser { | ||
return jsutil.ConvertReadableStreamToReadCloser(c.ctxObj.Get("res").Get("body")) | ||
} | ||
|
||
func (c *Context) SetBody(body io.ReadCloser) { | ||
bodyObj := convertBodyToJS(body) | ||
respObj := c.ctxObj.Call("body", bodyObj) | ||
c.ctxObj.Set("res", respObj) | ||
} | ||
|
||
func (c *Context) SetResponse(respObj js.Value) { | ||
c.ctxObj.Set("res", respObj) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package hono | ||
|
||
import ( | ||
"fmt" | ||
"syscall/js" | ||
|
||
"github.com/syumai/workers/internal/jsutil" | ||
) | ||
|
||
type Middleware func(c *Context, next func()) | ||
|
||
var middleware Middleware | ||
|
||
func ChainMiddlewares(middlewares ...Middleware) Middleware { | ||
if len(middlewares) == 0 { | ||
return nil | ||
} | ||
if len(middlewares) == 1 { | ||
return middlewares[0] | ||
} | ||
return func(c *Context, next func()) { | ||
for i := len(middlewares) - 1; i > 0; i-- { | ||
i := i | ||
f := next | ||
next = func() { | ||
middlewares[i](c, f) | ||
} | ||
} | ||
middlewares[0](c, next) | ||
} | ||
} | ||
|
||
func init() { | ||
runHonoMiddlewareCallback := js.FuncOf(func(_ js.Value, args []js.Value) any { | ||
if len(args) > 1 { | ||
panic(fmt.Errorf("too many args given to handleRequest: %d", len(args))) | ||
} | ||
nextFnObj := args[0] | ||
var cb js.Func | ||
cb = js.FuncOf(func(_ js.Value, pArgs []js.Value) any { | ||
defer cb.Release() | ||
resolve := pArgs[0] | ||
go func() { | ||
err := runHonoMiddleware(nextFnObj) | ||
if err != nil { | ||
panic(err) | ||
} | ||
resolve.Invoke(js.Undefined()) | ||
}() | ||
return js.Undefined() | ||
}) | ||
return jsutil.NewPromise(cb) | ||
}) | ||
jsutil.Binding.Set("runHonoMiddleware", runHonoMiddlewareCallback) | ||
} | ||
|
||
func runHonoMiddleware(nextFnObj js.Value) error { | ||
if middleware == nil { | ||
return fmt.Errorf("ServeMiddleware must be called before runHonoMiddleware.") | ||
} | ||
c := newContext(jsutil.RuntimeContext.Get("ctx")) | ||
next := func() { | ||
jsutil.AwaitPromise(nextFnObj.Invoke()) | ||
} | ||
middleware(c, next) | ||
return nil | ||
} | ||
|
||
//go:wasmimport workers ready | ||
func ready() | ||
|
||
// ServeMiddleware sets the Task to be executed | ||
func ServeMiddleware(middleware_ Middleware) { | ||
middleware = middleware_ | ||
ready() | ||
select {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package hono | ||
|
||
import "testing" | ||
|
||
func TestChainMiddlewares(t *testing.T) { | ||
result := "" | ||
middlewares := []Middleware{ | ||
func(c *Context, next func()) { | ||
result += "1" | ||
next() | ||
result += "1" | ||
}, | ||
func(c *Context, next func()) { | ||
result += "2" | ||
next() | ||
result += "2" | ||
}, | ||
func(c *Context, next func()) { | ||
result += "3" | ||
next() | ||
result += "3" | ||
}, | ||
} | ||
m := ChainMiddlewares(middlewares...) | ||
m(nil, func() { | ||
result += "0" | ||
}) | ||
const want = "1230321" | ||
if result != want { | ||
t.Errorf("result: got %q, want %q", result, want) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package hono | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
"syscall/js" | ||
|
||
"github.com/syumai/workers/internal/jshttp" | ||
"github.com/syumai/workers/internal/jsutil" | ||
) | ||
|
||
func convertBodyToJS(body io.ReadCloser) js.Value { | ||
if sr, ok := body.(jsutil.RawJSBodyGetter); ok { | ||
return sr.GetRawJSBody() | ||
} | ||
return jsutil.ConvertReaderToReadableStream(body) | ||
} | ||
|
||
func NewJSResponse(body io.ReadCloser, statusCode int, headers http.Header) js.Value { | ||
bodyObj := convertBodyToJS(body) | ||
opts := jsutil.ObjectClass.New() | ||
if statusCode != 0 { | ||
opts.Set("status", statusCode) | ||
} | ||
if headers != nil { | ||
headersObj := jshttp.ToJSHeader(headers) | ||
opts.Set("headers", headersObj) | ||
} | ||
return jsutil.ResponseClass.New(bodyObj, opts) | ||
} | ||
|
||
func NewJSResponseWithBase(body io.ReadCloser, baseRespObj js.Value) js.Value { | ||
bodyObj := convertBodyToJS(body) | ||
return jsutil.ResponseClass.New(bodyObj, baseRespObj) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,10 @@ type RawJSBodyWriter interface { | |
WriteRawJSBody(body js.Value) | ||
} | ||
|
||
type RawJSBodyGetter interface { | ||
GetRawJSBody() js.Value | ||
} | ||
|
||
// readableStreamToReadCloser implements io.Reader sourced from ReadableStreamDefaultReader. | ||
// - ReadableStreamDefaultReader: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader | ||
// - This implementation is based on: https://deno.land/[email protected]/streams/conversion.ts#L76 | ||
|
@@ -21,8 +25,9 @@ type readableStreamToReadCloser struct { | |
} | ||
|
||
var ( | ||
_ io.ReadCloser = (*readableStreamToReadCloser)(nil) | ||
_ io.WriterTo = (*readableStreamToReadCloser)(nil) | ||
_ io.ReadCloser = (*readableStreamToReadCloser)(nil) | ||
_ io.WriterTo = (*readableStreamToReadCloser)(nil) | ||
_ RawJSBodyGetter = (*readableStreamToReadCloser)(nil) | ||
) | ||
|
||
// Read reads bytes from ReadableStreamDefaultReader. | ||
|
@@ -91,6 +96,10 @@ func (sr *readableStreamToReadCloser) WriteTo(w io.Writer) (n int64, err error) | |
return io.Copy(w, &readerWrapper{sr}) | ||
} | ||
|
||
func (sr *readableStreamToReadCloser) GetRawJSBody() js.Value { | ||
return sr.stream | ||
} | ||
|
||
// ConvertReadableStreamToReadCloser converts ReadableStream to io.ReadCloser. | ||
func ConvertReadableStreamToReadCloser(stream js.Value) io.ReadCloser { | ||
return &readableStreamToReadCloser{ | ||
|