115 lines
2.9 KiB
Go
115 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/valyala/fasthttp"
|
|
"go.uber.org/zap"
|
|
"storj.io/uplink/edge"
|
|
)
|
|
|
|
type serverStorj struct {
|
|
opts obsOptions
|
|
logger *zap.SugaredLogger
|
|
|
|
sc *storjAggegrateClient
|
|
}
|
|
|
|
func (s *serverStorj) Init(ctx context.Context, opts serverOptions) (err error) {
|
|
s.opts = opts.GetOpts()
|
|
s.logger = opts.Logger.Named(s.Name()).Sugar()
|
|
{
|
|
if s.sc, err = newObsStorjClient(ctx, opts.GetUplinkOpts()); err != nil {
|
|
err = errors.Wrap(err, "obs uplink client")
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *serverStorj) Name() string {
|
|
return "storj"
|
|
}
|
|
|
|
func (s *serverStorj) getLogger() *zap.SugaredLogger { return s.logger }
|
|
func (s *serverStorj) reportError(ctx *fasthttp.RequestCtx, errType string, err any) {
|
|
reportError(s, ctx, errType, err)
|
|
}
|
|
|
|
var (
|
|
ErrKind_StorjComposeShareURL = "STORJ_COMPOSE_SHARE_URL"
|
|
)
|
|
|
|
func (s *serverStorj) handle(ctx *fasthttp.RequestCtx) {
|
|
isMethodGet := bytes.Equal(ctx.Method(), MethodGet)
|
|
isMethodHead := bytes.Equal(ctx.Method(), MethodHead)
|
|
if !(isMethodGet || isMethodHead) {
|
|
ctx.SetStatusCode(http.StatusMethodNotAllowed)
|
|
s.reportError(ctx, ErrKind_MethodNotAllowed, "")
|
|
return
|
|
}
|
|
|
|
if isMethodHead {
|
|
// Doc: https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.2-1
|
|
ctx.Response.Header.Set("Content-Length", "0")
|
|
}
|
|
|
|
bucketName := s.opts.BucketName
|
|
path := ctx.Path()
|
|
_path := bytes.TrimLeft(path, "/")
|
|
if _, _pathWithoutBucketName, found := bytes.Cut(_path, []byte(`/`)); found {
|
|
// no need to check `isVirtualHostStyle` since this is our own implementation of handling request URI
|
|
_path = _pathWithoutBucketName
|
|
}
|
|
objectName := unsafeByteSliceToString(_path)
|
|
|
|
s.logger.Debugw("handle",
|
|
"bucket", bucketName,
|
|
"objectName", objectName)
|
|
|
|
// use project
|
|
if project := s.sc.getProject(); project != nil {
|
|
// check if we had access to the object
|
|
if meta, err := project.StatObject(ctx, bucketName, objectName); err != nil {
|
|
ctx.SetStatusCode(http.StatusNotFound)
|
|
s.reportError(ctx, ErrKind_ResourceNotFound, err)
|
|
return
|
|
} else {
|
|
_ = meta
|
|
}
|
|
}
|
|
|
|
shareURL, err := s.sc.JoinShareURL(bucketName, objectName, &edge.ShareURLOptions{
|
|
Raw: true,
|
|
})
|
|
if err != nil {
|
|
ctx.SetStatusCode(http.StatusInternalServerError)
|
|
s.reportError(ctx, ErrKind_StorjComposeShareURL, err)
|
|
}
|
|
|
|
var statusCode = s.opts.RedirectCode
|
|
|
|
if statusCode < 300 || statusCode >= 399 {
|
|
// fallback of invalid redirect code
|
|
statusCode = http.StatusTemporaryRedirect
|
|
}
|
|
|
|
expireAt := time.Now().UTC().Add(s.opts.URLExpiry)
|
|
expireSeconds := int64(s.opts.URLExpiry / time.Second)
|
|
// set redirect cache lifetime
|
|
if statusCode == http.StatusTemporaryRedirect {
|
|
ctx.Response.Header.Set("Cache-Control", fmt.Sprintf("max-age=%d", expireSeconds))
|
|
ctx.Response.Header.Set("Expires", expireAt.Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
|
}
|
|
|
|
ctx.Redirect(shareURL, s.opts.RedirectCode)
|
|
}
|
|
|
|
func (s *serverStorj) GetHandler() fasthttp.RequestHandler {
|
|
return s.handle
|
|
}
|