diff --git a/cmd/tusd/cli/flags.go b/cmd/tusd/cli/flags.go index 728a485..b4e713a 100644 --- a/cmd/tusd/cli/flags.go +++ b/cmd/tusd/cli/flags.go @@ -8,6 +8,7 @@ import ( var Flags struct { HttpHost string HttpPort string + HttpSock string MaxSize int64 UploadDir string StoreSize int64 @@ -16,7 +17,7 @@ var Flags struct { S3Bucket string S3ObjectPrefix string S3Endpoint string - GCSBucket string + GCSBucket string FileHooksDir string HttpHooksEndpoint string HttpHooksRetry int @@ -33,6 +34,7 @@ var Flags struct { func ParseFlags() { flag.StringVar(&Flags.HttpHost, "host", "0.0.0.0", "Host to bind HTTP server to") flag.StringVar(&Flags.HttpPort, "port", "1080", "Port to bind HTTP server to") + flag.StringVar(&Flags.HttpSock, "unix-sock", "", "If set, will listen to a UNIX socket at this location instead of a TCP socket") flag.Int64Var(&Flags.MaxSize, "max-size", 0, "Maximum size of a single upload in bytes") flag.StringVar(&Flags.UploadDir, "dir", "./data", "Directory to store uploads in") flag.Int64Var(&Flags.StoreSize, "store-size", 0, "Size of space allowed for storage") diff --git a/cmd/tusd/cli/listener.go b/cmd/tusd/cli/listener.go index 054a39f..afd7aba 100644 --- a/cmd/tusd/cli/listener.go +++ b/cmd/tusd/cli/listener.go @@ -1,7 +1,9 @@ package cli import ( + "errors" "net" + "os" "time" ) @@ -98,3 +100,40 @@ func NewListener(addr string, readTimeout, writeTimeout time.Duration) (net.List } return tl, nil } + +// Binds to a UNIX socket. If the file already exists, try to remove it before +// binding again. This logic is borrowed from Gunicorn +// (see https://github.com/benoitc/gunicorn/blob/a8963ef1a5a76f3df75ce477b55fe0297e3b617d/gunicorn/sock.py#L106) +func NewUnixListener(path string, readTimeout, writeTimeout time.Duration) (net.Listener, error) { + stat, err := os.Stat(path) + + if err != nil { + if !os.IsNotExist(err) { + return nil, err + } + } else { + if stat.Mode()&os.ModeSocket != 0 { + err = os.Remove(path) + + if err != nil { + return nil, err + } + } else { + return nil, errors.New("specified path is not a socket") + } + } + + l, err := net.Listen("unix", path) + + if err != nil { + return nil, err + } + + tl := &Listener{ + Listener: l, + ReadTimeout: readTimeout, + WriteTimeout: writeTimeout, + } + + return tl, nil +} diff --git a/cmd/tusd/cli/serve.go b/cmd/tusd/cli/serve.go index c510862..a1e1754 100644 --- a/cmd/tusd/cli/serve.go +++ b/cmd/tusd/cli/serve.go @@ -1,12 +1,19 @@ package cli import ( + "net" "net/http" "time" "github.com/tus/tusd" ) +// Setups the different components, starts a Listener and give it to +// http.Serve(). +// +// By default it will bind to the specified host/port, unless a UNIX socket is +// specified, in which case a different socket creation and binding mechanism +// is put in place. func Serve() { SetupPreHooks(Composer) @@ -24,10 +31,17 @@ func Serve() { stderr.Fatalf("Unable to create handler: %s", err) } - address := Flags.HttpHost + ":" + Flags.HttpPort basepath := Flags.Basepath + address := "" + + if Flags.HttpSock != "" { + address = Flags.HttpSock + stdout.Printf("Using %s as socket to listen.\n", address) + } else { + address = Flags.HttpHost + ":" + Flags.HttpPort + stdout.Printf("Using %s as address to listen.\n", address) + } - stdout.Printf("Using %s as address to listen.\n", address) stdout.Printf("Using %s as the base path.\n", basepath) SetupPostHooks(handler) @@ -47,8 +61,15 @@ func Serve() { http.Handle(basepath, http.StripPrefix(basepath, handler)) + var listener net.Listener timeoutDuration := time.Duration(Flags.Timeout) * time.Millisecond - listener, err := NewListener(address, timeoutDuration, timeoutDuration) + + if Flags.HttpSock != "" { + listener, err = NewUnixListener(address, timeoutDuration, timeoutDuration) + } else { + listener, err = NewListener(address, timeoutDuration, timeoutDuration) + } + if err != nil { stderr.Fatalf("Unable to create listener: %s", err) }