Rework error type from interface to struct
This commit is contained in:
parent
0513a59e0b
commit
93187d760c
|
@ -46,7 +46,6 @@ var Flags struct {
|
||||||
GrpcHooksEndpoint string
|
GrpcHooksEndpoint string
|
||||||
GrpcHooksRetry int
|
GrpcHooksRetry int
|
||||||
GrpcHooksBackoff int
|
GrpcHooksBackoff int
|
||||||
HooksStopUploadCode int
|
|
||||||
EnabledHooks []hooks.HookType
|
EnabledHooks []hooks.HookType
|
||||||
ShowVersion bool
|
ShowVersion bool
|
||||||
ExposeMetrics bool
|
ExposeMetrics bool
|
||||||
|
@ -98,7 +97,6 @@ func ParseFlags() {
|
||||||
flag.StringVar(&Flags.GrpcHooksEndpoint, "hooks-grpc", "", "An gRPC endpoint to which hook events will be sent to")
|
flag.StringVar(&Flags.GrpcHooksEndpoint, "hooks-grpc", "", "An gRPC endpoint to which hook events will be sent to")
|
||||||
flag.IntVar(&Flags.GrpcHooksRetry, "hooks-grpc-retry", 3, "Number of times to retry on a server error or network timeout")
|
flag.IntVar(&Flags.GrpcHooksRetry, "hooks-grpc-retry", 3, "Number of times to retry on a server error or network timeout")
|
||||||
flag.IntVar(&Flags.GrpcHooksBackoff, "hooks-grpc-backoff", 1, "Number of seconds to wait before retrying each retry")
|
flag.IntVar(&Flags.GrpcHooksBackoff, "hooks-grpc-backoff", 1, "Number of seconds to wait before retrying each retry")
|
||||||
flag.IntVar(&Flags.HooksStopUploadCode, "hooks-stop-code", 0, "Return code from post-receive hook which causes tusd to stop and delete the current upload. A zero value means that no uploads will be stopped")
|
|
||||||
flag.BoolVar(&Flags.ShowVersion, "version", false, "Print tusd version information")
|
flag.BoolVar(&Flags.ShowVersion, "version", false, "Print tusd version information")
|
||||||
flag.BoolVar(&Flags.ExposeMetrics, "expose-metrics", true, "Expose metrics about tusd usage")
|
flag.BoolVar(&Flags.ExposeMetrics, "expose-metrics", true, "Expose metrics about tusd usage")
|
||||||
flag.StringVar(&Flags.MetricsPath, "metrics-path", "/metrics", "Path under which the metrics endpoint will be accessible")
|
flag.StringVar(&Flags.MetricsPath, "metrics-path", "/metrics", "Path under which the metrics endpoint will be accessible")
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,27 +21,12 @@ func hookTypeInSlice(a hooks.HookType, list []hooks.HookType) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func hookCallback(typ hooks.HookType, info handler.HookEvent) error {
|
func preCreateCallback(event handler.HookEvent) (handler.HTTPResponse, error) {
|
||||||
if output, err := invokeHookSync(typ, info, true); err != nil {
|
return invokeHookSync(hooks.HookPreCreate, event)
|
||||||
if hookErr, ok := err.(hooks.HookError); ok {
|
|
||||||
return hooks.NewHookError(
|
|
||||||
fmt.Errorf("%s hook failed: %s", typ, err),
|
|
||||||
hookErr.StatusCode(),
|
|
||||||
hookErr.Body(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%s hook failed: %s\n%s", typ, err, string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func preCreateCallback(info handler.HookEvent) error {
|
func preFinishCallback(event handler.HookEvent) (handler.HTTPResponse, error) {
|
||||||
return hookCallback(hooks.HookPreCreate, info)
|
return invokeHookSync(hooks.HookPreFinish, event)
|
||||||
}
|
|
||||||
|
|
||||||
func preFinishCallback(info handler.HookEvent) error {
|
|
||||||
return hookCallback(hooks.HookPreFinish, info)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupHookMetrics() {
|
func SetupHookMetrics() {
|
||||||
|
@ -59,13 +45,14 @@ func SetupHookMetrics() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupPreHooks(config *handler.Config) error {
|
func SetupPreHooks(config *handler.Config) error {
|
||||||
if Flags.FileHooksDir != "" {
|
// if Flags.FileHooksDir != "" {
|
||||||
stdout.Printf("Using '%s' for hooks", Flags.FileHooksDir)
|
// stdout.Printf("Using '%s' for hooks", Flags.FileHooksDir)
|
||||||
|
|
||||||
hookHandler = &hooks.FileHook{
|
// hookHandler = &hooks.FileHook{
|
||||||
Directory: Flags.FileHooksDir,
|
// Directory: Flags.FileHooksDir,
|
||||||
}
|
// }
|
||||||
} else if Flags.HttpHooksEndpoint != "" {
|
// } else
|
||||||
|
if Flags.HttpHooksEndpoint != "" {
|
||||||
stdout.Printf("Using '%s' as the endpoint for hooks", Flags.HttpHooksEndpoint)
|
stdout.Printf("Using '%s' as the endpoint for hooks", Flags.HttpHooksEndpoint)
|
||||||
|
|
||||||
hookHandler = &hooks.HttpHook{
|
hookHandler = &hooks.HttpHook{
|
||||||
|
@ -74,14 +61,14 @@ func SetupPreHooks(config *handler.Config) error {
|
||||||
Backoff: Flags.HttpHooksBackoff,
|
Backoff: Flags.HttpHooksBackoff,
|
||||||
ForwardHeaders: strings.Split(Flags.HttpHooksForwardHeaders, ","),
|
ForwardHeaders: strings.Split(Flags.HttpHooksForwardHeaders, ","),
|
||||||
}
|
}
|
||||||
} else if Flags.GrpcHooksEndpoint != "" {
|
// } else if Flags.GrpcHooksEndpoint != "" {
|
||||||
stdout.Printf("Using '%s' as the endpoint for gRPC hooks", Flags.GrpcHooksEndpoint)
|
// stdout.Printf("Using '%s' as the endpoint for gRPC hooks", Flags.GrpcHooksEndpoint)
|
||||||
|
|
||||||
hookHandler = &hooks.GrpcHook{
|
// hookHandler = &hooks.GrpcHook{
|
||||||
Endpoint: Flags.GrpcHooksEndpoint,
|
// Endpoint: Flags.GrpcHooksEndpoint,
|
||||||
MaxRetries: Flags.GrpcHooksRetry,
|
// MaxRetries: Flags.GrpcHooksRetry,
|
||||||
Backoff: Flags.GrpcHooksBackoff,
|
// Backoff: Flags.GrpcHooksBackoff,
|
||||||
}
|
// }
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -107,35 +94,35 @@ func SetupPostHooks(handler *handler.Handler) {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case info := <-handler.CompleteUploads:
|
case event := <-handler.CompleteUploads:
|
||||||
invokeHookAsync(hooks.HookPostFinish, info)
|
invokeHookAsync(hooks.HookPostFinish, event)
|
||||||
case info := <-handler.TerminatedUploads:
|
case event := <-handler.TerminatedUploads:
|
||||||
invokeHookAsync(hooks.HookPostTerminate, info)
|
invokeHookAsync(hooks.HookPostTerminate, event)
|
||||||
case info := <-handler.UploadProgress:
|
case event := <-handler.UploadProgress:
|
||||||
invokeHookAsync(hooks.HookPostReceive, info)
|
invokeHookAsync(hooks.HookPostReceive, event)
|
||||||
case info := <-handler.CreatedUploads:
|
case event := <-handler.CreatedUploads:
|
||||||
invokeHookAsync(hooks.HookPostCreate, info)
|
invokeHookAsync(hooks.HookPostCreate, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeHookAsync(typ hooks.HookType, info handler.HookEvent) {
|
func invokeHookAsync(typ hooks.HookType, event handler.HookEvent) {
|
||||||
go func() {
|
go func() {
|
||||||
// Error handling is taken care by the function.
|
// Error handling is taken care by the function.
|
||||||
_, _ = invokeHookSync(typ, info, false)
|
_, _ = invokeHookSync(typ, event)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeHookSync(typ hooks.HookType, info handler.HookEvent, captureOutput bool) ([]byte, error) {
|
func invokeHookSync(typ hooks.HookType, event handler.HookEvent) (httpRes handler.HTTPResponse, err error) {
|
||||||
if !hookTypeInSlice(typ, Flags.EnabledHooks) {
|
if !hookTypeInSlice(typ, Flags.EnabledHooks) {
|
||||||
return nil, nil
|
return httpRes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
MetricsHookInvocationsTotal.WithLabelValues(string(typ)).Add(1)
|
MetricsHookInvocationsTotal.WithLabelValues(string(typ)).Add(1)
|
||||||
|
|
||||||
id := info.Upload.ID
|
id := event.Upload.ID
|
||||||
size := info.Upload.Size
|
size := event.Upload.Size
|
||||||
|
|
||||||
switch typ {
|
switch typ {
|
||||||
case hooks.HookPostFinish:
|
case hooks.HookPostFinish:
|
||||||
|
@ -145,28 +132,52 @@ func invokeHookSync(typ hooks.HookType, info handler.HookEvent, captureOutput bo
|
||||||
}
|
}
|
||||||
|
|
||||||
if hookHandler == nil {
|
if hookHandler == nil {
|
||||||
return nil, nil
|
return httpRes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
name := string(typ)
|
|
||||||
if Flags.VerboseOutput {
|
if Flags.VerboseOutput {
|
||||||
logEv(stdout, "HookInvocationStart", "type", name, "id", id)
|
logEv(stdout, "HookInvocationStart", "type", string(typ), "id", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
output, returnCode, err := hookHandler.InvokeHook(typ, info, captureOutput)
|
hookRes, err := hookHandler.InvokeHook(hooks.HookRequest{
|
||||||
|
Type: typ,
|
||||||
|
Event: event,
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
err = fmt.Errorf("%s hook failed: %s", typ, err)
|
||||||
logEv(stderr, "HookInvocationError", "type", string(typ), "id", id, "error", err.Error())
|
logEv(stderr, "HookInvocationError", "type", string(typ), "id", id, "error", err.Error())
|
||||||
MetricsHookErrorsTotal.WithLabelValues(string(typ)).Add(1)
|
MetricsHookErrorsTotal.WithLabelValues(string(typ)).Add(1)
|
||||||
} else if Flags.VerboseOutput {
|
} else if Flags.VerboseOutput {
|
||||||
logEv(stdout, "HookInvocationFinish", "type", string(typ), "id", id)
|
logEv(stdout, "HookInvocationFinish", "type", string(typ), "id", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if typ == hooks.HookPostReceive && Flags.HooksStopUploadCode != 0 && Flags.HooksStopUploadCode == returnCode {
|
// IDEA: PreHooks work like this: error return value does not carry HTTP response information
|
||||||
logEv(stdout, "HookStopUpload", "id", id)
|
// Instead the additional HTTP response return value
|
||||||
|
|
||||||
info.Upload.StopUpload()
|
httpRes = hookRes.HTTPResponse
|
||||||
|
|
||||||
|
if hookRes.Error != "" {
|
||||||
|
// TODO: Is this actually useful?
|
||||||
|
return httpRes, errors.New(hookRes.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return output, err
|
// If the hook response includes the instruction to reject the upload, reuse the error code
|
||||||
|
// and message from ErrUploadRejectedByServer, but also include custom HTTP response values
|
||||||
|
if typ == hooks.HookPreCreate && hookRes.RejectUpload {
|
||||||
|
return httpRes, handler.Error{
|
||||||
|
ErrorCode: handler.ErrUploadRejectedByServer.ErrorCode,
|
||||||
|
Message: handler.ErrUploadRejectedByServer.Message,
|
||||||
|
HTTPResponse: httpRes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if typ == hooks.HookPostReceive && hookRes.StopUpload {
|
||||||
|
logEv(stdout, "HookStopUpload", "id", id)
|
||||||
|
|
||||||
|
// TODO: Control response for PATCH request
|
||||||
|
event.Upload.StopUpload()
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpRes, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,23 @@ import (
|
||||||
|
|
||||||
type HookHandler interface {
|
type HookHandler interface {
|
||||||
Setup() error
|
Setup() error
|
||||||
InvokeHook(typ HookType, info handler.HookEvent, captureOutput bool) ([]byte, int, error)
|
InvokeHook(req HookRequest) (res HookResponse, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HookRequest struct {
|
||||||
|
Type HookType
|
||||||
|
Event handler.HookEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
type HookResponse struct {
|
||||||
|
// Error indicates whether a fault occurred while processing the hook request.
|
||||||
|
// If Error is an empty string, no fault is assumed.
|
||||||
|
Error string
|
||||||
|
|
||||||
|
HTTPResponse handler.HTTPResponse
|
||||||
|
|
||||||
|
RejectUpload bool
|
||||||
|
StopUpload bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type HookType string
|
type HookType string
|
||||||
|
@ -21,29 +37,3 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var AvailableHooks []HookType = []HookType{HookPreCreate, HookPostCreate, HookPostReceive, HookPostTerminate, HookPostFinish, HookPreFinish}
|
var AvailableHooks []HookType = []HookType{HookPreCreate, HookPostCreate, HookPostReceive, HookPostTerminate, HookPostFinish, HookPreFinish}
|
||||||
|
|
||||||
type hookDataStore struct {
|
|
||||||
handler.DataStore
|
|
||||||
}
|
|
||||||
|
|
||||||
type HookError struct {
|
|
||||||
error
|
|
||||||
statusCode int
|
|
||||||
body []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHookError(err error, statusCode int, body []byte) HookError {
|
|
||||||
return HookError{err, statusCode, body}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (herr HookError) StatusCode() int {
|
|
||||||
return herr.statusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (herr HookError) Body() []byte {
|
|
||||||
return herr.body
|
|
||||||
}
|
|
||||||
|
|
||||||
func (herr HookError) Error() string {
|
|
||||||
return herr.error.Error()
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,8 +8,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tus/tusd/pkg/handler"
|
|
||||||
|
|
||||||
"github.com/sethgrid/pester"
|
"github.com/sethgrid/pester"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,35 +16,11 @@ type HttpHook struct {
|
||||||
MaxRetries int
|
MaxRetries int
|
||||||
Backoff int
|
Backoff int
|
||||||
ForwardHeaders []string
|
ForwardHeaders []string
|
||||||
|
|
||||||
|
client *pester.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ HttpHook) Setup() error {
|
func (h *HttpHook) Setup() error {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h HttpHook) InvokeHook(typ HookType, info handler.HookEvent, captureOutput bool) ([]byte, int, error) {
|
|
||||||
jsonInfo, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", h.Endpoint, bytes.NewBuffer(jsonInfo))
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, k := range h.ForwardHeaders {
|
|
||||||
// Lookup the Canonicalised version of the specified header
|
|
||||||
if vals, ok := info.HTTPRequest.Header[http.CanonicalHeaderKey(k)]; ok {
|
|
||||||
// but set the case specified by the user
|
|
||||||
req.Header[k] = vals
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Hook-Name", string(typ))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// TODO: Can we initialize this in Setup()?
|
|
||||||
// Use linear backoff strategy with the user defined values.
|
// Use linear backoff strategy with the user defined values.
|
||||||
client := pester.New()
|
client := pester.New()
|
||||||
client.KeepLog = true
|
client.KeepLog = true
|
||||||
|
@ -55,24 +29,51 @@ func (h HttpHook) InvokeHook(typ HookType, info handler.HookEvent, captureOutput
|
||||||
return time.Duration(h.Backoff) * time.Second
|
return time.Duration(h.Backoff) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
h.client = client
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
return nil
|
||||||
if err != nil {
|
}
|
||||||
return nil, 0, err
|
|
||||||
}
|
func (h HttpHook) InvokeHook(hookReq HookRequest) (hookRes HookResponse, err error) {
|
||||||
|
jsonInfo, err := json.Marshal(hookReq)
|
||||||
if resp.StatusCode >= http.StatusBadRequest {
|
if err != nil {
|
||||||
return body, resp.StatusCode, NewHookError(fmt.Errorf("endpoint returned: %s", resp.Status), resp.StatusCode, body)
|
return hookRes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if captureOutput {
|
httpReq, err := http.NewRequest("POST", h.Endpoint, bytes.NewBuffer(jsonInfo))
|
||||||
return body, resp.StatusCode, err
|
if err != nil {
|
||||||
}
|
return hookRes, err
|
||||||
|
}
|
||||||
return nil, resp.StatusCode, err
|
|
||||||
|
for _, k := range h.ForwardHeaders {
|
||||||
|
// Lookup the Canonicalised version of the specified header
|
||||||
|
if vals, ok := hookReq.Event.HTTPRequest.Header[http.CanonicalHeaderKey(k)]; ok {
|
||||||
|
// but set the case specified by the user
|
||||||
|
httpReq.Header[k] = vals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
httpRes, err := h.client.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return hookRes, err
|
||||||
|
}
|
||||||
|
defer httpRes.Body.Close()
|
||||||
|
|
||||||
|
httpBody, err := ioutil.ReadAll(httpRes.Body)
|
||||||
|
if err != nil {
|
||||||
|
return hookRes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report an error, if the response has a non-2XX status code
|
||||||
|
if httpRes.StatusCode < http.StatusOK || httpRes.StatusCode >= http.StatusMultipleChoices {
|
||||||
|
return hookRes, fmt.Errorf("unexpected response code from hook endpoint (%d): %s", httpRes.StatusCode, string(httpBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(httpBody, &hookRes); err != nil {
|
||||||
|
return hookRes, fmt.Errorf("failed to parse hook response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hookRes, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,15 +40,16 @@ type Config struct {
|
||||||
// potentially set by proxies when generating an absolute URL in the
|
// potentially set by proxies when generating an absolute URL in the
|
||||||
// response to POST requests.
|
// response to POST requests.
|
||||||
RespectForwardedHeaders bool
|
RespectForwardedHeaders bool
|
||||||
|
// TODO: Update comments
|
||||||
// PreUploadCreateCallback will be invoked before a new upload is created, if the
|
// PreUploadCreateCallback will be invoked before a new upload is created, if the
|
||||||
// property is supplied. If the callback returns nil, the upload will be created.
|
// property is supplied. If the callback returns nil, the upload will be created.
|
||||||
// Otherwise the HTTP request will be aborted. This can be used to implement
|
// Otherwise the HTTP request will be aborted. This can be used to implement
|
||||||
// validation of upload metadata etc.
|
// validation of upload metadata etc.
|
||||||
PreUploadCreateCallback func(hook HookEvent) error
|
PreUploadCreateCallback func(hook HookEvent) (HTTPResponse, error)
|
||||||
// PreFinishResponseCallback will be invoked after an upload is completed but before
|
// PreFinishResponseCallback will be invoked after an upload is completed but before
|
||||||
// a response is returned to the client. Error responses from the callback will be passed
|
// a response is returned to the client. Error responses from the callback will be passed
|
||||||
// back to the client. This can be used to implement post-processing validation.
|
// back to the client. This can be used to implement post-processing validation.
|
||||||
PreFinishResponseCallback func(hook HookEvent) error
|
PreFinishResponseCallback func(hook HookEvent) (HTTPResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) validate() error {
|
func (config *Config) validate() error {
|
||||||
|
|
|
@ -41,6 +41,7 @@ type FileInfo struct {
|
||||||
// more data. Furthermore, a response is sent to notify the client of the
|
// more data. Furthermore, a response is sent to notify the client of the
|
||||||
// interrupting and the upload is terminated (if supported by the data store),
|
// interrupting and the upload is terminated (if supported by the data store),
|
||||||
// so the upload cannot be resumed anymore.
|
// so the upload cannot be resumed anymore.
|
||||||
|
// TODO: Allow passing in a HTTP Response
|
||||||
func (f FileInfo) StopUpload() {
|
func (f FileInfo) StopUpload() {
|
||||||
if f.stopUpload != nil {
|
if f.stopUpload != nil {
|
||||||
f.stopUpload()
|
f.stopUpload()
|
||||||
|
|
|
@ -31,7 +31,7 @@ func (m Metrics) incRequestsTotal(method string) {
|
||||||
|
|
||||||
// TODO: Rework to only store error code
|
// TODO: Rework to only store error code
|
||||||
// incErrorsTotal increases the counter for this error atomically by one.
|
// incErrorsTotal increases the counter for this error atomically by one.
|
||||||
func (m Metrics) incErrorsTotal(err HTTPError) {
|
func (m Metrics) incErrorsTotal(err Error) {
|
||||||
ptr := m.ErrorsTotal.retrievePointerFor(err)
|
ptr := m.ErrorsTotal.retrievePointerFor(err)
|
||||||
atomic.AddUint64(ptr, 1)
|
atomic.AddUint64(ptr, 1)
|
||||||
}
|
}
|
||||||
|
@ -95,10 +95,10 @@ func newErrorsTotalMap() *ErrorsTotalMap {
|
||||||
|
|
||||||
// retrievePointerFor returns (after creating it if necessary) the pointer to
|
// retrievePointerFor returns (after creating it if necessary) the pointer to
|
||||||
// the counter for the error.
|
// the counter for the error.
|
||||||
func (e *ErrorsTotalMap) retrievePointerFor(err HTTPError) *uint64 {
|
func (e *ErrorsTotalMap) retrievePointerFor(err Error) *uint64 {
|
||||||
serr := ErrorsTotalMapEntry{
|
serr := ErrorsTotalMapEntry{
|
||||||
ErrorCode: err.ErrorCode(),
|
ErrorCode: err.ErrorCode,
|
||||||
StatusCode: err.StatusCode(),
|
StatusCode: err.HTTPResponse.StatusCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
e.lock.RLock()
|
e.lock.RLock()
|
||||||
|
|
|
@ -23,69 +23,60 @@ var (
|
||||||
reMimeType = regexp.MustCompile(`^[a-z]+\/[a-z0-9\-\+\.]+$`)
|
reMimeType = regexp.MustCompile(`^[a-z]+\/[a-z0-9\-\+\.]+$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPError represents an error with an additional status code attached
|
// TODO: Move in own file
|
||||||
// which may be used when this error is sent in a HTTP response.
|
// ErrorWithResponse represents an error with an additional HTTP response
|
||||||
// See the net/http package for standardized status codes.
|
// attached, which can hold a status code, body and headers.
|
||||||
type HTTPError interface {
|
type Error struct {
|
||||||
error
|
ErrorCode string
|
||||||
ErrorCode() string
|
Message string
|
||||||
StatusCode() int
|
HTTPResponse HTTPResponse
|
||||||
Body() []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpError struct {
|
func (e Error) Error() string {
|
||||||
errorCode string
|
return e.ErrorCode + ": " + e.Message
|
||||||
message string
|
|
||||||
statusCode int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err httpError) Error() string {
|
// TODO: Rename comment
|
||||||
return err.errorCode + ": " + err.message
|
// NewError adds the given status code to the provided error and returns
|
||||||
}
|
|
||||||
|
|
||||||
func (err httpError) StatusCode() int {
|
|
||||||
return err.statusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err httpError) ErrorCode() string {
|
|
||||||
return err.errorCode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err httpError) Body() []byte {
|
|
||||||
return []byte(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHTTPError adds the given status code to the provided error and returns
|
|
||||||
// the new error instance. The status code may be used in corresponding HTTP
|
// the new error instance. The status code may be used in corresponding HTTP
|
||||||
// responses. See the net/http package for standardized status codes.
|
// responses. See the net/http package for standardized status codes.
|
||||||
func NewHTTPError(errCode string, message string, statusCode int) HTTPError {
|
func NewError(errCode string, message string, statusCode int) Error {
|
||||||
return httpError{errCode, message, statusCode}
|
return Error{
|
||||||
|
ErrorCode: errCode,
|
||||||
|
Message: message,
|
||||||
|
HTTPResponse: HTTPResponse{
|
||||||
|
StatusCode: statusCode,
|
||||||
|
Body: []byte(errCode + ": " + message),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrUnsupportedVersion = NewHTTPError("ERR_UNSUPPORTED_VERSION", "missing, invalid or unsupported Tus-Resumable header", http.StatusPreconditionFailed)
|
ErrUnsupportedVersion = NewError("ERR_UNSUPPORTED_VERSION", "missing, invalid or unsupported Tus-Resumable header", http.StatusPreconditionFailed)
|
||||||
ErrMaxSizeExceeded = NewHTTPError("ERR_MAX_SIZE_EXCEEDED", "maximum size exceeded", http.StatusRequestEntityTooLarge)
|
ErrMaxSizeExceeded = NewError("ERR_MAX_SIZE_EXCEEDED", "maximum size exceeded", http.StatusRequestEntityTooLarge)
|
||||||
ErrInvalidContentType = NewHTTPError("ERR_INVALID_CONTENT_TYPE", "missing or invalid Content-Type header", http.StatusBadRequest)
|
ErrInvalidContentType = NewError("ERR_INVALID_CONTENT_TYPE", "missing or invalid Content-Type header", http.StatusBadRequest)
|
||||||
ErrInvalidUploadLength = NewHTTPError("ERR_INVALID_UPLOAD_LENGTH", "missing or invalid Upload-Length header", http.StatusBadRequest)
|
ErrInvalidUploadLength = NewError("ERR_INVALID_UPLOAD_LENGTH", "missing or invalid Upload-Length header", http.StatusBadRequest)
|
||||||
ErrInvalidOffset = NewHTTPError("ERR_INVALID_OFFSET", "missing or invalid Upload-Offset header", http.StatusBadRequest)
|
ErrInvalidOffset = NewError("ERR_INVALID_OFFSET", "missing or invalid Upload-Offset header", http.StatusBadRequest)
|
||||||
ErrNotFound = NewHTTPError("ERR_UPLOAD_NOT_FOUND", "upload not found", http.StatusNotFound)
|
ErrNotFound = NewError("ERR_UPLOAD_NOT_FOUND", "upload not found", http.StatusNotFound)
|
||||||
ErrFileLocked = NewHTTPError("ERR_UPLOAD_LOCKED", "file currently locked", http.StatusLocked)
|
ErrFileLocked = NewError("ERR_UPLOAD_LOCKED", "file currently locked", http.StatusLocked)
|
||||||
ErrMismatchOffset = NewHTTPError("ERR_MISMATCHED_OFFSET", "mismatched offset", http.StatusConflict)
|
ErrMismatchOffset = NewError("ERR_MISMATCHED_OFFSET", "mismatched offset", http.StatusConflict)
|
||||||
ErrSizeExceeded = NewHTTPError("ERR_UPLOAD_SIZE_EXCEEDED", "upload's size exceeded", http.StatusRequestEntityTooLarge)
|
ErrSizeExceeded = NewError("ERR_UPLOAD_SIZE_EXCEEDED", "upload's size exceeded", http.StatusRequestEntityTooLarge)
|
||||||
ErrNotImplemented = NewHTTPError("ERR_NOT_IMPLEMENTED", "feature not implemented", http.StatusNotImplemented)
|
ErrNotImplemented = NewError("ERR_NOT_IMPLEMENTED", "feature not implemented", http.StatusNotImplemented)
|
||||||
ErrUploadNotFinished = NewHTTPError("ERR_UPLOAD_NOT_FINISHED", "one of the partial uploads is not finished", http.StatusBadRequest)
|
ErrUploadNotFinished = NewError("ERR_UPLOAD_NOT_FINISHED", "one of the partial uploads is not finished", http.StatusBadRequest)
|
||||||
ErrInvalidConcat = NewHTTPError("ERR_INVALID_CONCAT", "invalid Upload-Concat header", http.StatusBadRequest)
|
ErrInvalidConcat = NewError("ERR_INVALID_CONCAT", "invalid Upload-Concat header", http.StatusBadRequest)
|
||||||
ErrModifyFinal = NewHTTPError("ERR_MODIFY_FINAL", "modifying a final upload is not allowed", http.StatusForbidden)
|
ErrModifyFinal = NewError("ERR_MODIFY_FINAL", "modifying a final upload is not allowed", http.StatusForbidden)
|
||||||
ErrUploadLengthAndUploadDeferLength = NewHTTPError("ERR_AMBIGUOUS_UPLOAD_LENGTH", "provided both Upload-Length and Upload-Defer-Length", http.StatusBadRequest)
|
ErrUploadLengthAndUploadDeferLength = NewError("ERR_AMBIGUOUS_UPLOAD_LENGTH", "provided both Upload-Length and Upload-Defer-Length", http.StatusBadRequest)
|
||||||
ErrInvalidUploadDeferLength = NewHTTPError("ERR_INVALID_UPLOAD_LENGTH_DEFER", "invalid Upload-Defer-Length header", http.StatusBadRequest)
|
ErrInvalidUploadDeferLength = NewError("ERR_INVALID_UPLOAD_LENGTH_DEFER", "invalid Upload-Defer-Length header", http.StatusBadRequest)
|
||||||
ErrUploadStoppedByServer = NewHTTPError("ERR_UPLOAD_STOPPED", "upload has been stopped by server", http.StatusBadRequest)
|
ErrUploadStoppedByServer = NewError("ERR_UPLOAD_STOPPED", "upload has been stopped by server", http.StatusBadRequest)
|
||||||
|
ErrUploadRejectedByServer = NewError("ERR_UPLOAD_REJECTED", "upload creation has been rejected by server", http.StatusBadRequest)
|
||||||
|
|
||||||
// TODO: These two responses are 500 for backwards compatability. We should discuss
|
// TODO: These two responses are 500 for backwards compatability. We should discuss
|
||||||
// whether it is better to more them to 4XX status codes.
|
// whether it is better to more them to 4XX status codes.
|
||||||
ErrReadTimeout = NewHTTPError("ERR_READ_TIMEOUT", "timeout while reading request body", http.StatusInternalServerError)
|
ErrReadTimeout = NewError("ERR_READ_TIMEOUT", "timeout while reading request body", http.StatusInternalServerError)
|
||||||
ErrConnectionReset = NewHTTPError("ERR_CONNECTION_RESET", "TCP connection reset by peer", http.StatusInternalServerError)
|
ErrConnectionReset = NewError("ERR_CONNECTION_RESET", "TCP connection reset by peer", http.StatusInternalServerError)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: Move HTTP structs into own file
|
||||||
// HTTPRequest contains basic details of an incoming HTTP request.
|
// HTTPRequest contains basic details of an incoming HTTP request.
|
||||||
type HTTPRequest struct {
|
type HTTPRequest struct {
|
||||||
// Method is the HTTP method, e.g. POST or PATCH
|
// Method is the HTTP method, e.g. POST or PATCH
|
||||||
|
@ -98,6 +89,15 @@ type HTTPRequest struct {
|
||||||
Header http.Header
|
Header http.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HTTPResponse struct {
|
||||||
|
// HTTPStatus, HTTPHeaders and HTTPBody control these details of the corresponding
|
||||||
|
// HTTP response.
|
||||||
|
// TODO: Currently only works for error responses
|
||||||
|
StatusCode int
|
||||||
|
Headers http.Header
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
// HookEvent represents an event from tusd which can be handled by the application.
|
// HookEvent represents an event from tusd which can be handled by the application.
|
||||||
type HookEvent struct {
|
type HookEvent struct {
|
||||||
// Upload contains information about the upload that caused this hook
|
// Upload contains information about the upload that caused this hook
|
||||||
|
@ -354,7 +354,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
if handler.config.PreUploadCreateCallback != nil {
|
if handler.config.PreUploadCreateCallback != nil {
|
||||||
if err := handler.config.PreUploadCreateCallback(newHookEvent(info, r)); err != nil {
|
if _, err := handler.config.PreUploadCreateCallback(newHookEvent(info, r)); err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -710,7 +710,7 @@ func (handler *UnroutedHandler) finishUploadIfComplete(ctx context.Context, uplo
|
||||||
handler.Metrics.incUploadsFinished()
|
handler.Metrics.incUploadsFinished()
|
||||||
|
|
||||||
if handler.config.PreFinishResponseCallback != nil {
|
if handler.config.PreFinishResponseCallback != nil {
|
||||||
if err := handler.config.PreFinishResponseCallback(newHookEvent(info, r)); err != nil {
|
if _, err := handler.config.PreFinishResponseCallback(newHookEvent(info, r)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -950,13 +950,13 @@ func (handler *UnroutedHandler) sendError(w http.ResponseWriter, r *http.Request
|
||||||
// err = nil
|
// err = nil
|
||||||
//}
|
//}
|
||||||
|
|
||||||
statusErr, ok := err.(HTTPError)
|
detailedErr, ok := err.(Error)
|
||||||
if !ok {
|
if !ok {
|
||||||
handler.log("InternalServerError", "message", err.Error(), "method", r.Method, "path", r.URL.Path, "requestId", getRequestId(r))
|
handler.log("InternalServerError", "message", err.Error(), "method", r.Method, "path", r.URL.Path, "requestId", getRequestId(r))
|
||||||
statusErr = NewHTTPError("ERR_INTERNAL_SERVER_ERROR", err.Error(), http.StatusInternalServerError)
|
detailedErr = NewError("ERR_INTERNAL_SERVER_ERROR", err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
reason := append(statusErr.Body(), '\n')
|
reason := append(detailedErr.HTTPResponse.Body, '\n')
|
||||||
if r.Method == "HEAD" {
|
if r.Method == "HEAD" {
|
||||||
reason = nil
|
reason = nil
|
||||||
}
|
}
|
||||||
|
@ -964,12 +964,12 @@ func (handler *UnroutedHandler) sendError(w http.ResponseWriter, r *http.Request
|
||||||
// TODO: Allow JSON response
|
// TODO: Allow JSON response
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(reason)))
|
w.Header().Set("Content-Length", strconv.Itoa(len(reason)))
|
||||||
w.WriteHeader(statusErr.StatusCode())
|
w.WriteHeader(detailedErr.HTTPResponse.StatusCode)
|
||||||
w.Write(reason)
|
w.Write(reason)
|
||||||
|
|
||||||
handler.log("ResponseOutgoing", "status", strconv.Itoa(statusErr.StatusCode()), "method", r.Method, "path", r.URL.Path, "error", statusErr.ErrorCode(), "requestId", getRequestId(r))
|
handler.log("ResponseOutgoing", "status", strconv.Itoa(detailedErr.HTTPResponse.StatusCode), "method", r.Method, "path", r.URL.Path, "error", detailedErr.ErrorCode, "requestId", getRequestId(r))
|
||||||
|
|
||||||
handler.Metrics.incErrorsTotal(statusErr)
|
handler.Metrics.incErrorsTotal(detailedErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendResp writes the header to w with the specified status code.
|
// sendResp writes the header to w with the specified status code.
|
||||||
|
|
|
@ -719,7 +719,7 @@ func (upload s3Upload) GetReader(ctx context.Context) (io.Reader, error) {
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// The multipart upload still exists, which means we cannot download it yet
|
// The multipart upload still exists, which means we cannot download it yet
|
||||||
return nil, handler.NewHTTPError("ERR_INCOMPLETE_UPLOAD", "cannot stream non-finished upload", http.StatusBadRequest)
|
return nil, handler.NewError("ERR_INCOMPLETE_UPLOAD", "cannot stream non-finished upload", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAwsError(err, "NoSuchUpload") {
|
if isAwsError(err, "NoSuchUpload") {
|
||||||
|
|
Loading…
Reference in New Issue