Skip to content

Commit

Permalink
Merge pull request #92 from syumai/return-fetch-body-directly
Browse files Browse the repository at this point in the history
return fetch body directly
  • Loading branch information
syumai authored Jan 24, 2024
2 parents 1519718 + b7c9d03 commit 4b950e8
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 29 deletions.
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

0 comments on commit 4b950e8

Please sign in to comment.