mirror of
https://github.com/syumai/workers.git
synced 2025-03-10 17:29:11 +00:00
Merge pull request #94 from syumai/add-hono-middleware
[experimental] add Hono middleware
This commit is contained in:
commit
a39272584a
64
exp/hono/context.go
Normal file
64
exp/hono/context.go
Normal file
@ -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)
|
||||||
|
}
|
77
exp/hono/middleware.go
Normal file
77
exp/hono/middleware.go
Normal file
@ -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 {}
|
||||||
|
}
|
32
exp/hono/middleware_test.go
Normal file
32
exp/hono/middleware_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
35
exp/hono/response.go
Normal file
35
exp/hono/response.go
Normal file
@ -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)
|
||||||
|
}
|
@ -11,6 +11,10 @@ type RawJSBodyWriter interface {
|
|||||||
WriteRawJSBody(body js.Value)
|
WriteRawJSBody(body js.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RawJSBodyGetter interface {
|
||||||
|
GetRawJSBody() js.Value
|
||||||
|
}
|
||||||
|
|
||||||
// readableStreamToReadCloser implements io.Reader sourced from ReadableStreamDefaultReader.
|
// readableStreamToReadCloser implements io.Reader sourced from ReadableStreamDefaultReader.
|
||||||
// - ReadableStreamDefaultReader: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader
|
// - ReadableStreamDefaultReader: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader
|
||||||
// - This implementation is based on: https://deno.land/std@0.139.0/streams/conversion.ts#L76
|
// - This implementation is based on: https://deno.land/std@0.139.0/streams/conversion.ts#L76
|
||||||
@ -21,8 +25,9 @@ type readableStreamToReadCloser struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ io.ReadCloser = (*readableStreamToReadCloser)(nil)
|
_ io.ReadCloser = (*readableStreamToReadCloser)(nil)
|
||||||
_ io.WriterTo = (*readableStreamToReadCloser)(nil)
|
_ io.WriterTo = (*readableStreamToReadCloser)(nil)
|
||||||
|
_ RawJSBodyGetter = (*readableStreamToReadCloser)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Read reads bytes from ReadableStreamDefaultReader.
|
// 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})
|
return io.Copy(w, &readerWrapper{sr})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sr *readableStreamToReadCloser) GetRawJSBody() js.Value {
|
||||||
|
return sr.stream
|
||||||
|
}
|
||||||
|
|
||||||
// ConvertReadableStreamToReadCloser converts ReadableStream to io.ReadCloser.
|
// ConvertReadableStreamToReadCloser converts ReadableStream to io.ReadCloser.
|
||||||
func ConvertReadableStreamToReadCloser(stream js.Value) io.ReadCloser {
|
func ConvertReadableStreamToReadCloser(stream js.Value) io.ReadCloser {
|
||||||
return &readableStreamToReadCloser{
|
return &readableStreamToReadCloser{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user