diff --git a/cmd/tusd/cli/hooks.go b/cmd/tusd/cli/hooks.go index 888e524..77683d4 100644 --- a/cmd/tusd/cli/hooks.go +++ b/cmd/tusd/cli/hooks.go @@ -30,8 +30,26 @@ type hookDataStore struct { tusd.DataStore } +type hookError struct { + error + statusCode int + body []byte +} + +func (herr hookError) StatusCode() int { + return herr.statusCode +} + +func (herr hookError) Body() []byte { + return herr.body +} + func (store hookDataStore) NewUpload(info tusd.FileInfo) (id string, err error) { if output, err := invokeHookSync(HookPreCreate, info, true); err != nil { + if hookErr, ok := err.(hookError); ok { + hookErr.error = fmt.Errorf("pre-create hook failed: %s", err) + return "", hookErr + } return "", fmt.Errorf("pre-create hook failed: %s\n%s", err, string(output)) } return store.DataStore.NewUpload(info) @@ -144,7 +162,7 @@ func invokeHttpHook(name string, typ HookType, info tusd.FileInfo, captureOutput } if resp.StatusCode >= http.StatusBadRequest { - return body, fmt.Errorf("endpoint returned: %s\n%s", resp.Status, body) + return body, hookError{fmt.Errorf("endpoint returned: %s", resp.Status), resp.StatusCode, body} } if captureOutput { diff --git a/docs/hooks.md b/docs/hooks.md index e51038f..ad351f6 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -7,11 +7,21 @@ When a specific action happens during an upload (pre-create, post-receive, post- 1. Execute an arbitrary file, mirroring the Git hook system, called File Hooks. 2. Fire off an HTTP POST request to a custom endpoint, called HTTP Hooks. -## Blocking and Non-Blocking Hooks +## Non-Blocking Hooks If not otherwise noted, all hooks are invoked in a *non-blocking* way, meaning that tusd will not wait until the hook process has finished and exited. Therefore, the hook process is not able to influence how tusd may continue handling the current request, regardless of which exit code it may set. Furthermore, the hook process' stdout and stderr will be piped to tusd's stdout and stderr correspondingly, allowing one to use these channels for additional logging. -On the other hand, there are a few *blocking* hooks, such as caused by the `pre-create` event. Because their exit code will dictate whether tusd will accept the current incoming request, tusd will wait until the hook process has exited. Therefore, in order to keep the response times low, one should avoid to make time-consuming operations inside the processes for blocking hooks. An exit code of `0` indicates that tusd should continue handling the request as normal. On the other hand, a non-zero exit code tells tusd to reject the request with a `500 Internal Server Error` response containing the process' output from stderr. For the sake of logging, the process' output from stdout will always be piped to tusd's stdout. +## Blocking Hooks + +On the other hand, there are a few *blocking* hooks, such as caused by the `pre-create` event. Because their exit code will dictate whether tusd will accept the current incoming request, tusd will wait until the hook process has exited. Therefore, in order to keep the response times low, one should avoid to make time-consuming operations inside the processes for blocking hooks. + +### Blocking File Hooks + +An exit code of `0` indicates that tusd should continue handling the request as normal. On the other hand, a non-zero exit code tells tusd to reject the request with a `500 Internal Server Error` response containing the process' output from stderr. For the sake of logging, the process' output from stdout will always be piped to tusd's stdout. + +### Blocking HTTP Hooks + +A successful HTTP response code (i.e. smaller than `400`) indicates that tusd should continue handling the request as normal. On the other hand, an HTTP response code greater than `400` will be forwarded to the client performing the upload, along with the body of the hook response. Only the response code will be logged by tusd. ## List of Available Hooks diff --git a/unrouted_handler.go b/unrouted_handler.go index 4115b8e..5f94391 100644 --- a/unrouted_handler.go +++ b/unrouted_handler.go @@ -31,6 +31,7 @@ var ( type HTTPError interface { error StatusCode() int + Body() []byte } type httpError struct { @@ -42,6 +43,10 @@ func (err httpError) StatusCode() int { return err.statusCode } +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 // responses. See the net/http package for standardized status codes. @@ -766,15 +771,15 @@ func (handler *UnroutedHandler) sendError(w http.ResponseWriter, r *http.Request statusErr = NewHTTPError(err, http.StatusInternalServerError) } - reason := err.Error() + "\n" + reason := append(statusErr.Body(), '\n') if r.Method == "HEAD" { - reason = "" + reason = nil } w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Length", strconv.Itoa(len(reason))) w.WriteHeader(statusErr.StatusCode()) - w.Write([]byte(reason)) + w.Write(reason) handler.log("ResponseOutgoing", "status", strconv.Itoa(statusErr.StatusCode()), "method", r.Method, "path", r.URL.Path, "error", err.Error())