mirror of
https://github.com/syumai/workers.git
synced 2025-03-11 01:39:11 +00:00
implement R2Object / R2Objects
This commit is contained in:
parent
3981556bb3
commit
a95ca1de25
@ -1,12 +1,7 @@
|
|||||||
module github.com/syumai/workers/examples/simple-json-server
|
module github.com/syumai/workers/examples/r2-image-server
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require github.com/syumai/workers v0.0.0-00010101000000-000000000000
|
||||||
github.com/mailru/easyjson v0.7.7
|
|
||||||
github.com/syumai/workers v0.0.0-00010101000000-000000000000
|
|
||||||
)
|
|
||||||
|
|
||||||
replace github.com/syumai/workers => ../../
|
replace github.com/syumai/workers => ../../
|
||||||
|
|
||||||
require github.com/josharian/intern v1.0.0 // indirect
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
|
@ -8,10 +8,51 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
global = js.Global()
|
global = js.Global()
|
||||||
objectClass = global.Get("Object")
|
objectClass = global.Get("Object")
|
||||||
|
promiseClass = global.Get("Promise")
|
||||||
|
uint8ArrayClass = global.Get("Uint8Array")
|
||||||
|
errorClass = global.Get("Error")
|
||||||
|
readableStreamClass = global.Get("ReadableStream")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func newObject() js.Value {
|
||||||
|
return objectClass.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUint8Array(size int) js.Value {
|
||||||
|
return uint8ArrayClass.New(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPromise(fn js.Func) js.Value {
|
||||||
|
return promiseClass.New(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func awaitPromise(promiseVal js.Value) (js.Value, error) {
|
||||||
|
resultCh := make(chan js.Value)
|
||||||
|
errCh := make(chan error)
|
||||||
|
var then, catch js.Func
|
||||||
|
then = js.FuncOf(func(_ js.Value, args []js.Value) any {
|
||||||
|
defer then.Release()
|
||||||
|
result := args[0]
|
||||||
|
resultCh <- result
|
||||||
|
return js.Undefined()
|
||||||
|
})
|
||||||
|
catch = js.FuncOf(func(_ js.Value, args []js.Value) any {
|
||||||
|
defer catch.Release()
|
||||||
|
result := args[0]
|
||||||
|
errCh <- fmt.Errorf("failed on promise: %s", result.Call("toString").String())
|
||||||
|
return js.Undefined()
|
||||||
|
})
|
||||||
|
promiseVal.Call("then", then).Call("catch", catch)
|
||||||
|
select {
|
||||||
|
case result := <-resultCh:
|
||||||
|
return result, nil
|
||||||
|
case err := <-errCh:
|
||||||
|
return js.Value{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dateToTime converts JavaScript side's Data object into time.Time.
|
// dateToTime converts JavaScript side's Data object into time.Time.
|
||||||
func dateToTime(v js.Value) (time.Time, error) {
|
func dateToTime(v js.Value) (time.Time, error) {
|
||||||
milliStr := v.Call("getTime").Call("toString").String()
|
milliStr := v.Call("getTime").Call("toString").String()
|
||||||
|
@ -1,13 +1,45 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/syumai/workers"
|
"github.com/syumai/workers"
|
||||||
"github.com/syumai/workers/examples/simple-json-server/app"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
// bucketName is R2 bucket name defined in wrangler.toml.
|
||||||
http.HandleFunc("/hello", app.HelloHandler)
|
const bucketName = "BUCKET"
|
||||||
workers.Serve(nil) // use http.DefaultServeMux
|
|
||||||
|
func handleErr(w http.ResponseWriter, msg string) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example is based on implementation in syumai/workers-playground
|
||||||
|
// * https://github.com/syumai/workers-playground/blob/e32881648ccc055e3690a0d9c750a834261c333e/r2-image-viewer/src/index.ts#L30
|
||||||
|
func handler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
bucket, err := NewR2Bucket(bucketName)
|
||||||
|
if err != nil {
|
||||||
|
handleErr(w, "failed to get R2Bucket\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
imgPath := req.URL.Path
|
||||||
|
imgObj, err := bucket.Get(imgPath)
|
||||||
|
if err != nil {
|
||||||
|
handleErr(w, "failed to get R2Object\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=14400")
|
||||||
|
w.Header().Set("ETag", fmt.Sprintf("W/%s", imgObj.HTTPETag))
|
||||||
|
contentType := "application/octet-stream"
|
||||||
|
if imgObj.HTTPMetadata.ContentType != nil {
|
||||||
|
contentType = *imgObj.HTTPMetadata.ContentType
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
io.Copy(w, imgObj.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
workers.Serve(http.HandlerFunc(handler))
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,11 @@ import (
|
|||||||
// - https://developers.cloudflare.com/r2/runtime-apis/#bucket-method-definitions
|
// - https://developers.cloudflare.com/r2/runtime-apis/#bucket-method-definitions
|
||||||
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1006
|
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1006
|
||||||
type R2Bucket interface {
|
type R2Bucket interface {
|
||||||
Head(key string)
|
Head(key string) (*R2Object, error)
|
||||||
Get(key string) *R2Object
|
Get(key string) (*R2Object, error)
|
||||||
Put(key string, value io.Reader)
|
Put(key string, value io.Reader) error
|
||||||
Delete(key string)
|
Delete(key string) error
|
||||||
List() []*R2Object
|
List() (*R2Objects, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type r2Bucket struct {
|
type r2Bucket struct {
|
||||||
@ -23,24 +23,45 @@ type r2Bucket struct {
|
|||||||
|
|
||||||
var _ R2Bucket = &r2Bucket{}
|
var _ R2Bucket = &r2Bucket{}
|
||||||
|
|
||||||
func (r *r2Bucket) Head(key string) {
|
func (r *r2Bucket) Head(key string) (*R2Object, error) {
|
||||||
|
p := r.instance.Call("head", key)
|
||||||
|
v, err := awaitPromise(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if v.IsNull() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return toR2Object(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *r2Bucket) Get(key string) (*R2Object, error) {
|
||||||
|
p := r.instance.Call("get", key)
|
||||||
|
v, err := awaitPromise(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if v.IsNull() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return toR2Object(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *r2Bucket) Put(key string, value io.Reader) error {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *r2Bucket) Get(key string) *R2Object {
|
func (r *r2Bucket) Delete(key string) error {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *r2Bucket) Put(key string, value io.Reader) {
|
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *r2Bucket) Delete(key string) {
|
func (r *r2Bucket) List() (*R2Objects, error) {
|
||||||
panic("implement me")
|
p := r.instance.Call("list")
|
||||||
}
|
v, err := awaitPromise(p)
|
||||||
|
if err != nil {
|
||||||
func (r *r2Bucket) List() []*R2Object {
|
return nil, err
|
||||||
panic("implement me")
|
}
|
||||||
|
return toR2Objects(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewR2Bucket(varName string) (R2Bucket, error) {
|
func NewR2Bucket(varName string) (R2Bucket, error) {
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// R2Object represents JavaScript side's R2Object.
|
||||||
|
// * https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1094
|
||||||
type R2Object struct {
|
type R2Object struct {
|
||||||
|
instance js.Value
|
||||||
Key string
|
Key string
|
||||||
Version string
|
Version string
|
||||||
Size int
|
Size int
|
||||||
@ -16,7 +20,9 @@ type R2Object struct {
|
|||||||
Uploaded time.Time
|
Uploaded time.Time
|
||||||
HTTPMetadata R2HTTPMetadata
|
HTTPMetadata R2HTTPMetadata
|
||||||
CustomMetadata map[string]string
|
CustomMetadata map[string]string
|
||||||
Body io.Reader
|
// Body is a body of R2Object.
|
||||||
|
// This value becomes nil when `Head` method of R2Bucket is called.
|
||||||
|
Body io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
@ -24,7 +30,15 @@ type R2Object struct {
|
|||||||
// func (o *R2Object) WriteHTTPMetadata(headers http.Header) {
|
// func (o *R2Object) WriteHTTPMetadata(headers http.Header) {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// toR2Object converts JavaScript side's R2Object to R2Object.
|
func (o *R2Object) BodyUsed() (bool, error) {
|
||||||
|
v := o.instance.Get("bodyUsed")
|
||||||
|
if v.IsUndefined() {
|
||||||
|
return false, errors.New("bodyUsed doesn't exist for this R2Object")
|
||||||
|
}
|
||||||
|
return v.Bool(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toR2Object converts JavaScript side's R2Object to *R2Object.
|
||||||
// * https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1094
|
// * https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1094
|
||||||
func toR2Object(v js.Value) (*R2Object, error) {
|
func toR2Object(v js.Value) (*R2Object, error) {
|
||||||
uploaded, err := dateToTime(v.Get("uploaded"))
|
uploaded, err := dateToTime(v.Get("uploaded"))
|
||||||
@ -35,7 +49,13 @@ func toR2Object(v js.Value) (*R2Object, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error converting httpMetadata: %w", err)
|
return nil, fmt.Errorf("error converting httpMetadata: %w", err)
|
||||||
}
|
}
|
||||||
|
bodyVal := v.Get("body")
|
||||||
|
var body io.Reader
|
||||||
|
if !bodyVal.IsUndefined() {
|
||||||
|
body = convertStreamReaderToReader(v.Get("body").Call("getReader"))
|
||||||
|
}
|
||||||
return &R2Object{
|
return &R2Object{
|
||||||
|
instance: v,
|
||||||
Key: v.Get("key").String(),
|
Key: v.Get("key").String(),
|
||||||
Version: v.Get("version").String(),
|
Version: v.Get("version").String(),
|
||||||
Size: v.Get("size").Int(),
|
Size: v.Get("size").Int(),
|
||||||
@ -44,7 +64,7 @@ func toR2Object(v js.Value) (*R2Object, error) {
|
|||||||
Uploaded: uploaded,
|
Uploaded: uploaded,
|
||||||
HTTPMetadata: r2Meta,
|
HTTPMetadata: r2Meta,
|
||||||
CustomMetadata: strRecordToMap(v.Get("customMetadata")),
|
CustomMetadata: strRecordToMap(v.Get("customMetadata")),
|
||||||
Body: convertStreamReaderToReader(v.Get("body").Call("getReader")),
|
Body: body,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,40 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall/js"
|
||||||
|
)
|
||||||
|
|
||||||
|
// R2Objects represents JavaScript side's R2Objects.
|
||||||
|
// * https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1121
|
||||||
type R2Objects struct {
|
type R2Objects struct {
|
||||||
Objects []*R2Object
|
Objects []*R2Object
|
||||||
|
Truncated bool
|
||||||
|
Cursor *string
|
||||||
|
DelimitedPrefixes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// toR2Objects converts JavaScript side's R2Objects to *R2Objects.
|
||||||
|
// * https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1121
|
||||||
|
func toR2Objects(v js.Value) (*R2Objects, error) {
|
||||||
|
objectsVal := v.Get("objects")
|
||||||
|
objects := make([]*R2Object, objectsVal.Length())
|
||||||
|
for i := 0; i < len(objects); i++ {
|
||||||
|
obj, err := toR2Object(objectsVal.Index(i))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error converting to R2Object: %w", err)
|
||||||
|
}
|
||||||
|
objects[i] = obj
|
||||||
|
}
|
||||||
|
prefixesVal := objectsVal.Get("delimitedPrefixes")
|
||||||
|
prefixes := make([]string, prefixesVal.Length())
|
||||||
|
for i := 0; i < len(prefixes); i++ {
|
||||||
|
prefixes[i] = prefixesVal.Index(i).String()
|
||||||
|
}
|
||||||
|
return &R2Objects{
|
||||||
|
Objects: objects,
|
||||||
|
Truncated: objectsVal.Get("truncated").Bool(),
|
||||||
|
Cursor: maybeString(objectsVal.Get("cursor")),
|
||||||
|
DelimitedPrefixes: prefixes,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
name = "simple-json-server"
|
name = "r2-image-server"
|
||||||
main = "./worker.mjs"
|
main = "./worker.mjs"
|
||||||
compatibility_date = "2022-05-13"
|
compatibility_date = "2022-05-13"
|
||||||
compatibility_flags = [
|
compatibility_flags = [
|
||||||
@ -7,3 +7,8 @@ compatibility_flags = [
|
|||||||
|
|
||||||
[build]
|
[build]
|
||||||
command = "make build"
|
command = "make build"
|
||||||
|
|
||||||
|
[[r2_buckets]]
|
||||||
|
binding = 'BUCKET'
|
||||||
|
bucket_name = 'r2-image-viewer'
|
||||||
|
preview_bucket_name = 'r2-image-viewer-dev'
|
Loading…
x
Reference in New Issue
Block a user