mirror of
https://github.com/syumai/workers.git
synced 2025-03-10 17:29:11 +00:00
fix implementation of put and delete of R2
This commit is contained in:
parent
02f63bd14c
commit
19537376e1
@ -10,8 +10,9 @@
|
||||
* [ ] R2 - Partially supported
|
||||
- [x] Head
|
||||
- [x] Get
|
||||
- [ ] Put
|
||||
- [ ] Delete
|
||||
- [x] Put (load all bytes to memory)
|
||||
- [ ] Put (stream)
|
||||
- [x] Delete
|
||||
- [x] List
|
||||
- [ ] Options for R2 methods
|
||||
* [ ] environment variables (WIP)
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/syumai/workers"
|
||||
@ -16,26 +17,61 @@ const bucketName = "BUCKET"
|
||||
func handleErr(w http.ResponseWriter, msg string, err error) {
|
||||
log.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
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) {
|
||||
type server struct {
|
||||
bucket workers.R2Bucket
|
||||
}
|
||||
|
||||
func newServer() (*server, error) {
|
||||
// delete image object from R2
|
||||
bucket, err := workers.NewR2Bucket(bucketName)
|
||||
if err != nil {
|
||||
handleErr(w, "failed to get R2Bucket\n", err)
|
||||
return nil, err
|
||||
}
|
||||
return &server{bucket: bucket}, nil
|
||||
}
|
||||
|
||||
func (s *server) post(w http.ResponseWriter, req *http.Request, key string) {
|
||||
objects, err := s.bucket.List()
|
||||
if err != nil {
|
||||
handleErr(w, "failed to list R2Objects\n", err)
|
||||
return
|
||||
}
|
||||
imgPath := strings.TrimPrefix(req.URL.Path, "/")
|
||||
imgObj, err := bucket.Get(imgPath)
|
||||
for _, obj := range objects.Objects {
|
||||
if obj.Key == key {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "key %s already exists\n", key)
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = s.bucket.Put(key, req.Body, &workers.R2PutOptions{
|
||||
HTTPMetadata: workers.R2HTTPMetadata{
|
||||
ContentType: req.Header.Get("Content-Type"),
|
||||
},
|
||||
CustomMetadata: map[string]string{"custom-key": "custom-value"},
|
||||
})
|
||||
if err != nil {
|
||||
handleErr(w, "failed to put R2Object\n", err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte("successfully uploaded image"))
|
||||
}
|
||||
|
||||
func (s *server) get(w http.ResponseWriter, req *http.Request, key string) {
|
||||
// get image object from R2
|
||||
imgObj, err := s.bucket.Get(key)
|
||||
if err != nil {
|
||||
handleErr(w, "failed to get R2Object\n", err)
|
||||
return
|
||||
}
|
||||
if imgObj == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(fmt.Sprintf("image not found: %s", imgPath)))
|
||||
w.Write([]byte(fmt.Sprintf("image not found: %s", key)))
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "public, max-age=14400")
|
||||
@ -48,6 +84,39 @@ func handler(w http.ResponseWriter, req *http.Request) {
|
||||
io.Copy(w, imgObj.Body)
|
||||
}
|
||||
|
||||
func main() {
|
||||
workers.Serve(http.HandlerFunc(handler))
|
||||
func (s *server) delete(w http.ResponseWriter, req *http.Request, key string) {
|
||||
// delete image object from R2
|
||||
if err := s.bucket.Delete(key); err != nil {
|
||||
handleErr(w, "failed to delete R2Object\n", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte("successfully deleted image"))
|
||||
}
|
||||
|
||||
func (s *server) routeHandler(w http.ResponseWriter, req *http.Request) {
|
||||
key := strings.TrimPrefix(req.URL.Path, "/")
|
||||
switch req.Method {
|
||||
case "GET":
|
||||
s.get(w, req, key)
|
||||
return
|
||||
case "DELETE":
|
||||
s.delete(w, req, key)
|
||||
return
|
||||
case "POST":
|
||||
s.post(w, req, key)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("url not found\n"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
s, err := newServer()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to start server: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
workers.Serve(http.HandlerFunc(s.routeHandler))
|
||||
}
|
||||
|
14
jsutil.go
14
jsutil.go
@ -2,7 +2,6 @@ package workers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"syscall/js"
|
||||
"time"
|
||||
)
|
||||
@ -17,9 +16,7 @@ var (
|
||||
uint8ArrayClass = global.Get("Uint8Array")
|
||||
errorClass = global.Get("Error")
|
||||
readableStreamClass = global.Get("ReadableStream")
|
||||
stringClass = global.Get("String")
|
||||
dateClass = global.Get("Date")
|
||||
numberClass = global.Get("Number")
|
||||
)
|
||||
|
||||
func newObject() js.Value {
|
||||
@ -96,16 +93,11 @@ func maybeDate(v js.Value) (time.Time, error) {
|
||||
|
||||
// dateToTime converts JavaScript side's Data object into time.Time.
|
||||
func dateToTime(v js.Value) (time.Time, error) {
|
||||
milliStr := stringClass.Invoke(v.Call("getTime")).String()
|
||||
milli, err := strconv.ParseInt(milliStr, 10, 64)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("failed to convert Date to time.Time: %w", err)
|
||||
}
|
||||
return time.UnixMilli(milli), nil
|
||||
milli := v.Call("getTime").Float()
|
||||
return time.UnixMilli(int64(milli)), nil
|
||||
}
|
||||
|
||||
// timeToDate converts Go side's time.Time into Date object.
|
||||
func timeToDate(t time.Time) js.Value {
|
||||
milliStr := strconv.FormatInt(t.UnixMilli(), 10)
|
||||
return dateClass.New(numberClass.Call(milliStr))
|
||||
return dateClass.New(t.UnixMilli())
|
||||
}
|
||||
|
15
r2bucket.go
15
r2bucket.go
@ -99,11 +99,24 @@ func (opts *R2PutOptions) toJS() js.Value {
|
||||
}
|
||||
|
||||
// Put returns the result of `put` call to R2Bucket.
|
||||
// * This method copies all bytes into memory for implementation restriction.
|
||||
// * Body field of *R2Object is always nil for Put call.
|
||||
// * if a network error happens, returns error.
|
||||
func (r *r2Bucket) Put(key string, value io.ReadCloser, opts *R2PutOptions) (*R2Object, error) {
|
||||
/* TODO: implement this in FixedLengthStream: https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/#fixedlengthstream
|
||||
body := convertReaderToReadableStream(value)
|
||||
p := r.instance.Call("put", key, body, opts.toJS())
|
||||
streams := fixedLengthStreamClass.New(contentLength)
|
||||
rs := streams.Get("readable")
|
||||
body.Call("pipeTo", streams.Get("writable"))
|
||||
*/
|
||||
b, err := io.ReadAll(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer value.Close()
|
||||
ua := newUint8Array(len(b))
|
||||
js.CopyBytesToJS(ua, b)
|
||||
p := r.instance.Call("put", key, ua.Get("buffer"), opts.toJS())
|
||||
v, err := awaitPromise(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -19,6 +19,7 @@ type R2Objects struct {
|
||||
// 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) {
|
||||
global.Get("console").Call("log", global.Get("JSON").Call("stringify", v, nil, 2))
|
||||
objectsVal := v.Get("objects")
|
||||
objects := make([]*R2Object, objectsVal.Length())
|
||||
for i := 0; i < len(objects); i++ {
|
||||
@ -28,15 +29,15 @@ func toR2Objects(v js.Value) (*R2Objects, error) {
|
||||
}
|
||||
objects[i] = obj
|
||||
}
|
||||
prefixesVal := objectsVal.Get("delimitedPrefixes")
|
||||
prefixesVal := v.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")),
|
||||
Truncated: v.Get("truncated").Bool(),
|
||||
Cursor: maybeString(v.Get("cursor")),
|
||||
DelimitedPrefixes: prefixes,
|
||||
}, nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user