mirror of
https://github.com/restic/restic.git
synced 2026-10-20 00:08:11 +00:00
Update github.com/kurin/blazer to 0.5.0
This includes support for the upcoming B2 application keys feature.
This commit is contained in:
1 parent
67535e00a8
commit
7ffc03ff8f
25 files changed
+4654
-335
No files matched your search
+64
-30
@@ -29,11 +29,14 @@ type ErrorMessage struct {
|
||||
}
|
||||
|
||||
type AuthorizeAccountResponse struct {
|
||||
AccountID string `json:"accountId"`
|
||||
AuthToken string `json:"authorizationToken"`
|
||||
URI string `json:"apiUrl"`
|
||||
DownloadURI string `json:"downloadUrl"`
|
||||
MinPartSize int `json:"minimumPartSize"`
|
||||
AccountID string `json:"accountId"`
|
||||
AuthToken string `json:"authorizationToken"`
|
||||
URI string `json:"apiUrl"`
|
||||
DownloadURI string `json:"downloadUrl"`
|
||||
MinPartSize int `json:"minimumPartSize"`
|
||||
PartSize int `json:"recommendedPartSize"`
|
||||
AbsMinPartSize int `json:"absoluteMinimumPartSize"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
|
||||
type LifecycleRule struct {
|
||||
@@ -73,15 +76,8 @@ type ListBucketsResponse struct {
|
||||
}
|
||||
|
||||
type UpdateBucketRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
BucketID string `json:"bucketId"`
|
||||
// bucketName is a required field according to
|
||||
// https://www.backblaze.com/b2/docs/b2_update_bucket.html.
|
||||
//
|
||||
// However, actually setting it returns 400: unknown field in
|
||||
// com.backblaze.modules.b2.data.UpdateBucketRequest: bucketName
|
||||
//
|
||||
//Name string `json:"bucketName"`
|
||||
AccountID string `json:"accountId"`
|
||||
BucketID string `json:"bucketId"`
|
||||
Type string `json:"bucketType,omitempty"`
|
||||
Info map[string]string `json:"bucketInfo,omitempty"`
|
||||
LifecycleRules []LifecycleRule `json:"lifecycleRules,omitempty"`
|
||||
@@ -99,11 +95,7 @@ type GetUploadURLResponse struct {
|
||||
Token string `json:"authorizationToken"`
|
||||
}
|
||||
|
||||
type UploadFileResponse struct {
|
||||
FileID string `json:"fileId"`
|
||||
Timestamp int64 `json:"uploadTimestamp"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
type UploadFileResponse GetFileInfoResponse
|
||||
|
||||
type DeleteFileVersionRequest struct {
|
||||
Name string `json:"fileName"`
|
||||
@@ -206,20 +198,23 @@ type GetFileInfoRequest struct {
|
||||
}
|
||||
|
||||
type GetFileInfoResponse struct {
|
||||
FileID string `json:"fileId"`
|
||||
Name string `json:"fileName"`
|
||||
SHA1 string `json:"contentSha1"`
|
||||
Size int64 `json:"contentLength"`
|
||||
ContentType string `json:"contentType"`
|
||||
Info map[string]string `json:"fileInfo"`
|
||||
Action string `json:"action"`
|
||||
Timestamp int64 `json:"uploadTimestamp"`
|
||||
FileID string `json:"fileId,omitempty"`
|
||||
Name string `json:"fileName,omitempty"`
|
||||
AccountID string `json:"accountId,omitempty"`
|
||||
BucketID string `json:"bucketId,omitempty"`
|
||||
Size int64 `json:"contentLength,omitempty"`
|
||||
SHA1 string `json:"contentSha1,omitempty"`
|
||||
ContentType string `json:"contentType,omitempty"`
|
||||
Info map[string]string `json:"fileInfo,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Timestamp int64 `json:"uploadTimestamp,omitempty"`
|
||||
}
|
||||
|
||||
type GetDownloadAuthorizationRequest struct {
|
||||
BucketID string `json:"bucketId"`
|
||||
Prefix string `json:"fileNamePrefix"`
|
||||
Valid int `json:"validDurationInSeconds"`
|
||||
BucketID string `json:"bucketId"`
|
||||
Prefix string `json:"fileNamePrefix"`
|
||||
Valid int `json:"validDurationInSeconds"`
|
||||
ContentDisposition string `json:"b2ContentDisposition,omitempty"`
|
||||
}
|
||||
|
||||
type GetDownloadAuthorizationResponse struct {
|
||||
@@ -238,3 +233,42 @@ type ListUnfinishedLargeFilesResponse struct {
|
||||
Files []GetFileInfoResponse `json:"files"`
|
||||
Continuation string `json:"nextFileId"`
|
||||
}
|
||||
|
||||
type CreateKeyRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Name string `json:"keyName"`
|
||||
Valid int `json:"validDurationInSeconds,omitempty"`
|
||||
BucketID string `json:"bucketId,omitempty"`
|
||||
Prefix string `json:"namePrefix,omitempty"`
|
||||
}
|
||||
|
||||
type Key struct {
|
||||
ID string `json:"applicationKeyId"`
|
||||
Secret string `json:"applicationKey"`
|
||||
AccountID string `json:"accountId"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Name string `json:"keyName"`
|
||||
Expires int64 `json:"expirationTimestamp"`
|
||||
BucketID string `json:"bucketId"`
|
||||
Prefix string `json:"namePrefix"`
|
||||
}
|
||||
|
||||
type CreateKeyResponse Key
|
||||
|
||||
type DeleteKeyRequest struct {
|
||||
KeyID string `json:"applicationKeyId"`
|
||||
}
|
||||
|
||||
type DeleteKeyResponse Key
|
||||
|
||||
type ListKeysRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
Max int `json:"maxKeyCount,omitempty"`
|
||||
Next string `json:"startApplicationKeyId,omitempty"`
|
||||
}
|
||||
|
||||
type ListKeysResponse struct {
|
||||
Keys []Key `json:"keys"`
|
||||
Next string `json:"nextApplicationKeyId"`
|
||||
}
|
||||
+325
@@ -0,0 +1,325 @@
|
||||
// Copyright 2018, Google
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pyre
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/google/uuid"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
pb "github.com/kurin/blazer/internal/pyre/proto"
|
||||
)
|
||||
|
||||
type apiErr struct {
|
||||
Status int `json:"status"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func serveMuxOptions() []runtime.ServeMuxOption {
|
||||
return []runtime.ServeMuxOption{
|
||||
runtime.WithMarshalerOption("*", &runtime.JSONPb{}),
|
||||
runtime.WithProtoErrorHandler(func(ctx context.Context, mux *runtime.ServeMux, m runtime.Marshaler, rw http.ResponseWriter, req *http.Request, err error) {
|
||||
aErr := apiErr{
|
||||
Status: 400,
|
||||
Code: "uh oh",
|
||||
Message: err.Error(),
|
||||
}
|
||||
rw.WriteHeader(aErr.Status)
|
||||
if err := m.NewEncoder(rw).Encode(aErr); err != nil {
|
||||
fmt.Fprintln(os.Stdout, err)
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func getAuth(ctx context.Context) (string, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return "", errors.New("no metadata")
|
||||
}
|
||||
data := md.Get("authorization")
|
||||
if len(data) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return data[0], nil
|
||||
}
|
||||
|
||||
func RegisterServerOnMux(ctx context.Context, srv *Server, mux *http.ServeMux) error {
|
||||
rmux := runtime.NewServeMux(serveMuxOptions()...)
|
||||
l, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gsrv := grpc.NewServer()
|
||||
if err := pb.RegisterPyreServiceHandlerFromEndpoint(ctx, rmux, l.Addr().String(), []grpc.DialOption{grpc.WithInsecure()}); err != nil {
|
||||
return err
|
||||
}
|
||||
pb.RegisterPyreServiceServer(gsrv, srv)
|
||||
mux.Handle("/b2api/v1/", rmux)
|
||||
go gsrv.Serve(l)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
gsrv.GracefulStop()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
type AccountManager interface {
|
||||
Authorize(acct, key string) (string, error)
|
||||
CheckCreds(token, api string) error
|
||||
APIRoot(acct string) string
|
||||
DownloadRoot(acct string) string
|
||||
UploadPartHost(fileID string) (string, error)
|
||||
UploadHost(id string) (string, error)
|
||||
Sizes(acct string) (recommended, minimum int32)
|
||||
}
|
||||
|
||||
type BucketManager interface {
|
||||
AddBucket(id, name string, bs []byte) error
|
||||
RemoveBucket(id string) error
|
||||
UpdateBucket(id string, rev int, bs []byte) error
|
||||
ListBuckets(acct string) ([][]byte, error)
|
||||
GetBucket(id string) ([]byte, error)
|
||||
}
|
||||
|
||||
type LargeFileOrganizer interface {
|
||||
Start(bucketID, fileName, fileID string, bs []byte) error
|
||||
Get(fileID string) ([]byte, error)
|
||||
Parts(fileID string) ([]string, error)
|
||||
Finish(fileID string) error
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Account AccountManager
|
||||
Bucket BucketManager
|
||||
LargeFile LargeFileOrganizer
|
||||
List ListManager
|
||||
}
|
||||
|
||||
func (s *Server) AuthorizeAccount(ctx context.Context, req *pb.AuthorizeAccountRequest) (*pb.AuthorizeAccountResponse, error) {
|
||||
auth, err := getAuth(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !strings.HasPrefix(auth, "Basic ") {
|
||||
return nil, errors.New("basic auth required")
|
||||
}
|
||||
auth = strings.TrimPrefix(auth, "Basic ")
|
||||
bs, err := base64.StdEncoding.DecodeString(auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
split := strings.Split(string(bs), ":")
|
||||
if len(split) != 2 {
|
||||
return nil, errors.New("bad auth")
|
||||
}
|
||||
acct, key := split[0], split[1]
|
||||
token, err := s.Account.Authorize(acct, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rec, min := s.Account.Sizes(acct)
|
||||
return &pb.AuthorizeAccountResponse{
|
||||
AuthorizationToken: token,
|
||||
ApiUrl: s.Account.APIRoot(acct),
|
||||
DownloadUrl: s.Account.DownloadRoot(acct),
|
||||
RecommendedPartSize: rec,
|
||||
MinimumPartSize: rec,
|
||||
AbsoluteMinimumPartSize: min,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListBuckets(ctx context.Context, req *pb.ListBucketsRequest) (*pb.ListBucketsResponse, error) {
|
||||
resp := &pb.ListBucketsResponse{}
|
||||
buckets, err := s.Bucket.ListBuckets(req.AccountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, bs := range buckets {
|
||||
var bucket pb.Bucket
|
||||
if err := proto.Unmarshal(bs, &bucket); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Buckets = append(resp.Buckets, &bucket)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateBucket(ctx context.Context, req *pb.Bucket) (*pb.Bucket, error) {
|
||||
req.BucketId = uuid.New().String()
|
||||
bs, err := proto.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.Bucket.AddBucket(req.BucketId, req.BucketName, bs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteBucket(ctx context.Context, req *pb.Bucket) (*pb.Bucket, error) {
|
||||
bs, err := s.Bucket.GetBucket(req.BucketId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var bucket pb.Bucket
|
||||
if err := proto.Unmarshal(bs, &bucket); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.Bucket.RemoveBucket(req.BucketId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &bucket, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetUploadUrl(ctx context.Context, req *pb.GetUploadUrlRequest) (*pb.GetUploadUrlResponse, error) {
|
||||
host, err := s.Account.UploadHost(req.BucketId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.GetUploadUrlResponse{
|
||||
UploadUrl: fmt.Sprintf("%s/b2api/v1/b2_upload_file/%s", host, req.BucketId),
|
||||
BucketId: req.BucketId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) StartLargeFile(ctx context.Context, req *pb.StartLargeFileRequest) (*pb.StartLargeFileResponse, error) {
|
||||
fileID := uuid.New().String()
|
||||
resp := &pb.StartLargeFileResponse{
|
||||
FileId: fileID,
|
||||
FileName: req.FileName,
|
||||
BucketId: req.BucketId,
|
||||
ContentType: req.ContentType,
|
||||
FileInfo: req.FileInfo,
|
||||
}
|
||||
bs, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.LargeFile.Start(req.BucketId, req.FileName, fileID, bs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetUploadPartUrl(ctx context.Context, req *pb.GetUploadPartUrlRequest) (*pb.GetUploadPartUrlResponse, error) {
|
||||
host, err := s.Account.UploadPartHost(req.FileId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.GetUploadPartUrlResponse{
|
||||
UploadUrl: fmt.Sprintf("%s/b2api/v1/b2_upload_part/%s", host, req.FileId),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) FinishLargeFile(ctx context.Context, req *pb.FinishLargeFileRequest) (*pb.FinishLargeFileResponse, error) {
|
||||
parts, err := s.LargeFile.Parts(req.FileId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !reflect.DeepEqual(parts, req.PartSha1Array) {
|
||||
return nil, errors.New("sha1 array mismatch")
|
||||
}
|
||||
if err := s.LargeFile.Finish(req.FileId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.FinishLargeFileResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListFileVersions(ctx context.Context, req *pb.ListFileVersionsRequest) (*pb.ListFileVersionsResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
//type objTuple struct {
|
||||
// name, version string
|
||||
//}
|
||||
|
||||
type ListManager interface {
|
||||
// NextN returns the next n objects, sorted by lexicographical order by name,
|
||||
// beginning at and including, if it exists, fileName. If withPrefix is not
|
||||
// empty, it only returns names that begin with that prefix. If skipPrefix
|
||||
// is not empty, then the no files with that prefix are returned. If the two
|
||||
// conflict, skipPrefix wins (i.e., do not return the entry).
|
||||
//
|
||||
// If fewer than n entries are returned, this signifies that no more names
|
||||
// exist that meet these criteria.
|
||||
NextN(bucketID, fileName, withPrefix, skipPrefix string, n int) ([]VersionedObject, error)
|
||||
}
|
||||
|
||||
type VersionedObject interface {
|
||||
Name() string
|
||||
NextNVersions(begin string, n int) ([]string, error)
|
||||
}
|
||||
|
||||
//func getNextObjects(lm ListManager, bucket, name, prefix, delimiter string, n int) ([]VersionedObject, error) {
|
||||
// if delimiter == "" {
|
||||
// return lm.NextN(bucket, name, prefix, "", n)
|
||||
// }
|
||||
// afterPfx := strings.TrimPrefix(name, prefix)
|
||||
// i := strings.Index(afterPfx, delimiter)
|
||||
// if i == 0 {
|
||||
//
|
||||
// }
|
||||
// if i < 0 {
|
||||
// return lm.NextN(bucket, name, prefix, "", n)
|
||||
// }
|
||||
// skipPfx := name[:len(prefix)+i]
|
||||
// // TO
|
||||
//}
|
||||
//
|
||||
//func listFileVersions(lm ListManager, bucket, name, version, prefix, delimiter string, n int) ([]objTuple, error) {
|
||||
// var tups []objTuple
|
||||
// var got int
|
||||
// for {
|
||||
// objs, err := getNextObjects(bucket, name, prefix, delimiter, n-got)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if len(objs) == 0 {
|
||||
// break
|
||||
// }
|
||||
// for _, o := range objs {
|
||||
// var begin string
|
||||
// if len(tups) == 0 {
|
||||
// begin = version
|
||||
// }
|
||||
// vers, err := lm.NextNVersions(begin, n-got)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// got += len(vers)
|
||||
// for _, ver := range vers {
|
||||
// tups = append(tups, objTuple{name: o.Name(), version: ver})
|
||||
// }
|
||||
// if got >= n {
|
||||
// return tups[:n], nil
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return tups, nil
|
||||
//}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
// Copyright 2018, Google
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pyre
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DownloadableObject interface {
|
||||
Size() int64
|
||||
Reader() io.ReaderAt
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type DownloadManager interface {
|
||||
ObjectByName(bucketID, name string) (DownloadableObject, error)
|
||||
GetBucketID(bucket string) (string, error)
|
||||
GetBucket(id string) ([]byte, error)
|
||||
}
|
||||
|
||||
type downloadServer struct {
|
||||
dm DownloadManager
|
||||
}
|
||||
|
||||
type downloadRequest struct {
|
||||
off, n int64
|
||||
}
|
||||
|
||||
func parseDownloadHeaders(r *http.Request) (*downloadRequest, error) {
|
||||
rang := r.Header.Get("Range")
|
||||
if rang == "" {
|
||||
return &downloadRequest{}, nil
|
||||
}
|
||||
if !strings.HasPrefix(rang, "bytes=") {
|
||||
return nil, fmt.Errorf("unknown range format: %q", rang)
|
||||
}
|
||||
rang = strings.TrimPrefix(rang, "bytes=")
|
||||
if !strings.Contains(rang, "-") {
|
||||
return nil, fmt.Errorf("unknown range format: %q", rang)
|
||||
}
|
||||
parts := strings.Split(rang, "-")
|
||||
off, err := strconv.ParseInt(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
end, err := strconv.ParseInt(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &downloadRequest{
|
||||
off: off,
|
||||
n: (end + 1) - off,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fs *downloadServer) serveWholeObject(rw http.ResponseWriter, obj DownloadableObject) {
|
||||
rw.Header().Set("Content-Length", fmt.Sprintf("%d", obj.Size()))
|
||||
sr := io.NewSectionReader(obj.Reader(), 0, obj.Size())
|
||||
if _, err := io.Copy(rw, sr); err != nil {
|
||||
http.Error(rw, err.Error(), 503)
|
||||
fmt.Println("no reader", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *downloadServer) servePartialObject(rw http.ResponseWriter, obj DownloadableObject, off, len int64) {
|
||||
if off >= obj.Size() {
|
||||
http.Error(rw, "hell naw", 416)
|
||||
fmt.Printf("range not good (%d-%d for %d)\n", off, len, obj.Size())
|
||||
return
|
||||
}
|
||||
if off+len > obj.Size() {
|
||||
len = obj.Size() - off
|
||||
}
|
||||
sr := io.NewSectionReader(obj.Reader(), off, len)
|
||||
rw.Header().Set("Content-Length", fmt.Sprintf("%d", len))
|
||||
rw.WriteHeader(206) // this goes after headers are set
|
||||
if _, err := io.Copy(rw, sr); err != nil {
|
||||
fmt.Println("bad read:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *downloadServer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
req, err := parseDownloadHeaders(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), 503)
|
||||
fmt.Println("weird header")
|
||||
return
|
||||
}
|
||||
path := strings.TrimPrefix(r.URL.Path, "/")
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) < 3 {
|
||||
http.Error(rw, err.Error(), 404)
|
||||
fmt.Println("weird file")
|
||||
return
|
||||
}
|
||||
bucket := parts[1]
|
||||
bid, err := fs.dm.GetBucketID(bucket)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), 503)
|
||||
fmt.Println("no bucket:", err)
|
||||
return
|
||||
}
|
||||
file := strings.Join(parts[2:], "/")
|
||||
obj, err := fs.dm.ObjectByName(bid, file)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), 503)
|
||||
fmt.Println("no reader", err)
|
||||
return
|
||||
}
|
||||
defer obj.Close()
|
||||
if req.off == 0 && req.n == 0 {
|
||||
fs.serveWholeObject(rw, obj)
|
||||
return
|
||||
}
|
||||
fs.servePartialObject(rw, obj, req.off, req.n)
|
||||
}
|
||||
|
||||
func RegisterDownloadManagerOnMux(d DownloadManager, mux *http.ServeMux) {
|
||||
mux.Handle("/file/", &downloadServer{dm: d})
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// Copyright 2018, Google
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pyre
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const uploadFilePartPrefix = "/b2api/v1/b2_upload_part/"
|
||||
|
||||
type LargeFileManager interface {
|
||||
PartWriter(id string, part int) (io.WriteCloser, error)
|
||||
}
|
||||
|
||||
type largeFileServer struct {
|
||||
fm LargeFileManager
|
||||
}
|
||||
|
||||
type uploadPartRequest struct {
|
||||
ID string `json:"fileId"`
|
||||
Part int `json:"partNumber"`
|
||||
Size int64 `json:"contentLength"`
|
||||
Hash string `json:"contentSha1"`
|
||||
}
|
||||
|
||||
func parseUploadPartHeaders(r *http.Request) (uploadPartRequest, error) {
|
||||
var ur uploadPartRequest
|
||||
ur.Hash = r.Header.Get("X-Bz-Content-Sha1")
|
||||
part, err := strconv.ParseInt(r.Header.Get("X-Bz-Part-Number"), 10, 64)
|
||||
if err != nil {
|
||||
return ur, err
|
||||
}
|
||||
ur.Part = int(part)
|
||||
size, err := strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
return ur, err
|
||||
}
|
||||
ur.Size = size
|
||||
ur.ID = strings.TrimPrefix(r.URL.Path, uploadFilePartPrefix)
|
||||
return ur, nil
|
||||
}
|
||||
|
||||
func (fs *largeFileServer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
req, err := parseUploadPartHeaders(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), 500)
|
||||
fmt.Println("oh no")
|
||||
return
|
||||
}
|
||||
w, err := fs.fm.PartWriter(req.ID, req.Part)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), 500)
|
||||
fmt.Println("oh no")
|
||||
return
|
||||
}
|
||||
if _, err := io.Copy(w, io.LimitReader(r.Body, req.Size)); err != nil {
|
||||
w.Close()
|
||||
http.Error(rw, err.Error(), 500)
|
||||
fmt.Println("oh no")
|
||||
return
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
http.Error(rw, err.Error(), 500)
|
||||
fmt.Println("oh no")
|
||||
return
|
||||
}
|
||||
if err := json.NewEncoder(rw).Encode(req); err != nil {
|
||||
fmt.Println("oh no")
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterLargeFileManagerOnMux(f LargeFileManager, mux *http.ServeMux) {
|
||||
mux.Handle(uploadFilePartPrefix, &largeFileServer{fm: f})
|
||||
}
|
||||
+1896
File diff suppressed because it is too large.
Load diff
+484
@@ -0,0 +1,484 @@
|
||||
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
|
||||
// source: proto/pyre.proto
|
||||
|
||||
/*
|
||||
Package pyre_proto is a reverse proxy.
|
||||
|
||||
It translates gRPC into RESTful JSON APIs.
|
||||
*/
|
||||
package pyre_proto
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/utilities"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var _ codes.Code
|
||||
var _ io.Reader
|
||||
var _ status.Status
|
||||
var _ = runtime.String
|
||||
var _ = utilities.NewDoubleArray
|
||||
|
||||
func request_PyreService_AuthorizeAccount_0(ctx context.Context, marshaler runtime.Marshaler, client PyreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq AuthorizeAccountRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := client.AuthorizeAccount(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_PyreService_ListBuckets_0(ctx context.Context, marshaler runtime.Marshaler, client PyreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListBucketsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.ListBuckets(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_PyreService_CreateBucket_0(ctx context.Context, marshaler runtime.Marshaler, client PyreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq Bucket
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.CreateBucket(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_PyreService_DeleteBucket_0(ctx context.Context, marshaler runtime.Marshaler, client PyreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq Bucket
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.DeleteBucket(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_PyreService_GetUploadUrl_0(ctx context.Context, marshaler runtime.Marshaler, client PyreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetUploadUrlRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.GetUploadUrl(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_PyreService_StartLargeFile_0(ctx context.Context, marshaler runtime.Marshaler, client PyreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq StartLargeFileRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.StartLargeFile(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_PyreService_GetUploadPartUrl_0(ctx context.Context, marshaler runtime.Marshaler, client PyreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetUploadPartUrlRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.GetUploadPartUrl(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_PyreService_FinishLargeFile_0(ctx context.Context, marshaler runtime.Marshaler, client PyreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq FinishLargeFileRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.FinishLargeFile(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_PyreService_ListFileVersions_0(ctx context.Context, marshaler runtime.Marshaler, client PyreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListFileVersionsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.ListFileVersions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterPyreServiceHandlerFromEndpoint is same as RegisterPyreServiceHandler but
|
||||
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||
func RegisterPyreServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||
conn, err := grpc.Dial(endpoint, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
return RegisterPyreServiceHandler(ctx, mux, conn)
|
||||
}
|
||||
|
||||
// RegisterPyreServiceHandler registers the http handlers for service PyreService to "mux".
|
||||
// The handlers forward requests to the grpc endpoint over "conn".
|
||||
func RegisterPyreServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||
return RegisterPyreServiceHandlerClient(ctx, mux, NewPyreServiceClient(conn))
|
||||
}
|
||||
|
||||
// RegisterPyreServiceHandlerClient registers the http handlers for service PyreService
|
||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "PyreServiceClient".
|
||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "PyreServiceClient"
|
||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||
// "PyreServiceClient" to call the correct interceptors.
|
||||
func RegisterPyreServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client PyreServiceClient) error {
|
||||
|
||||
mux.Handle("GET", pattern_PyreService_AuthorizeAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
go func(done <-chan struct{}, closed <-chan bool) {
|
||||
select {
|
||||
case <-done:
|
||||
case <-closed:
|
||||
cancel()
|
||||
}
|
||||
}(ctx.Done(), cn.CloseNotify())
|
||||
}
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_PyreService_AuthorizeAccount_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PyreService_AuthorizeAccount_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_PyreService_ListBuckets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
go func(done <-chan struct{}, closed <-chan bool) {
|
||||
select {
|
||||
case <-done:
|
||||
case <-closed:
|
||||
cancel()
|
||||
}
|
||||
}(ctx.Done(), cn.CloseNotify())
|
||||
}
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_PyreService_ListBuckets_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PyreService_ListBuckets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_PyreService_CreateBucket_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
go func(done <-chan struct{}, closed <-chan bool) {
|
||||
select {
|
||||
case <-done:
|
||||
case <-closed:
|
||||
cancel()
|
||||
}
|
||||
}(ctx.Done(), cn.CloseNotify())
|
||||
}
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_PyreService_CreateBucket_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PyreService_CreateBucket_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_PyreService_DeleteBucket_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
go func(done <-chan struct{}, closed <-chan bool) {
|
||||
select {
|
||||
case <-done:
|
||||
case <-closed:
|
||||
cancel()
|
||||
}
|
||||
}(ctx.Done(), cn.CloseNotify())
|
||||
}
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_PyreService_DeleteBucket_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PyreService_DeleteBucket_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_PyreService_GetUploadUrl_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
go func(done <-chan struct{}, closed <-chan bool) {
|
||||
select {
|
||||
case <-done:
|
||||
case <-closed:
|
||||
cancel()
|
||||
}
|
||||
}(ctx.Done(), cn.CloseNotify())
|
||||
}
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_PyreService_GetUploadUrl_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PyreService_GetUploadUrl_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_PyreService_StartLargeFile_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
go func(done <-chan struct{}, closed <-chan bool) {
|
||||
select {
|
||||
case <-done:
|
||||
case <-closed:
|
||||
cancel()
|
||||
}
|
||||
}(ctx.Done(), cn.CloseNotify())
|
||||
}
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_PyreService_StartLargeFile_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PyreService_StartLargeFile_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_PyreService_GetUploadPartUrl_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
go func(done <-chan struct{}, closed <-chan bool) {
|
||||
select {
|
||||
case <-done:
|
||||
case <-closed:
|
||||
cancel()
|
||||
}
|
||||
}(ctx.Done(), cn.CloseNotify())
|
||||
}
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_PyreService_GetUploadPartUrl_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PyreService_GetUploadPartUrl_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_PyreService_FinishLargeFile_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
go func(done <-chan struct{}, closed <-chan bool) {
|
||||
select {
|
||||
case <-done:
|
||||
case <-closed:
|
||||
cancel()
|
||||
}
|
||||
}(ctx.Done(), cn.CloseNotify())
|
||||
}
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_PyreService_FinishLargeFile_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PyreService_FinishLargeFile_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_PyreService_ListFileVersions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
go func(done <-chan struct{}, closed <-chan bool) {
|
||||
select {
|
||||
case <-done:
|
||||
case <-closed:
|
||||
cancel()
|
||||
}
|
||||
}(ctx.Done(), cn.CloseNotify())
|
||||
}
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_PyreService_ListFileVersions_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_PyreService_ListFileVersions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pattern_PyreService_AuthorizeAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"b2api", "v1", "b2_authorize_account"}, ""))
|
||||
|
||||
pattern_PyreService_ListBuckets_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"b2api", "v1", "b2_list_buckets"}, ""))
|
||||
|
||||
pattern_PyreService_CreateBucket_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"b2api", "v1", "b2_create_bucket"}, ""))
|
||||
|
||||
pattern_PyreService_DeleteBucket_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"b2api", "v1", "b2_delete_bucket"}, ""))
|
||||
|
||||
pattern_PyreService_GetUploadUrl_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"b2api", "v1", "b2_get_upload_url"}, ""))
|
||||
|
||||
pattern_PyreService_StartLargeFile_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"b2api", "v1", "b2_start_large_file"}, ""))
|
||||
|
||||
pattern_PyreService_GetUploadPartUrl_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"b2api", "v1", "b2_get_upload_part_url"}, ""))
|
||||
|
||||
pattern_PyreService_FinishLargeFile_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"b2api", "v1", "b2_finish_large_file"}, ""))
|
||||
|
||||
pattern_PyreService_ListFileVersions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"b2api", "v1", "b2_list_file_versions"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
forward_PyreService_AuthorizeAccount_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_PyreService_ListBuckets_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_PyreService_CreateBucket_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_PyreService_DeleteBucket_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_PyreService_GetUploadUrl_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_PyreService_StartLargeFile_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_PyreService_GetUploadPartUrl_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_PyreService_FinishLargeFile_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_PyreService_ListFileVersions_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
// Copyright 2018, Google
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
|
||||
package pyre.proto;
|
||||
|
||||
message AuthorizeAccountRequest {}
|
||||
|
||||
message AuthorizeAccountResponse {
|
||||
// The identifier for the account.
|
||||
string account_id = 1;
|
||||
// An authorization token to use with all calls, other than
|
||||
// b2_authorize_account, that need an Authorization header. This
|
||||
// authorization token is valid for at most 24 hours.
|
||||
string authorization_token = 2;
|
||||
// The base URL to use for all API calls except for uploading and downloading
|
||||
// files.
|
||||
string api_url = 3;
|
||||
// The base URL to use for downloading files.
|
||||
string download_url = 4;
|
||||
// The recommended size for each part of a large file. We recommend using
|
||||
// this part size for optimal upload performance.
|
||||
int32 recommended_part_size = 5;
|
||||
// The smallest possible size of a part of a large file (except the last
|
||||
// one). This is smaller than the recommended part size. If you use it, you
|
||||
// may find that it takes longer overall to upload a large file.
|
||||
int32 absolute_minimum_part_size = 6;
|
||||
int32 minimum_part_size = 7; // alias for recommended_part_size
|
||||
}
|
||||
|
||||
message ListBucketsRequest {
|
||||
// The ID of your account.
|
||||
string account_id = 1;
|
||||
// When specified, the result will be a list containing just this bucket, if
|
||||
// it's present in the account, or no buckets if the account does not have a
|
||||
// bucket with this ID.
|
||||
string bucket_id = 2;
|
||||
// When specified, the result will be a list containing just this bucket, if
|
||||
// it's present in the account, or no buckets if the account does not have a
|
||||
// bucket with this ID.
|
||||
string bucket_name = 3;
|
||||
// If present, B2 will use it as a filter for bucket types returned in the
|
||||
// list buckets response. If not present, only buckets with bucket types
|
||||
// "allPublic", "allPrivate" and "snapshot" will be returned. A special
|
||||
// filter value of ["all"] will return all bucket types.
|
||||
//
|
||||
// If present, it must be in the form of a json array of strings containing
|
||||
// valid bucket types in quotes and separated by a comma. Valid bucket types
|
||||
// include "allPrivate", "allPublic", "snapshot", and other values added in
|
||||
// the future.
|
||||
//
|
||||
// A bad request error will be returned if "all" is used with other bucket
|
||||
// types, this field is empty, or invalid bucket types are requested.
|
||||
repeated string bucket_types = 4;
|
||||
}
|
||||
|
||||
message LifecycleRule {
|
||||
// After a file is uploaded, the number of days before it can be hidden.
|
||||
int32 days_from_uploading_to_hiding = 1;
|
||||
// After a file is hidden, the number of days before it can be deleted.
|
||||
int32 days_from_hiding_to_deleting = 2;
|
||||
// The rule applies to files whose names start with this prefix.
|
||||
string file_name_prefix = 3;
|
||||
}
|
||||
|
||||
message CorsRule {
|
||||
// A name for humans to recognize the rule in a user interface. Names must be
|
||||
// unique within a bucket. Names can consist of upper-case and lower-case
|
||||
// English letters, numbers, and "-". No other characters are allowed. A name
|
||||
// must be at least 6 characters long, and can be at most 50 characters long.
|
||||
// These are all allowed names: myPhotosSite, allowAnyHttps,
|
||||
// backblaze-images. Names that start with "b2-" are reserved for Backblaze
|
||||
// use.
|
||||
string cors_rule_name = 1;
|
||||
// A non-empty list specifying which origins the rule covers. Each value may
|
||||
// have one of many formats:
|
||||
//
|
||||
// * The origin can be fully specified, such as http://www.example.com:8180
|
||||
// or https://www.example.com:4433.
|
||||
//
|
||||
// * The origin can omit a default port, such as https://www.example.com.
|
||||
//
|
||||
// * The origin may have a single '*' as part of the domain name, such as
|
||||
// https://*.example.com, https://*:8443 or https://*.
|
||||
//
|
||||
// * The origin may be 'https' to match any origin that uses HTTPS. (This is
|
||||
// broader than 'https://*' because it matches any port.)
|
||||
//
|
||||
// * Finally, the origin can be a single '*' to match any origin.
|
||||
//
|
||||
// If any entry is "*", it must be the only entry. There can be at most one
|
||||
// "https" entry and no entry after it may start with "https:".
|
||||
repeated string allowed_origins = 2;
|
||||
// A list specifying which operations the rule allows. At least one value
|
||||
// must be specified. All values must be from the following list. More values
|
||||
// may be added to this list at any time.
|
||||
//
|
||||
// b2_download_file_by_name
|
||||
// b2_download_file_by_id
|
||||
// b2_upload_file
|
||||
// b2_upload_part
|
||||
repeated string allowed_operations = 3;
|
||||
// If present, this is a list of headers that are allowed in a pre-flight
|
||||
// OPTIONS's request's Access-Control-Request-Headers header value. Each
|
||||
// value may have one of many formats:
|
||||
//
|
||||
// * It may be a complete header name, such as x-bz-content-sha1.
|
||||
//
|
||||
// * It may end with an asterisk, such as x-bz-info-*.
|
||||
//
|
||||
// * Finally, it may be a single '*' to match any header.
|
||||
//
|
||||
// If any entry is "*", it must be the only entry in the list. If this list
|
||||
// is missing, it is be treated as if it is a list with no entries.
|
||||
repeated string allowed_headers = 4;
|
||||
// If present, this is a list of headers that may be exposed to an
|
||||
// application inside the client (eg. exposed to Javascript in a browser).
|
||||
// Each entry in the list must be a complete header name (eg.
|
||||
// "x-bz-content-sha1"). If this list is missing or empty, no headers will be
|
||||
// exposed.
|
||||
repeated string expose_headers = 5;
|
||||
// This specifies the maximum number of seconds that a browser may cache the
|
||||
// response to a preflight request. The value must not be negative and it
|
||||
// must not be more than 86,400 seconds (one day).
|
||||
int32 max_age_seconds = 6;
|
||||
}
|
||||
|
||||
message Bucket {
|
||||
string account_id = 1;
|
||||
string bucket_id = 2;
|
||||
string bucket_name = 3;
|
||||
string bucket_type = 4;
|
||||
map<string, string> bucket_info = 5;
|
||||
repeated CorsRule cores_rules = 6;
|
||||
repeated LifecycleRule lifecycle_rules = 7;
|
||||
int32 revision = 8;
|
||||
}
|
||||
|
||||
message ListBucketsResponse {
|
||||
repeated Bucket buckets = 1;
|
||||
}
|
||||
|
||||
message GetUploadUrlRequest {
|
||||
string bucket_id = 1;
|
||||
}
|
||||
|
||||
message GetUploadUrlResponse {
|
||||
string bucket_id = 1;
|
||||
string upload_url = 2;
|
||||
string authorization_token = 3;
|
||||
}
|
||||
|
||||
message UploadFileResponse {
|
||||
string file_id = 1;
|
||||
string file_name = 2;
|
||||
string account_id = 3;
|
||||
string bucket_id = 4;
|
||||
int32 content_length = 5;
|
||||
string content_sha1 = 6;
|
||||
string content_type = 7;
|
||||
map<string, string> file_info = 8;
|
||||
string action = 9;
|
||||
int64 upload_timestamp = 10;
|
||||
}
|
||||
|
||||
message StartLargeFileRequest {
|
||||
string bucket_id = 1;
|
||||
string file_name = 2;
|
||||
string content_type = 3;
|
||||
map<string, string> file_info = 4;
|
||||
}
|
||||
|
||||
message StartLargeFileResponse {
|
||||
string file_id = 1;
|
||||
string file_name = 2;
|
||||
string account_id = 3;
|
||||
string bucket_id = 4;
|
||||
string content_type = 5;
|
||||
map<string, string> file_info = 6;
|
||||
int64 upload_timestamp = 7;
|
||||
}
|
||||
|
||||
message GetUploadPartUrlRequest {
|
||||
string file_id = 1;
|
||||
}
|
||||
|
||||
message GetUploadPartUrlResponse {
|
||||
string file_id = 1;
|
||||
string upload_url = 2;
|
||||
string authorization_token = 3;
|
||||
}
|
||||
|
||||
message FinishLargeFileRequest {
|
||||
string file_id = 1;
|
||||
repeated string part_sha1_array = 2;
|
||||
// string sha1 = 3;
|
||||
}
|
||||
|
||||
message FinishLargeFileResponse {
|
||||
string file_id = 1;
|
||||
string file_name = 2;
|
||||
string account_id = 3;
|
||||
string bucket_id = 4;
|
||||
int64 content_length = 5;
|
||||
string content_sha1 = 6; // always "none"
|
||||
string content_type = 7;
|
||||
map<string, string> file_info = 8;
|
||||
string action = 9;
|
||||
int64 upload_timestamp = 10;
|
||||
}
|
||||
|
||||
message ListFileVersionsRequest {
|
||||
string bucket_id = 1;
|
||||
string start_file_name = 2;
|
||||
string start_file_id = 3;
|
||||
int32 max_file_count = 4;
|
||||
string prefix = 5;
|
||||
string delimiter = 6;
|
||||
}
|
||||
|
||||
message ListFileVersionsResponse {
|
||||
repeated File files = 1;
|
||||
string next_file_name = 2;
|
||||
string next_file_id = 3;
|
||||
}
|
||||
|
||||
message File {
|
||||
string file_id = 1;
|
||||
string file_name = 2;
|
||||
int64 content_length = 3;
|
||||
string content_type = 4;
|
||||
string content_sha1 = 5;
|
||||
map<string, string> file_info = 6;
|
||||
string action = 7;
|
||||
int64 size = 8;
|
||||
int64 upload_timestamp = 9;
|
||||
}
|
||||
|
||||
service PyreService {
|
||||
// Used to log in to the B2 API. Returns an authorization token that can be
|
||||
// used for account-level operations, and a URL that should be used as the
|
||||
// base URL for subsequent API calls.
|
||||
rpc AuthorizeAccount(AuthorizeAccountRequest) returns (AuthorizeAccountResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/b2api/v1/b2_authorize_account"
|
||||
};
|
||||
}
|
||||
|
||||
// Lists buckets associated with an account, in alphabetical order by bucket
|
||||
// name.
|
||||
rpc ListBuckets(ListBucketsRequest) returns (ListBucketsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/b2api/v1/b2_list_buckets"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Creates a new bucket. A bucket belongs to the account used to create it.
|
||||
//
|
||||
// Buckets can be named. The name must be globally unique. No account can use
|
||||
// a bucket with the same name. Buckets are assigned a unique bucketId which
|
||||
// is used when uploading, downloading, or deleting files.
|
||||
//
|
||||
// There is a limit of 100 buckets per account.
|
||||
rpc CreateBucket(Bucket) returns (Bucket) {
|
||||
option (google.api.http) = {
|
||||
post: "/b2api/v1/b2_create_bucket"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Deletes the bucket specified. Only buckets that contain no version of any
|
||||
// files can be deleted.
|
||||
rpc DeleteBucket(Bucket) returns (Bucket) {
|
||||
option (google.api.http) = {
|
||||
post: "/b2api/v1/b2_delete_bucket"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetUploadUrl(GetUploadUrlRequest) returns (GetUploadUrlResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/b2api/v1/b2_get_upload_url"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Prepares for uploading the parts of a large file.
|
||||
rpc StartLargeFile(StartLargeFileRequest) returns (StartLargeFileResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/b2api/v1/b2_start_large_file"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Gets an URL to use for uploading parts of a large file.
|
||||
rpc GetUploadPartUrl(GetUploadPartUrlRequest) returns (GetUploadPartUrlResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/b2api/v1/b2_get_upload_part_url"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Converts the parts that have been uploaded into a single B2 file.
|
||||
rpc FinishLargeFile(FinishLargeFileRequest) returns (FinishLargeFileResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/b2api/v1/b2_finish_large_file"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Lists all of the versions of all of the files contained in one bucket, in
|
||||
// alphabetical order by file name, and by reverse of date/time uploaded for
|
||||
// versions of files with the same name.
|
||||
rpc ListFileVersions(ListFileVersionsRequest) returns (ListFileVersionsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/b2api/v1/b2_list_file_versions"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright 2018, Google
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package pyre provides a gRPC-based implementation of B2, as well as a
|
||||
// RESTful gateway on top of it.
|
||||
package pyre
|
||||
|
||||
//go:generate protoc -I/usr/local/include -I. -I$GOPATH/src -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:. proto/pyre.proto
|
||||
//go:generate protoc -I/usr/local/include -I. -I$GOPATH/src -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=plugins=grpc:. proto/pyre.proto
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Copyright 2018, Google
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pyre
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kurin/blazer/internal/b2types"
|
||||
)
|
||||
|
||||
const uploadFilePrefix = "/b2api/v1/b2_upload_file/"
|
||||
|
||||
type SimpleFileManager interface {
|
||||
Writer(bucket, name, id string) (io.WriteCloser, error)
|
||||
}
|
||||
|
||||
type simpleFileServer struct {
|
||||
fm SimpleFileManager
|
||||
}
|
||||
|
||||
type uploadRequest struct {
|
||||
name string
|
||||
contentType string
|
||||
size int64
|
||||
sha1 string
|
||||
bucket string
|
||||
info map[string]string
|
||||
}
|
||||
|
||||
func parseUploadHeaders(r *http.Request) (*uploadRequest, error) {
|
||||
ur := &uploadRequest{info: make(map[string]string)}
|
||||
ur.name = r.Header.Get("X-Bz-File-Name")
|
||||
ur.contentType = r.Header.Get("Content-Type")
|
||||
ur.sha1 = r.Header.Get("X-Bz-Content-Sha1")
|
||||
size, err := strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ur.size = size
|
||||
for k := range r.Header {
|
||||
if !strings.HasPrefix("X-Bz-Info-", k) {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimPrefix("X-Bz-Info-", k)
|
||||
ur.info[name] = r.Header.Get(k)
|
||||
}
|
||||
ur.bucket = strings.TrimPrefix(r.URL.Path, uploadFilePrefix)
|
||||
return ur, nil
|
||||
}
|
||||
|
||||
func (fs *simpleFileServer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
req, err := parseUploadHeaders(r)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), 500)
|
||||
fmt.Println("oh no")
|
||||
return
|
||||
}
|
||||
id := uuid.New().String()
|
||||
w, err := fs.fm.Writer(req.bucket, req.name, id)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), 500)
|
||||
fmt.Println("oh no")
|
||||
return
|
||||
}
|
||||
if _, err := io.Copy(w, io.LimitReader(r.Body, req.size)); err != nil {
|
||||
w.Close()
|
||||
http.Error(rw, err.Error(), 500)
|
||||
fmt.Println("oh no")
|
||||
return
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
http.Error(rw, err.Error(), 500)
|
||||
fmt.Println("oh no")
|
||||
return
|
||||
}
|
||||
resp := &b2types.UploadFileResponse{
|
||||
FileID: id,
|
||||
Name: req.name,
|
||||
SHA1: req.sha1,
|
||||
BucketID: req.bucket,
|
||||
}
|
||||
if err := json.NewEncoder(rw).Encode(resp); err != nil {
|
||||
http.Error(rw, err.Error(), 500)
|
||||
fmt.Println("oh no")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterSimpleFileManagerOnMux(f SimpleFileManager, mux *http.ServeMux) {
|
||||
mux.Handle(uploadFilePrefix, &simpleFileServer{fm: f})
|
||||
}
|
||||
Reference in new issue
Block a user