mirror of
https://github.com/syumai/workers.git
synced 2025-03-10 09:27:08 +00:00
[experimental] add Hono middleware
This commit is contained in:
parent
484c7be891
commit
8cf026a01b
63
exp/hono/context.go
Normal file
63
exp/hono/context.go
Normal file
@ -0,0 +1,63 @@
|
||||
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) Header() Header {
|
||||
return &header{
|
||||
headerObj: c.ctxObj.Get("req").Get("headers"),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) SetStatus(statusCode int) {
|
||||
c.ctxObj.Call("status", statusCode)
|
||||
}
|
||||
|
||||
func (c *Context) ResponseBody() io.ReadCloser {
|
||||
return jsutil.ConvertReadableStreamToReadCloser(c.ctxObj.Get("res").Get("body"))
|
||||
}
|
||||
|
||||
func (c *Context) SetResponseBody(body io.ReadCloser) {
|
||||
var res js.Value
|
||||
if sr, ok := body.(jsutil.RawJSBodyGetter); ok {
|
||||
res = jsutil.ResponseClass.New(sr, c.ctxObj.Get("res"))
|
||||
} else {
|
||||
bodyObj := jsutil.ConvertReaderToReadableStream(body)
|
||||
res = jsutil.ResponseClass.New(bodyObj, c.ctxObj.Get("res"))
|
||||
}
|
||||
c.ctxObj.Set("res", res)
|
||||
}
|
63
exp/hono/header.go
Normal file
63
exp/hono/header.go
Normal file
@ -0,0 +1,63 @@
|
||||
package hono
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
type Header interface {
|
||||
Add(key, value string)
|
||||
Set(key, value string)
|
||||
Get(key string) string
|
||||
Values(key string) []string
|
||||
Entries() []HeaderEntry
|
||||
// Write(w io.Writer) // TODO: implement
|
||||
// Clone() httpHeader // Not planned to be implemented
|
||||
}
|
||||
|
||||
type HeaderEntry struct {
|
||||
Key string
|
||||
Values []string
|
||||
}
|
||||
|
||||
type header struct {
|
||||
headerObj js.Value
|
||||
}
|
||||
|
||||
var _ Header = (*header)(nil)
|
||||
|
||||
func (h *header) Add(key, value string) {
|
||||
h.headerObj.Call("append", key, value)
|
||||
}
|
||||
|
||||
func (h *header) Set(key, value string) {
|
||||
h.headerObj.Call("set", key, value)
|
||||
}
|
||||
|
||||
func (h *header) Get(key string) string {
|
||||
vs := h.Values(key)
|
||||
if len(vs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return vs[0]
|
||||
}
|
||||
|
||||
func (h *header) Values(key string) []string {
|
||||
values := h.headerObj.Call("get", key).String()
|
||||
return strings.Split(values, ",")
|
||||
}
|
||||
|
||||
func (h *header) Entries() []HeaderEntry {
|
||||
var entries []HeaderEntry
|
||||
entriesObj := js.Global().Get("Object").Call("entries", h.headerObj)
|
||||
for i := 0; i < entriesObj.Length(); i++ {
|
||||
entryObj := entriesObj.Index(i)
|
||||
key := entryObj.Index(0).String()
|
||||
values := entryObj.Index(1).String()
|
||||
entries[i] = HeaderEntry{
|
||||
Key: key,
|
||||
Values: strings.Split(values, ","),
|
||||
}
|
||||
}
|
||||
return entries
|
||||
}
|
78
exp/hono/middleware.go
Normal file
78
exp/hono/middleware.go
Normal file
@ -0,0 +1,78 @@
|
||||
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) > 2 {
|
||||
panic(fmt.Errorf("too many args given to handleRequest: %d", len(args)))
|
||||
}
|
||||
reqObj := args[0]
|
||||
nextFnObj := args[1]
|
||||
var cb js.Func
|
||||
cb = js.FuncOf(func(_ js.Value, pArgs []js.Value) any {
|
||||
defer cb.Release()
|
||||
resolve := pArgs[0]
|
||||
go func() {
|
||||
err := runHonoMiddleware(reqObj, nextFnObj)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resolve.Invoke(js.Undefined())
|
||||
}()
|
||||
return js.Undefined()
|
||||
})
|
||||
return js.Undefined()
|
||||
})
|
||||
jsutil.Binding.Set("runHonoMiddleware", runHonoMiddlewareCallback)
|
||||
}
|
||||
|
||||
func runHonoMiddleware(reqObj, nextFnObj js.Value) error {
|
||||
if middleware == nil {
|
||||
return fmt.Errorf("ServeMiddleware must be called before runHonoMiddleware.")
|
||||
}
|
||||
c := newContext(reqObj)
|
||||
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)
|
||||
}
|
||||
}
|
@ -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/std@0.139.0/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{
|
||||
|
Loading…
x
Reference in New Issue
Block a user