diff --git a/.infra/Freyfile.hcl b/.infra/Freyfile.hcl index 63c51e1..48fddbd 100644 --- a/.infra/Freyfile.hcl +++ b/.infra/Freyfile.hcl @@ -33,15 +33,9 @@ infra variable { } infra output { - public_address { - value = "${aws_instance.tusd.0.public_dns}" - } - public_addresses { - value = "${join("\n", aws_instance.tusd.*.public_dns)}" - } - endpoint { - value = "http://${aws_route53_record.www.name}:80/" - } + public_address { value = "${aws_instance.tusd.0.public_dns}" } + public_addresses { value = "${join("\n", aws_instance.tusd.*.public_dns)}" } + endpoint { value = "http://${aws_route53_record.www.name}:80/" } } infra resource aws_instance tusd { @@ -53,13 +47,11 @@ infra resource aws_instance tusd { key_file = "{{{config.global.ssh.privatekey_file}}}" user = "{{{config.global.ssh.user}}}" } - tags { - "Name" = "${var.FREY_DOMAIN}" - } + tags { Name = "master.tus.io" } } infra resource "aws_route53_record" www { - name = "${var.FREY_DOMAIN}" + name = "master.tus.io" records = ["${aws_instance.tusd.public_dns}"] ttl = "300" type = "CNAME" @@ -110,7 +102,7 @@ install { } tasks { name = "Common | Set motd" - copy = "content='Welcome to {{lookup('env', 'FREY_DOMAIN')}}' dest=/etc/motd owner=root group=root mode=0644 backup=yes" + copy = "content='Welcome to master.tus.io' dest=/etc/motd owner=root group=root mode=0644 backup=yes" } tasks { name = "Common | Set timezone variables" @@ -151,12 +143,20 @@ setup { } roles { role = "{{{init.paths.roles_dir}}}/fqdn/v1.0.0" - fqdn = "{{lookup('env', 'FREY_DOMAIN')}}" + fqdn = "master.tus.io" } tasks { - file = "path=/mnt/tusd-data state=directory owner=www-data group=www-data mode=0755 recurse=yes" + file = "path=/mnt/tusd-data state=directory owner=www-data group=ubuntu mode=ug+rwX,o= recurse=yes" name = "tusd | Create tusd data dir" } + tasks { + name = "tusd | Create purger crontab (clean up >24h (1400minutes) files)" + cron { + name = "purger" + special_time = "hourly" + job = "find /mnt/tusd-data -type f -mmin +1440 -print0 | xargs -n 200 -r -0 rm || true" + } + } } } diff --git a/.infra/env.example.sh b/.infra/env.example.sh index 0fe3534..8e563df 100644 --- a/.infra/env.example.sh +++ b/.infra/env.example.sh @@ -2,9 +2,7 @@ # So suitable for adding secret keys and such # export DEBUG="frey:*" -# export FREY_DOMAIN="master.tus.io" # export FREY_ENCRYPTION_SECRET="***" # source env.sh -# travis encrypt --add env.global "FREY_DOMAIN=${FREY_DOMAIN}" # travis encrypt --add env.global "FREY_ENCRYPTION_SECRET=${FREY_ENCRYPTION_SECRET}" diff --git a/.scripts/build_all.sh b/.scripts/build_all.sh new file mode 100755 index 0000000..b36ead0 --- /dev/null +++ b/.scripts/build_all.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -e + +version=$TRAVIS_TAG +commit=$TRAVIS_COMMIT + +function compile { + local os=$1 + local arch=$2 + local ext=$3 + + echo "Compiling for $os/$arch..." + + local dir="tusd_${os}_${arch}" + rm -rf "$dir" + mkdir -p "$dir" + GOOS=$os GOARCH=$arch go build \ + -ldflags="-X github.com/tus/tusd/cmd/tusd/cli.VersionName=${version} -X github.com/tus/tusd/cmd/tusd/cli.GitCommit=${commit} -X 'github.com/tus/tusd/cmd/tusd/cli.BuildDate=$(date --utc)'" \ + -o "$dir/tusd$ext" ./cmd/tusd/main.go +} + +function makezip { + local os=$1 + local arch=$2 + local ext=$3 + + echo "Zipping for $os/$arch..." + + local dir="tusd_${os}_${arch}" + zip "$dir.zip" "$dir/tusd$ext" LICENSE.txt README.md +} + +function maketar { + local os=$1 + local arch=$2 + + echo "Tarring for $os/$arch..." + + local dir="tusd_${os}_${arch}" + tar -czf "$dir.tar.gz" "$dir/tusd" LICENSE.txt README.md +} + +function makedep { + local arch=$1 + + echo "Debbing for $arch..." + + local dir="tusd_snapshot_${arch}" + rm -rf "$dir" + mkdir -p "$dir" + mkdir -p "$dir/DEBIAN" + mkdir -p "$dir/usr/bin" + cp "./tusd_linux_${arch}/tusd" "./$dir/usr/bin/tusd" + + echo "Package: tusd" >> "./$dir/DEBIAN/control" + echo "Maintainer: Marius " >> "./$dir/DEBIAN/control" + echo "Section: devel" >> "./$dir/DEBIAN/control" + echo "Priority: optional" >> "./$dir/DEBIAN/control" + echo "Version: ${version}" >> "./$dir/DEBIAN/control" + echo "Architecture: ${arch}" >> "./$dir/DEBIAN/control" + echo "Homepage: https://github.com/tus/tusd" >> "./$dir/DEBIAN/control" + echo "Built-Using: $(go version)" >> "./$dir/DEBIAN/control" + echo "Description: The official server implementation of the tus resumable upload protocol." >> "./$dir/DEBIAN/control" + + dpkg-deb --build "$dir" +} + +compile linux 386 +compile linux amd64 +compile linux arm +compile darwin 386 +compile darwin amd64 +compile windows 386 .exe +compile windows amd64 .exe + +maketar linux 386 +maketar linux amd64 +maketar linux arm +makezip darwin 386 +makezip darwin amd64 +makezip windows 386 .exe +makezip windows amd64 .exe +makedep amd64 diff --git a/.travis.yml b/.travis.yml index 3702c92..13d4353 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,29 +28,18 @@ install: script: - go test $PACKAGES before_deploy: -- export GOROOT_BOOTSTRAP=$GOROOT -- go get github.com/laher/goxc -- goxc -t -bc="linux darwin windows" -- goxc -d=./ -wd=./cmd/tusd -bc="linux darwin windows" -build-ldflags="-X github.com/tus/tusd/cmd/tusd/cli.VersionName=$TRAVIS_TAG -X github.com/tus/tusd/cmd/tusd/cli.GitCommit=$TRAVIS_COMMIT -X 'github.com/tus/tusd/cmd/tusd/cli.BuildDate=$(date --utc)'" +- ./.scripts/build_all.sh deploy: provider: releases api_key: secure: dV3wr9ebEps3YrzIoqmkYc7fw0IECz7QLPRENPSxTJyd5TTYXGsnTS26cMe2LdGwYrXw0njt2GGovMyBZFTtxyYI3mMO4AZRwvZfx/yGzPWJBbVi6NjZVRg/bpyK+mQJ5BUlkPAYJmRpdc6qD+nvCGakBOxoByC5XDK+yM+bKFs= - file: - - snapshot/tusd_darwin_386.zip - - snapshot/tusd_darwin_amd64.zip - - snapshot/tusd_linux_386.tar.gz - - snapshot/tusd_linux_amd64.tar.gz - - snapshot/tusd_linux_arm.tar.gz - - snapshot/tusd_snapshot_amd64.deb - - snapshot/tusd_snapshot_armhf.deb - - snapshot/tusd_snapshot_i386.deb - - snapshot/tusd_windows_386.zip - - snapshot/tusd_windows_amd64.zip + file_glob: true + file: tusd_*.* skip_cleanup: true on: + all_branches: true tags: true - go: 1.5 + go: 1.7 repo: tus/tusd after_deploy: - make frey && frey setup --force-yes --projectDir .infra diff --git a/appveyor.yml b/appveyor.yml index 3973899..77d1c42 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,10 +10,13 @@ install: build_script: - go env - go version + - go get ./s3store + - go get ./consullocker test_script: - go test . - go test ./filestore - go test ./limitedstore - go test ./memorylocker + - go test ./consullocker - go test ./s3store diff --git a/cmd/tusd/cli/hooks.go b/cmd/tusd/cli/hooks.go index 10c7abb..7cb8d7f 100644 --- a/cmd/tusd/cli/hooks.go +++ b/cmd/tusd/cli/hooks.go @@ -51,19 +51,17 @@ func SetupPostHooks(handler *tusd.Handler) { func invokeHook(typ HookType, info tusd.FileInfo) { go func() { - _, err := invokeHookSync(typ, info, false) - if err != nil { - stderr.Printf("Error running %s hook for %s: %s", string(typ), info.ID, err) - } + // Error handling is token care of by the function. + _, _ = invokeHookSync(typ, info, false) }() } func invokeHookSync(typ HookType, info tusd.FileInfo, captureOutput bool) ([]byte, error) { switch typ { case HookPostFinish: - stdout.Printf("Upload %s (%d bytes) finished\n", info.ID, info.Size) + logEv("UploadFinished", "id", info.ID, "size", strconv.FormatInt(info.Size, 10)) case HookPostTerminate: - stdout.Printf("Upload %s terminated\n", info.ID) + logEv("UploadTerminated", "id", info.ID) } if !Flags.HooksInstalled { @@ -71,7 +69,7 @@ func invokeHookSync(typ HookType, info tusd.FileInfo, captureOutput bool) ([]byt } name := string(typ) - stdout.Printf("Invoking %s hook…\n", name) + logEv("HookInvocationStart", "type", name, "id", info.ID) cmd := exec.Command(Flags.HooksDir + "/" + name) env := os.Environ() @@ -100,10 +98,15 @@ func invokeHookSync(typ HookType, info tusd.FileInfo, captureOutput bool) ([]byt output, err = cmd.Output() } + if err != nil { + logEv("HookInvocationError", "type", string(typ), "id", info.ID, "error", err.Error()) + } else { + logEv("HookInvocationFinish", "type", string(typ), "id", info.ID) + } + // Ignore the error, only, if the hook's file could not be found. This usually // means that the user is only using a subset of the available hooks. if os.IsNotExist(err) { - stdout.Printf("Unable to invoke %s hook: %s\n", name, err) err = nil } diff --git a/cmd/tusd/cli/log.go b/cmd/tusd/cli/log.go index 84917e8..5cbeb62 100644 --- a/cmd/tusd/cli/log.go +++ b/cmd/tusd/cli/log.go @@ -3,7 +3,13 @@ package cli import ( "log" "os" + + "github.com/tus/tusd" ) var stdout = log.New(os.Stdout, "[tusd] ", 0) var stderr = log.New(os.Stderr, "[tusd] ", 0) + +func logEv(eventName string, details ...string) { + tusd.LogEvent(stderr, eventName, details...) +} diff --git a/config.go b/config.go index 98ab97a..1ed82cf 100644 --- a/config.go +++ b/config.go @@ -35,7 +35,7 @@ type Config struct { Logger *log.Logger // Respect the X-Forwarded-Host, X-Forwarded-Proto and Forwarded headers // potentially set by proxies when generating an absolute URL in the - // reponse to POST requests. + // response to POST requests. RespectForwardedHeaders bool } diff --git a/cors_test.go b/cors_test.go index 875311a..6f42e07 100644 --- a/cors_test.go +++ b/cors_test.go @@ -22,9 +22,9 @@ func TestCORS(t *testing.T) { }, Code: http.StatusOK, ResHeader: map[string]string{ - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Max-Age": "", + "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata", + "Access-Control-Allow-Methods": "POST, GET, HEAD, PATCH, DELETE, OPTIONS", + "Access-Control-Max-Age": "86400", "Access-Control-Allow-Origin": "tus.io", }, }).Run(handler, t) @@ -37,7 +37,7 @@ func TestCORS(t *testing.T) { }, Code: http.StatusMethodNotAllowed, ResHeader: map[string]string{ - "Access-Control-Expose-Headers": "", + "Access-Control-Expose-Headers": "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata", "Access-Control-Allow-Origin": "tus.io", }, }).Run(handler, t) diff --git a/datastore.go b/datastore.go index aa130b3..588199a 100644 --- a/datastore.go +++ b/datastore.go @@ -69,7 +69,7 @@ type FinisherDataStore interface { // Common ways to store this information is in memory, on disk or using an // external service, such as ZooKeeper. // When multiple processes are attempting to access an upload, whether it be -// by reading or writing, a syncronization mechanism is required to prevent +// by reading or writing, a synchronization mechanism is required to prevent // data corruption, especially to ensure correct offset values and the proper // order of chunks inside a single upload. type LockerDataStore interface { diff --git a/filestore/filestore.go b/filestore/filestore.go index b676633..1d82e55 100644 --- a/filestore/filestore.go +++ b/filestore/filestore.go @@ -7,9 +7,9 @@ // No cleanup is performed so you may want to run a cronjob to ensure your disk // is not filled up with old and finished uploads. // -// In addition, it provides an exclusive upload locking mechansim using lock files +// In addition, it provides an exclusive upload locking mechanism using lock files // which are stored on disk. Each of them stores the PID of the process which -// aquired the lock. This allows locks to be automatically freed when a process +// acquired the lock. This allows locks to be automatically freed when a process // is unable to release it on its own because the process is not alive anymore. // For more information, consult the documentation for tusd.LockerDataStore // interface, which is implemented by FileStore @@ -161,12 +161,12 @@ func (store FileStore) UnlockUpload(id string) error { // A "no such file or directory" will be returned if no lockfile was found. // Since this means that the file has never been locked, we drop the error - // and continue as if nothing happend. + // and continue as if nothing happened. if os.IsNotExist(err) { err = nil } - return nil + return err } // newLock contructs a new Lockfile instance. diff --git a/handler_test.go b/handler_test.go index 87971a6..3a32813 100644 --- a/handler_test.go +++ b/handler_test.go @@ -61,11 +61,7 @@ func (test *httpTest) Run(handler http.Handler, t *testing.T) *httptest.Response for key, value := range test.ResHeader { header := w.HeaderMap.Get(key) - if value == "" && header == "" { - t.Errorf("Expected '%s' in response", key) - } - - if value != "" && value != header { + if value != header { t.Errorf("Expected '%s' as '%s' (got '%s')", value, key, header) } } diff --git a/log.go b/log.go new file mode 100644 index 0000000..ffef931 --- /dev/null +++ b/log.go @@ -0,0 +1,27 @@ +package tusd + +import ( + "log" +) + +func (h *UnroutedHandler) log(eventName string, details ...string) { + LogEvent(h.logger, eventName, details...) +} + +func LogEvent(logger *log.Logger, eventName string, details ...string) { + result := make([]byte, 0, 100) + + result = append(result, `event="`...) + result = append(result, eventName...) + result = append(result, `" `...) + + for i := 0; i < len(details); i += 2 { + result = append(result, details[i]...) + result = append(result, `="`...) + result = append(result, details[i+1]...) + result = append(result, `" `...) + } + + result = append(result, "\n"...) + logger.Output(2, string(result)) +} diff --git a/memorylocker/memorylocker.go b/memorylocker/memorylocker.go index a4ea689..aa0664d 100644 --- a/memorylocker/memorylocker.go +++ b/memorylocker/memorylocker.go @@ -1,12 +1,12 @@ // Package memorylocker provides an in-memory locking mechanism. // // When multiple processes are attempting to access an upload, whether it be -// by reading or writing, a syncronization mechanism is required to prevent +// by reading or writing, a synchronization mechanism is required to prevent // data corruption, especially to ensure correct offset values and the proper // order of chunks inside a single upload. // // MemoryLocker persists locks using memory and therefore allowing a simple and -// cheap mechansim. Locks will only exist as long as this object is kept in +// cheap mechanism. Locks will only exist as long as this object is kept in // reference and will be erased if the program exits. package memorylocker @@ -17,7 +17,7 @@ import ( ) // MemoryLocker persists locks using memory and therefore allowing a simple and -// cheap mechansim. Locks will only exist as long as this object is kept in +// cheap mechanism. Locks will only exist as long as this object is kept in // reference and will be erased if the program exits. type MemoryLocker struct { locks map[string]bool diff --git a/metrics.go b/metrics.go index 64275e0..92df351 100644 --- a/metrics.go +++ b/metrics.go @@ -80,7 +80,7 @@ func newMetrics() Metrics { func newErrorsTotalMap() map[string]*uint64 { m := make(map[string]*uint64, len(ErrStatusCodes)+1) - for err, _ := range ErrStatusCodes { + for err := range ErrStatusCodes { m[err.Error()] = new(uint64) } diff --git a/post_test.go b/post_test.go index ea91f21..ae0a3d1 100644 --- a/post_test.go +++ b/post_test.go @@ -200,11 +200,17 @@ func TestPostWithUpload(t *testing.T) { Name: "Incorrect content type", Method: "POST", ReqHeader: map[string]string{ - "Tus-Resumable": "1.0.0", - "Content-Type": "application/false", + "Tus-Resumable": "1.0.0", + "Upload-Length": "300", + "Upload-Metadata": "foo aGVsbG8=, bar d29ybGQ=", + "Content-Type": "application/false", }, ReqBody: strings.NewReader("hello"), - Code: http.StatusBadRequest, + Code: http.StatusCreated, + ResHeader: map[string]string{ + "Location": "http://tus.io/files/foo", + "Upload-Offset": "", + }, }).Run(handler, t) (&httpTest{ diff --git a/s3store/multi_error.go b/s3store/multi_error.go index 7bdb830..a2d9e3d 100644 --- a/s3store/multi_error.go +++ b/s3store/multi_error.go @@ -5,7 +5,7 @@ import ( ) func newMultiError(errs []error) error { - message := "Multiple errors occured:\n" + message := "Multiple errors occurred:\n" for _, err := range errs { message += "\t" + err.Error() + "\n" } diff --git a/s3store/s3store.go b/s3store/s3store.go index 7b467e6..f006163 100644 --- a/s3store/s3store.go +++ b/s3store/s3store.go @@ -272,7 +272,7 @@ func (store S3Store) WriteChunk(id string, offset int64, src io.Reader) (int64, func (store S3Store) GetInfo(id string) (info tusd.FileInfo, err error) { uploadId, multipartId := splitIds(id) - // Get file info stored in seperate object + // Get file info stored in separate object res, err := store.Service.GetObject(&s3.GetObjectInput{ Bucket: aws.String(store.Bucket), Key: aws.String(uploadId + ".info"), @@ -335,7 +335,7 @@ func (store S3Store) GetReader(id string) (io.Reader, error) { Key: aws.String(uploadId), }) if err == nil { - // No error occured, and we are able to stream the object + // No error occurred, and we are able to stream the object return res.Body, nil } diff --git a/s3store/s3store_test.go b/s3store/s3store_test.go index de45129..d3f3c82 100644 --- a/s3store/s3store_test.go +++ b/s3store/s3store_test.go @@ -538,7 +538,7 @@ func TestTerminateWithErrors(t *testing.T) { }, nil) err := store.Terminate("uploadId+multipartId") - assert.Equal("Multiple errors occured:\n\tAWS S3 Error (hello) for object uploadId: it's me.\n", err.Error()) + assert.Equal("Multiple errors occurred:\n\tAWS S3 Error (hello) for object uploadId: it's me.\n", err.Error()) } func TestConcatUploads(t *testing.T) { diff --git a/uid/uid.go b/uid/uid.go index 1b53cdc..71f0cb3 100644 --- a/uid/uid.go +++ b/uid/uid.go @@ -15,7 +15,7 @@ func Uid() string { id := make([]byte, 16) _, err := io.ReadFull(rand.Reader, id) if err != nil { - // This is probably an appropiate way to handle errors from our source + // This is probably an appropriate way to handle errors from our source // for random bits. panic(err) } diff --git a/unrouted_handler.go b/unrouted_handler.go index 0876e5f..8442aec 100644 --- a/unrouted_handler.go +++ b/unrouted_handler.go @@ -126,10 +126,9 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler { r.Method = newMethod } - go func() { - handler.logger.Println(r.Method, r.URL.Path) - handler.Metrics.incRequestsTotal(r.Method) - }() + handler.log("RequestIncoming", "method", r.Method, "path", r.URL.Path) + + go handler.Metrics.incRequestsTotal(r.Method) header := w.Header() @@ -171,7 +170,7 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler { // will be ignored or interpreted as a rejection. // For example, the Presto engine, which is used in older versions of // Opera, Opera Mobile and Opera Mini, handles CORS this way. - w.WriteHeader(http.StatusOK) + handler.sendResp(w, r, http.StatusOK) return } @@ -191,15 +190,10 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler { // PostFile creates a new file upload using the datastore after validating the // length and parsing the metadata. func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) { - // Check for presence of application/offset+octet-stream - containsChunk := false - if contentType := r.Header.Get("Content-Type"); contentType != "" { - if contentType != "application/offset+octet-stream" { - handler.sendError(w, r, ErrInvalidContentType) - return - } - containsChunk = true - } + // Check for presence of application/offset+octet-stream. If another content + // type is defined, it will be ignored and treated as none was set because + // some HTTP clients may enforce a default value for this header. + containsChunk := r.Header.Get("Content-Type") == "application/offset+octet-stream" // Only use the proper Upload-Concat header if the concatenation extension // is even supported by the data store. @@ -268,6 +262,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) w.Header().Set("Location", url) go handler.Metrics.incUploadsCreated() + handler.log("UploadCreated", "id", id, "size", i64toa(size), "url", url) if isFinal { if err := handler.composer.Concater.ConcatUploads(id, partialUploads); err != nil { @@ -299,7 +294,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) } } - w.WriteHeader(http.StatusCreated) + handler.sendResp(w, r, http.StatusCreated) } // HeadFile returns the length and offset for the HEAD request @@ -347,7 +342,7 @@ func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request) w.Header().Set("Cache-Control", "no-store") w.Header().Set("Upload-Length", strconv.FormatInt(info.Size, 10)) w.Header().Set("Upload-Offset", strconv.FormatInt(info.Offset, 10)) - w.WriteHeader(http.StatusOK) + handler.sendResp(w, r, http.StatusOK) } // PatchFile adds a chunk to an upload. Only allowed enough space is left. @@ -402,7 +397,7 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request // Do not proxy the call to the data store if the upload is already completed if info.Offset == info.Size { w.Header().Set("Upload-Offset", strconv.FormatInt(offset, 10)) - w.WriteHeader(http.StatusNoContent) + handler.sendResp(w, r, http.StatusNoContent) return } @@ -411,7 +406,7 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request return } - w.WriteHeader(http.StatusNoContent) + handler.sendResp(w, r, http.StatusNoContent) } // PatchFile adds a chunk to an upload. Only allowed enough space is left. @@ -430,6 +425,8 @@ func (handler *UnroutedHandler) writeChunk(id string, info FileInfo, w http.Resp maxSize = length } + handler.log("ChunkWriteStart", "id", id, "maxSize", i64toa(maxSize), "offset", i64toa(offset)) + var bytesWritten int64 // Prevent a nil pointer derefernce when accessing the body which may not be // available in the case of a malicious request. @@ -444,6 +441,8 @@ func (handler *UnroutedHandler) writeChunk(id string, info FileInfo, w http.Resp } } + handler.log("ChunkWriteComplete", "id", id, "bytesWritten", i64toa(bytesWritten)) + // Send new offset to client newOffset := offset + bytesWritten w.Header().Set("Upload-Offset", strconv.FormatInt(newOffset, 10)) @@ -502,7 +501,7 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) // Do not do anything if no data is stored yet. if info.Offset == 0 { - w.WriteHeader(http.StatusNoContent) + handler.sendResp(w, r, http.StatusNoContent) return } @@ -518,7 +517,7 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) } w.Header().Set("Content-Length", strconv.FormatInt(info.Offset, 10)) - w.WriteHeader(http.StatusOK) + handler.sendResp(w, r, http.StatusOK) io.Copy(w, src) // Try to close the reader if the io.Closer interface is implemented @@ -566,7 +565,7 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) return } - w.WriteHeader(http.StatusNoContent) + handler.sendResp(w, r, http.StatusNoContent) if handler.config.NotifyTerminatedUploads { handler.TerminatedUploads <- info @@ -598,9 +597,18 @@ func (handler *UnroutedHandler) sendError(w http.ResponseWriter, r *http.Request w.WriteHeader(status) w.Write([]byte(reason)) + handler.log("ResponseOutgoing", "status", strconv.Itoa(status), "method", r.Method, "path", r.URL.Path, "error", err.Error()) + go handler.Metrics.incErrorsTotal(err) } +// sendResp writes the header to w with the specified status code. +func (handler *UnroutedHandler) sendResp(w http.ResponseWriter, r *http.Request, status int) { + w.WriteHeader(status) + + handler.log("ResponseOutgoing", "status", strconv.Itoa(status), "method", r.Method, "path", r.URL.Path) +} + // Make an absolute URLs to the given upload id. If the base path is absolute // it will be prepended else the host and protocol from the request is used. func (handler *UnroutedHandler) absFileURL(r *http.Request, id string) string { @@ -772,3 +780,7 @@ func extractIDFromPath(url string) (string, error) { } return result[1], nil } + +func i64toa(num int64) string { + return strconv.FormatInt(num, 10) +}