Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

return fetch body directly #92

Merged
merged 2 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions _examples/basic-auth-proxy/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ module github.com/syumai/workers/_examples/basic-auth-server

go 1.21.3

require (
github.com/syumai/tinyutil v0.3.0
github.com/syumai/workers v0.5.1
)
require github.com/syumai/workers v0.5.1

replace github.com/syumai/workers => ../../
2 changes: 0 additions & 2 deletions _examples/basic-auth-proxy/go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
github.com/syumai/tinyutil v0.3.0 h1:sgWeE8oQyequIRLNeHZgR1PddpY4mxcdkfMgx2m53IE=
github.com/syumai/tinyutil v0.3.0/go.mod h1:/owCyUs1bh6tKxH7K1Ze3M/zZtZ+vGrj3h82fgNHDFI=
13 changes: 11 additions & 2 deletions _examples/basic-auth-proxy/main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package main

import (
"fmt"
"io"
"log"
"net/http"

"github.com/syumai/tinyutil/httputil"
"github.com/syumai/workers"
"github.com/syumai/workers/cloudflare/fetch"
)

const (
Expand All @@ -33,12 +34,20 @@ func handleRequest(w http.ResponseWriter, req *http.Request) {
u := *req.URL
u.Scheme = "https"
u.Host = "syum.ai"
resp, err := httputil.Get(u.String())
r, err := fetch.NewRequest(req.Context(), req.Method, u.String(), req.Body)
if err != nil {
handleError(w, http.StatusInternalServerError, "Internal Error")
log.Printf("failed to execute proxy request: %v\n", err)
return
}
r.Header = req.Header.Clone()
cli := fetch.NewClient()
resp, err := cli.Do(r, nil)
if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
for k, values := range resp.Header {
for _, v := range values {
w.Header().Add(k, v)
Expand Down
2 changes: 1 addition & 1 deletion cloudflare/kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (kv *KVNamespace) GetReader(key string, opts *KVNamespaceGetOptions) (io.Re
if err != nil {
return nil, err
}
return jsutil.ConvertStreamReaderToReader(v.Call("getReader")), nil
return jsutil.ConvertReadableStreamToReadCloser(v), nil
}

// KVNamespaceListOptions represents Cloudflare KV namespace list options.
Expand Down
2 changes: 1 addition & 1 deletion cloudflare/r2object.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func toR2Object(v js.Value) (*R2Object, error) {
bodyVal := v.Get("body")
var body io.Reader
if !bodyVal.IsUndefined() {
body = jsutil.ConvertStreamReaderToReader(v.Get("body").Call("getReader"))
body = jsutil.ConvertReadableStreamToReadCloser(v.Get("body"))
}
return &R2Object{
instance: v,
Expand Down
7 changes: 4 additions & 3 deletions cloudflare/sockets/socket.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@ import (
func newSocket(ctx context.Context, sockVal js.Value, readDeadline, writeDeadline time.Time) *Socket {
ctx, cancel := context.WithCancel(ctx)
writerVal := sockVal.Get("writable").Call("getWriter")
readerVal := sockVal.Get("readable").Call("getReader")
readerVal := sockVal.Get("readable")
readCloser := jsutil.ConvertReadableStreamToReadCloser(readerVal)
return &Socket{
ctx: ctx,
cancel: cancel,

reader: jsutil.ConvertStreamReaderToReader(readerVal),
reader: readCloser,
writerVal: writerVal,

readDeadline: readDeadline,
writeDeadline: writeDeadline,

startTLS: func() js.Value { return sockVal.Call("startTls") },
close: func() { sockVal.Call("close") },
closeRead: func() { readerVal.Call("close") },
closeRead: func() { readCloser.Close() },
closeWrite: func() { writerVal.Call("close") },
}
}
Expand Down
3 changes: 1 addition & 2 deletions internal/jshttp/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ func ToBody(streamOrNull js.Value) io.ReadCloser {
if streamOrNull.IsNull() {
return nil
}
sr := streamOrNull.Call("getReader")
return io.NopCloser(jsutil.ConvertStreamReaderToReader(sr))
return jsutil.ConvertReadableStreamToReadCloser(streamOrNull)
}

// ToRequest converts JavaScript sides Request to *http.Request.
Expand Down
13 changes: 9 additions & 4 deletions internal/jshttp/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@ func ToResponse(res js.Value) (*http.Response, error) {
Status: strconv.Itoa(status) + " " + res.Get("statusText").String(),
StatusCode: status,
Header: header,
Body: io.NopCloser(jsutil.ConvertStreamReaderToReader(blob.Call("stream").Call("getReader"))),
Body: jsutil.ConvertReadableStreamToReadCloser(blob.Call("stream")),
ContentLength: contentLength,
}, nil
}

// ToJSResponse converts *http.Response to JavaScript sides Response class object.
func ToJSResponse(res *http.Response) js.Value {
return newJSResponse(res.StatusCode, res.Header, res.Body)
return newJSResponse(res.StatusCode, res.Header, res.Body, nil)
}

// newJSResponse creates JavaScript sides Response class object.
// - Response: https://developer.mozilla.org/docs/Web/API/Response
func newJSResponse(statusCode int, headers http.Header, body io.ReadCloser) js.Value {
func newJSResponse(statusCode int, headers http.Header, body io.ReadCloser, rawBody *js.Value) js.Value {
status := statusCode
if status == 0 {
status = http.StatusOK
Expand All @@ -52,6 +52,11 @@ func newJSResponse(statusCode int, headers http.Header, body io.ReadCloser) js.V
status == http.StatusNotModified {
return jsutil.ResponseClass.New(jsutil.Null, respInit)
}
readableStream := jsutil.ConvertReaderToReadableStream(body)
var readableStream js.Value
if rawBody != nil {
readableStream = *rawBody
} else {
readableStream = jsutil.ConvertReaderToReadableStream(body)
}
return jsutil.ResponseClass.New(readableStream, respInit)
}
14 changes: 12 additions & 2 deletions internal/jshttp/responsewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"net/http"
"sync"
"syscall/js"

"github.com/syumai/workers/internal/jsutil"
)

type ResponseWriter struct {
Expand All @@ -14,9 +16,13 @@ type ResponseWriter struct {
Writer *io.PipeWriter
ReadyCh chan struct{}
Once sync.Once
RawJSBody *js.Value
}

var _ http.ResponseWriter = &ResponseWriter{}
var (
_ http.ResponseWriter = (*ResponseWriter)(nil)
_ jsutil.RawJSBodyWriter = (*ResponseWriter)(nil)
)

// Ready indicates that ResponseWriter is ready to be converted to Response.
func (w *ResponseWriter) Ready() {
Expand All @@ -38,8 +44,12 @@ func (w *ResponseWriter) WriteHeader(statusCode int) {
w.StatusCode = statusCode
}

func (w *ResponseWriter) WriteRawJSBody(body js.Value) {
w.RawJSBody = &body
}

// ToJSResponse converts *ResponseWriter to JavaScript sides Response.
// - Response: https://developer.mozilla.org/docs/Web/API/Response
func (w *ResponseWriter) ToJSResponse() js.Value {
return newJSResponse(w.StatusCode, w.HeaderValue, w.Reader)
return newJSResponse(w.StatusCode, w.HeaderValue, w.Reader, w.RawJSBody)
}
51 changes: 43 additions & 8 deletions internal/jsutil/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,30 @@ import (
"syscall/js"
)

// streamReaderToReader implements io.Reader sourced from ReadableStreamDefaultReader.
type RawJSBodyWriter interface {
WriteRawJSBody(body 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
type streamReaderToReader struct {
type readableStreamToReadCloser struct {
buf bytes.Buffer
streamReader js.Value
stream js.Value
streamReader *js.Value
}

var (
_ io.ReadCloser = (*readableStreamToReadCloser)(nil)
_ io.WriterTo = (*readableStreamToReadCloser)(nil)
)

// Read reads bytes from ReadableStreamDefaultReader.
func (sr *streamReaderToReader) Read(p []byte) (n int, err error) {
func (sr *readableStreamToReadCloser) Read(p []byte) (n int, err error) {
if sr.streamReader == nil {
r := sr.stream.Call("getReader")
sr.streamReader = &r
}
if sr.buf.Len() == 0 {
promise := sr.streamReader.Call("read")
resultCh := make(chan js.Value)
Expand Down Expand Up @@ -56,10 +70,31 @@ func (sr *streamReaderToReader) Read(p []byte) (n int, err error) {
return sr.buf.Read(p)
}

// ConvertStreamReaderToReader converts ReadableStreamDefaultReader to io.Reader.
func ConvertStreamReaderToReader(sr js.Value) io.Reader {
return &streamReaderToReader{
streamReader: sr,
func (sr *readableStreamToReadCloser) Close() error {
if sr.streamReader == nil {
return nil
}
sr.streamReader.Call("close")
return nil
}

// readerWrapper is wrapper to disable readableStreamToReadCloser's WriteTo method.
type readerWrapper struct {
io.Reader
}

func (sr *readableStreamToReadCloser) WriteTo(w io.Writer) (n int64, err error) {
if w, ok := w.(RawJSBodyWriter); ok {
w.WriteRawJSBody(sr.stream)
return 0, nil
}
return io.Copy(w, &readerWrapper{sr})
}

// ConvertReadableStreamToReadCloser converts ReadableStream to io.ReadCloser.
func ConvertReadableStreamToReadCloser(stream js.Value) io.ReadCloser {
return &readableStreamToReadCloser{
stream: stream,
}
}

Expand Down
Loading