diff --git a/test/tus/Makefile b/test/tus/Makefile index d07d6c2b6..1a944a4f0 100644 --- a/test/tus/Makefile +++ b/test/tus/Makefile @@ -95,12 +95,13 @@ start-seaweedfs: check-binary > /tmp/seaweedfs-tus-volume.log 2>&1 & @sleep 3 - # Start filer server + # Start filer server with TUS enabled @echo "Starting filer server..." @nohup $(SEAWEEDFS_ROOT)/weed/weed filer \ -port=$(FILER_PORT) \ -master=127.0.0.1:$(MASTER_PORT) \ -ip=127.0.0.1 \ + -tus.path=/.tus \ > /tmp/seaweedfs-tus-filer.log 2>&1 & @sleep 5 diff --git a/test/tus/README.md b/test/tus/README.md index e8913d82f..e948c06d1 100644 --- a/test/tus/README.md +++ b/test/tus/README.md @@ -40,6 +40,20 @@ TUS is an open protocol for resumable file uploads over HTTP. It allows clients - `Upload-Offset` - Current byte offset - `Location` - Upload URL (on POST) +## Enabling TUS + +TUS protocol support must be explicitly enabled when starting the filer server using the `-tus.path` flag: + +```bash +# Start filer with TUS enabled at /.tus path +weed filer -master=localhost:9333 -tus.path=/.tus + +# Or use a custom path +weed filer -master=localhost:9333 -tus.path=/uploads/tus +``` + +If `-tus.path` is not specified, TUS endpoints are disabled. + ## Test Structure ### Integration Tests diff --git a/weed/command/filer.go b/weed/command/filer.go index 86991a181..d60312f5e 100644 --- a/weed/command/filer.go +++ b/weed/command/filer.go @@ -74,6 +74,7 @@ type FilerOptions struct { diskType *string allowedOrigins *string exposeDirectoryData *bool + tusPath *string certProvider certprovider.Provider } @@ -109,6 +110,7 @@ func init() { f.diskType = cmdFiler.Flag.String("disk", "", "[hdd|ssd|] hard drive or solid state drive or any tag") f.allowedOrigins = cmdFiler.Flag.String("allowedOrigins", "*", "comma separated list of allowed origins") f.exposeDirectoryData = cmdFiler.Flag.Bool("exposeDirectoryData", true, "whether to return directory metadata and content in Filer UI") + f.tusPath = cmdFiler.Flag.String("tus.path", "", "TUS resumable upload endpoint path, e.g., /.tus (disabled if empty)") // start s3 on filer filerStartS3 = cmdFiler.Flag.Bool("s3", false, "whether to start S3 gateway") @@ -334,6 +336,7 @@ func (fo *FilerOptions) startFiler() { DownloadMaxBytesPs: int64(*fo.downloadMaxMBps) * 1024 * 1024, DiskType: *fo.diskType, AllowedOrigins: strings.Split(*fo.allowedOrigins, ","), + TusPath: *fo.tusPath, }) if nfs_err != nil { glog.Fatalf("Filer startup error: %v", nfs_err) diff --git a/weed/server/filer_server.go b/weed/server/filer_server.go index a76994b89..d35485ad2 100644 --- a/weed/server/filer_server.go +++ b/weed/server/filer_server.go @@ -79,6 +79,7 @@ type FilerOption struct { DiskType string AllowedOrigins []string ExposeDirectoryData bool + TusPath string } type FilerServer struct { @@ -196,7 +197,13 @@ func NewFilerServer(defaultMux, readonlyMux *http.ServeMux, option *FilerOption) if !option.DisableHttp { defaultMux.HandleFunc("/healthz", requestIDMiddleware(fs.filerHealthzHandler)) // TUS resumable upload protocol handler - defaultMux.HandleFunc("/.tus/", fs.filerGuard.WhiteList(requestIDMiddleware(fs.tusHandler))) + if option.TusPath != "" { + tusPath := option.TusPath + if !strings.HasSuffix(tusPath, "/") { + tusPath += "/" + } + defaultMux.HandleFunc(tusPath, fs.filerGuard.WhiteList(requestIDMiddleware(fs.tusHandler))) + } defaultMux.HandleFunc("/", fs.filerGuard.WhiteList(requestIDMiddleware(fs.filerHandler))) } if defaultMux != readonlyMux { diff --git a/weed/server/filer_server_tus_handlers.go b/weed/server/filer_server_tus_handlers.go index 5a95b249f..8fe93a6e7 100644 --- a/weed/server/filer_server_tus_handlers.go +++ b/weed/server/filer_server_tus_handlers.go @@ -35,11 +35,15 @@ func (fs *FilerServer) tusHandler(w http.ResponseWriter, r *http.Request) { // Route based on method and path path := r.URL.Path - tusPrefix := "/.tus" + tusPrefix := fs.option.TusPath + if tusPrefix == "" { + tusPrefix = "/.tus" + } - // Check if this is an upload location (contains upload ID after /.tus/.uploads/) - if strings.HasPrefix(path, tusPrefix+"/.uploads/") { - uploadID := strings.TrimPrefix(path, tusPrefix+"/.uploads/") + // Check if this is an upload location (contains upload ID after {tusPrefix}/.uploads/) + uploadsPrefix := tusPrefix + "/.uploads/" + if strings.HasPrefix(path, uploadsPrefix) { + uploadID := strings.TrimPrefix(path, uploadsPrefix) uploadID = strings.Split(uploadID, "/")[0] // Get just the ID, not any trailing path switch r.Method { @@ -97,8 +101,14 @@ func (fs *FilerServer) tusCreateHandler(w http.ResponseWriter, r *http.Request) // Parse Upload-Metadata header (optional) metadata := parseTusMetadata(r.Header.Get("Upload-Metadata")) + // Get TUS path prefix + tusPrefix := fs.option.TusPath + if tusPrefix == "" { + tusPrefix = "/.tus" + } + // Determine target path from request URL - targetPath := strings.TrimPrefix(r.URL.Path, "/.tus") + targetPath := strings.TrimPrefix(r.URL.Path, tusPrefix) if targetPath == "" || targetPath == "/" { http.Error(w, "Target path required", http.StatusBadRequest) return @@ -116,7 +126,7 @@ func (fs *FilerServer) tusCreateHandler(w http.ResponseWriter, r *http.Request) } // Build upload location URL - uploadLocation := fmt.Sprintf("/.tus/.uploads/%s", uploadID) + uploadLocation := fmt.Sprintf("%s/.uploads/%s", tusPrefix, uploadID) // Handle creation-with-upload extension if r.ContentLength > 0 && r.Header.Get("Content-Type") == "application/offset+octet-stream" {