Rework error type from interface to struct

This commit is contained in:
Marius 2021-11-27 21:38:26 +01:00
parent 0513a59e0b
commit 93187d760c
9 changed files with 197 additions and 195 deletions

View File

@ -46,7 +46,6 @@ var Flags struct {
GrpcHooksEndpoint string
GrpcHooksRetry int
GrpcHooksBackoff int
HooksStopUploadCode int
EnabledHooks []hooks.HookType
ShowVersion 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.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.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.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")

View File

@ -1,6 +1,7 @@
package cli
import (
"errors"
"fmt"
"strconv"
"strings"
@ -20,27 +21,12 @@ func hookTypeInSlice(a hooks.HookType, list []hooks.HookType) bool {
return false
}
func hookCallback(typ hooks.HookType, info handler.HookEvent) error {
if output, err := invokeHookSync(typ, info, true); err != nil {
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(event handler.HookEvent) (handler.HTTPResponse, error) {
return invokeHookSync(hooks.HookPreCreate, event)
}
func preCreateCallback(info handler.HookEvent) error {
return hookCallback(hooks.HookPreCreate, info)
}
func preFinishCallback(info handler.HookEvent) error {
return hookCallback(hooks.HookPreFinish, info)
func preFinishCallback(event handler.HookEvent) (handler.HTTPResponse, error) {
return invokeHookSync(hooks.HookPreFinish, event)
}
func SetupHookMetrics() {
@ -59,13 +45,14 @@ func SetupHookMetrics() {
}
func SetupPreHooks(config *handler.Config) error {
if Flags.FileHooksDir != "" {
stdout.Printf("Using '%s' for hooks", Flags.FileHooksDir)
// if Flags.FileHooksDir != "" {
// stdout.Printf("Using '%s' for hooks", Flags.FileHooksDir)
hookHandler = &hooks.FileHook{
Directory: Flags.FileHooksDir,
}
} else if Flags.HttpHooksEndpoint != "" {
// hookHandler = &hooks.FileHook{
// Directory: Flags.FileHooksDir,
// }
// } else
if Flags.HttpHooksEndpoint != "" {
stdout.Printf("Using '%s' as the endpoint for hooks", Flags.HttpHooksEndpoint)
hookHandler = &hooks.HttpHook{
@ -74,14 +61,14 @@ func SetupPreHooks(config *handler.Config) error {
Backoff: Flags.HttpHooksBackoff,
ForwardHeaders: strings.Split(Flags.HttpHooksForwardHeaders, ","),
}
} else if Flags.GrpcHooksEndpoint != "" {
stdout.Printf("Using '%s' as the endpoint for gRPC hooks", Flags.GrpcHooksEndpoint)
// } else if Flags.GrpcHooksEndpoint != "" {
// stdout.Printf("Using '%s' as the endpoint for gRPC hooks", Flags.GrpcHooksEndpoint)
hookHandler = &hooks.GrpcHook{
Endpoint: Flags.GrpcHooksEndpoint,
MaxRetries: Flags.GrpcHooksRetry,
Backoff: Flags.GrpcHooksBackoff,
}
// hookHandler = &hooks.GrpcHook{
// Endpoint: Flags.GrpcHooksEndpoint,
// MaxRetries: Flags.GrpcHooksRetry,
// Backoff: Flags.GrpcHooksBackoff,
// }
} else {
return nil
}
@ -107,35 +94,35 @@ func SetupPostHooks(handler *handler.Handler) {
go func() {
for {
select {
case info := <-handler.CompleteUploads:
invokeHookAsync(hooks.HookPostFinish, info)
case info := <-handler.TerminatedUploads:
invokeHookAsync(hooks.HookPostTerminate, info)
case info := <-handler.UploadProgress:
invokeHookAsync(hooks.HookPostReceive, info)
case info := <-handler.CreatedUploads:
invokeHookAsync(hooks.HookPostCreate, info)
case event := <-handler.CompleteUploads:
invokeHookAsync(hooks.HookPostFinish, event)
case event := <-handler.TerminatedUploads:
invokeHookAsync(hooks.HookPostTerminate, event)
case event := <-handler.UploadProgress:
invokeHookAsync(hooks.HookPostReceive, event)
case event := <-handler.CreatedUploads:
invokeHookAsync(hooks.HookPostCreate, event)
}
}
}()
}
func invokeHookAsync(typ hooks.HookType, info handler.HookEvent) {
func invokeHookAsync(typ hooks.HookType, event handler.HookEvent) {
go func() {
// 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) {
return nil, nil
return httpRes, nil
}
MetricsHookInvocationsTotal.WithLabelValues(string(typ)).Add(1)
id := info.Upload.ID
size := info.Upload.Size
id := event.Upload.ID
size := event.Upload.Size
switch typ {
case hooks.HookPostFinish:
@ -145,28 +132,52 @@ func invokeHookSync(typ hooks.HookType, info handler.HookEvent, captureOutput bo
}
if hookHandler == nil {
return nil, nil
return httpRes, nil
}
name := string(typ)
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 {
err = fmt.Errorf("%s hook failed: %s", typ, err)
logEv(stderr, "HookInvocationError", "type", string(typ), "id", id, "error", err.Error())
MetricsHookErrorsTotal.WithLabelValues(string(typ)).Add(1)
} else if Flags.VerboseOutput {
logEv(stdout, "HookInvocationFinish", "type", string(typ), "id", id)
}
if typ == hooks.HookPostReceive && Flags.HooksStopUploadCode != 0 && Flags.HooksStopUploadCode == returnCode {
logEv(stdout, "HookStopUpload", "id", id)
// IDEA: PreHooks work like this: error return value does not carry HTTP response information
// 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
}

View File

@ -6,7 +6,23 @@ import (
type HookHandler interface {
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
@ -21,29 +37,3 @@ const (
)
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()
}

View File

@ -8,8 +8,6 @@ import (
"net/http"
"time"
"github.com/tus/tusd/pkg/handler"
"github.com/sethgrid/pester"
)
@ -18,35 +16,11 @@ type HttpHook struct {
MaxRetries int
Backoff int
ForwardHeaders []string
client *pester.Client
}
func (_ 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()?
func (h *HttpHook) Setup() error {
// Use linear backoff strategy with the user defined values.
client := pester.New()
client.KeepLog = true
@ -55,24 +29,51 @@ func (h HttpHook) InvokeHook(typ HookType, info handler.HookEvent, captureOutput
return time.Duration(h.Backoff) * time.Second
}
resp, err := client.Do(req)
if err != nil {
return nil, 0, err
}
defer resp.Body.Close()
h.client = client
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, 0, err
}
if resp.StatusCode >= http.StatusBadRequest {
return body, resp.StatusCode, NewHookError(fmt.Errorf("endpoint returned: %s", resp.Status), resp.StatusCode, body)
}
if captureOutput {
return body, resp.StatusCode, err
}
return nil, resp.StatusCode, err
return nil
}
func (h HttpHook) InvokeHook(hookReq HookRequest) (hookRes HookResponse, err error) {
jsonInfo, err := json.Marshal(hookReq)
if err != nil {
return hookRes, err
}
httpReq, err := http.NewRequest("POST", h.Endpoint, bytes.NewBuffer(jsonInfo))
if err != nil {
return hookRes, 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
}

View File

@ -40,15 +40,16 @@ type Config struct {
// potentially set by proxies when generating an absolute URL in the
// response to POST requests.
RespectForwardedHeaders bool
// TODO: Update comments
// 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.
// Otherwise the HTTP request will be aborted. This can be used to implement
// 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
// 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.
PreFinishResponseCallback func(hook HookEvent) error
PreFinishResponseCallback func(hook HookEvent) (HTTPResponse, error)
}
func (config *Config) validate() error {

View File

@ -41,6 +41,7 @@ type FileInfo struct {
// 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),
// so the upload cannot be resumed anymore.
// TODO: Allow passing in a HTTP Response
func (f FileInfo) StopUpload() {
if f.stopUpload != nil {
f.stopUpload()

View File

@ -31,7 +31,7 @@ func (m Metrics) incRequestsTotal(method string) {
// TODO: Rework to only store error code
// 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)
atomic.AddUint64(ptr, 1)
}
@ -95,10 +95,10 @@ func newErrorsTotalMap() *ErrorsTotalMap {
// retrievePointerFor returns (after creating it if necessary) the pointer to
// the counter for the error.
func (e *ErrorsTotalMap) retrievePointerFor(err HTTPError) *uint64 {
func (e *ErrorsTotalMap) retrievePointerFor(err Error) *uint64 {
serr := ErrorsTotalMapEntry{
ErrorCode: err.ErrorCode(),
StatusCode: err.StatusCode(),
ErrorCode: err.ErrorCode,
StatusCode: err.HTTPResponse.StatusCode,
}
e.lock.RLock()

View File

@ -23,69 +23,60 @@ var (
reMimeType = regexp.MustCompile(`^[a-z]+\/[a-z0-9\-\+\.]+$`)
)
// HTTPError represents an error with an additional status code attached
// which may be used when this error is sent in a HTTP response.
// See the net/http package for standardized status codes.
type HTTPError interface {
error
ErrorCode() string
StatusCode() int
Body() []byte
// TODO: Move in own file
// ErrorWithResponse represents an error with an additional HTTP response
// attached, which can hold a status code, body and headers.
type Error struct {
ErrorCode string
Message string
HTTPResponse HTTPResponse
}
type httpError struct {
errorCode string
message string
statusCode int
func (e Error) Error() string {
return e.ErrorCode + ": " + e.Message
}
func (err httpError) Error() string {
return err.errorCode + ": " + err.message
}
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
// TODO: Rename comment
// NewError adds the given status code to the provided error and returns
// the new error instance. The status code may be used in corresponding HTTP
// responses. See the net/http package for standardized status codes.
func NewHTTPError(errCode string, message string, statusCode int) HTTPError {
return httpError{errCode, message, statusCode}
func NewError(errCode string, message string, statusCode int) Error {
return Error{
ErrorCode: errCode,
Message: message,
HTTPResponse: HTTPResponse{
StatusCode: statusCode,
Body: []byte(errCode + ": " + message),
},
}
}
var (
ErrUnsupportedVersion = NewHTTPError("ERR_UNSUPPORTED_VERSION", "missing, invalid or unsupported Tus-Resumable header", http.StatusPreconditionFailed)
ErrMaxSizeExceeded = NewHTTPError("ERR_MAX_SIZE_EXCEEDED", "maximum size exceeded", http.StatusRequestEntityTooLarge)
ErrInvalidContentType = NewHTTPError("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)
ErrInvalidOffset = NewHTTPError("ERR_INVALID_OFFSET", "missing or invalid Upload-Offset header", http.StatusBadRequest)
ErrNotFound = NewHTTPError("ERR_UPLOAD_NOT_FOUND", "upload not found", http.StatusNotFound)
ErrFileLocked = NewHTTPError("ERR_UPLOAD_LOCKED", "file currently locked", http.StatusLocked)
ErrMismatchOffset = NewHTTPError("ERR_MISMATCHED_OFFSET", "mismatched offset", http.StatusConflict)
ErrSizeExceeded = NewHTTPError("ERR_UPLOAD_SIZE_EXCEEDED", "upload's size exceeded", http.StatusRequestEntityTooLarge)
ErrNotImplemented = NewHTTPError("ERR_NOT_IMPLEMENTED", "feature not implemented", http.StatusNotImplemented)
ErrUploadNotFinished = NewHTTPError("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)
ErrModifyFinal = NewHTTPError("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)
ErrInvalidUploadDeferLength = NewHTTPError("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)
ErrUnsupportedVersion = NewError("ERR_UNSUPPORTED_VERSION", "missing, invalid or unsupported Tus-Resumable header", http.StatusPreconditionFailed)
ErrMaxSizeExceeded = NewError("ERR_MAX_SIZE_EXCEEDED", "maximum size exceeded", http.StatusRequestEntityTooLarge)
ErrInvalidContentType = NewError("ERR_INVALID_CONTENT_TYPE", "missing or invalid Content-Type header", http.StatusBadRequest)
ErrInvalidUploadLength = NewError("ERR_INVALID_UPLOAD_LENGTH", "missing or invalid Upload-Length header", http.StatusBadRequest)
ErrInvalidOffset = NewError("ERR_INVALID_OFFSET", "missing or invalid Upload-Offset header", http.StatusBadRequest)
ErrNotFound = NewError("ERR_UPLOAD_NOT_FOUND", "upload not found", http.StatusNotFound)
ErrFileLocked = NewError("ERR_UPLOAD_LOCKED", "file currently locked", http.StatusLocked)
ErrMismatchOffset = NewError("ERR_MISMATCHED_OFFSET", "mismatched offset", http.StatusConflict)
ErrSizeExceeded = NewError("ERR_UPLOAD_SIZE_EXCEEDED", "upload's size exceeded", http.StatusRequestEntityTooLarge)
ErrNotImplemented = NewError("ERR_NOT_IMPLEMENTED", "feature not implemented", http.StatusNotImplemented)
ErrUploadNotFinished = NewError("ERR_UPLOAD_NOT_FINISHED", "one of the partial uploads is not finished", http.StatusBadRequest)
ErrInvalidConcat = NewError("ERR_INVALID_CONCAT", "invalid Upload-Concat header", http.StatusBadRequest)
ErrModifyFinal = NewError("ERR_MODIFY_FINAL", "modifying a final upload is not allowed", http.StatusForbidden)
ErrUploadLengthAndUploadDeferLength = NewError("ERR_AMBIGUOUS_UPLOAD_LENGTH", "provided both Upload-Length and Upload-Defer-Length", http.StatusBadRequest)
ErrInvalidUploadDeferLength = NewError("ERR_INVALID_UPLOAD_LENGTH_DEFER", "invalid Upload-Defer-Length header", 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
// whether it is better to more them to 4XX status codes.
ErrReadTimeout = NewHTTPError("ERR_READ_TIMEOUT", "timeout while reading request body", http.StatusInternalServerError)
ErrConnectionReset = NewHTTPError("ERR_CONNECTION_RESET", "TCP connection reset by peer", http.StatusInternalServerError)
ErrReadTimeout = NewError("ERR_READ_TIMEOUT", "timeout while reading request body", 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.
type HTTPRequest struct {
// Method is the HTTP method, e.g. POST or PATCH
@ -98,6 +89,15 @@ type HTTPRequest struct {
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.
type HookEvent struct {
// 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 err := handler.config.PreUploadCreateCallback(newHookEvent(info, r)); err != nil {
if _, err := handler.config.PreUploadCreateCallback(newHookEvent(info, r)); err != nil {
handler.sendError(w, r, err)
return
}
@ -710,7 +710,7 @@ func (handler *UnroutedHandler) finishUploadIfComplete(ctx context.Context, uplo
handler.Metrics.incUploadsFinished()
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
}
}
@ -950,13 +950,13 @@ func (handler *UnroutedHandler) sendError(w http.ResponseWriter, r *http.Request
// err = nil
//}
statusErr, ok := err.(HTTPError)
detailedErr, ok := err.(Error)
if !ok {
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" {
reason = nil
}
@ -964,12 +964,12 @@ func (handler *UnroutedHandler) sendError(w http.ResponseWriter, r *http.Request
// TODO: Allow JSON response
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(reason)))
w.WriteHeader(statusErr.StatusCode())
w.WriteHeader(detailedErr.HTTPResponse.StatusCode)
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.

View File

@ -719,7 +719,7 @@ func (upload s3Upload) GetReader(ctx context.Context) (io.Reader, error) {
})
if err == nil {
// 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") {