Go Trace transfer.sh open project

因為工作需求,需要一個透過 cli 就可以 upload 和 download 的工具,剛好 open project transfer.sh 有提供類似的功能,不過我們有額外的需求,包含每個 request 都要能 fiter IP 和執行 HTTP Basic Auth 驗證,因此就有這個機會來分析一下 transfer.sh source code 並且後續可以針對我們需求來修改!

Source code 分析

HTTP Router

首先來看 transfer.sh 的 source code,這個 project 其實程式碼蠻少的,所以大概花個幾個小時就可以大致了解運作方式。transfer.sh 是一個簡單的 Web server,其 path router 是使用 gorilla/mux,雖然 gorilla/mux 的 path search 是 slice 設計, route 效率不比 radix tree router 效能好,不過因為這個 Web server 的 path 也不多,所以就還可以接受。

Upload Files

下圖為 Upload 行為簡易流程圖:

hugo

在上傳檔案的時候,可以透過一開始 start server 的 params 設定要不要有 HTTP Basic Auth 驗證 (http-auth-user, http-auth-pass),不過這樣子的設定也意味著 Basic Auth 的帳號密碼是直接寫死在 server struct,無法透過其他方式來對帳號密碼進行驗證 (e.g. 打 API 到其他 account service),不太符合我們的需求,因此這是要調整的地方之一。

當使用者上傳檔案時,除了上傳檔案到指定的 storage 之外,也會額外儲存 MetadataMetadata 包含了基本檔案資訊,以及下載次數、時限,和刪除檔案時所需要的 token,這樣使用者進行下載時,我們可以透過 Metadata 的內容來進行下載驗證,像是下載次數是不是過多、或是已經超過下載時限等。使用者在發送 request 時,可以透過 Max-Downloads , Max-Days 來設定這兩項數值,

這邊要額外注意的是,在上傳檔案之前,會亂數產生一個 token,接著檔案會被上傳到 <token>/<filename> 的位置,以避免名稱重複所造成的衝突。此外,從 source code 可以發現,作者使用 Mutex 來控制 goroutine 對特定檔案進行讀寫行為,而不是發現檔案被開啟就直接回傳 error。

在上傳完檔案後,發送回給使用者的 HTTP response 除了會包含 download URL 之外,也會帶有指定的 Delete URL X-Url-Delete ,讓前端可以進一步地設計刪除按鈕來供使用者刪除該檔案。

關於 http-auth-user, http-auth-pass

http-auth-userhttp-auth-pass 的驗證只會套用在 upload 相關 API 上,並不會影響 download API。所以即使設定了這個參數, download 還是允許所有使用者使用。

The difference of uploading files using POST and PUT

transfer.sh 可以使用 POSTPUT 兩種 HTTP Method 來上傳檔案,主要差異在於 PUT 預設使用者只上傳一個檔案,因此 request 的 Content-Type header 就是檔案的類型,而 request body 則是檔案內容,這樣使用者也可以很方便地使用以下 cmd 來上傳檔案。(Note: –upload-file 是使用 PUT HTTP Method)

1curl --upload-file ./example.md "http://example.com/example.md"

POST 則是預期 request 的 Content Type 為 multipart/form-data,因此使用者可以一次上傳多個檔案,而 Response 也會包含個別檔案的下載 URL。缺點是使用 POST 會缺少 X-Url-Delete 這個 Delete Header 讓使用者可以手動刪除檔案,所以雖然它提供了比較完整的上傳功能,卻也少了些便利設計。

1url -i -X POST -H "Content-Type: multipart/form-data" -F "file=@./examples.md" -F "file=@./examples2.md" "http://example.com/"

Download Files

剛剛有提到,上傳檔案後會從 response 得到下載的 URL,而接下來的行為會根據我們怎麼使用 URL 而有所差異:如果使用者是使用瀏覽器開啟這 URL 的話,那 server 會先回傳 preview 畫面,preview 畫面會有檔案格式和檔案大小等資訊,如果是 markdown 類型的文檔甚至還可以呈現出所有內容給使用者先預覽。(實現方式是透過 mux 的 MatcherFunc,比對是否含有 Accept: text/html 這個 header,來判斷是否使用者是不是使用瀏覽器); 反之,如果是使用像是 curl 來開啟連接,那就會直接下載檔案,而不會出現 preview 的 html 頁面。

下圖為下載檔案的簡易流程圖(省略 preview 畫面):

hugo

使用者發送下載的 request 後,server 會先取出 Metadata,檢查 Metadata 內的時限和下載次數是否有超過設定數值,接著再取出檔案並且回傳檔案內容。雖然取出檔案的流程比較簡單,但也有發現一些問題,例如檢查 Metadata 時發現下載已經超過時限或是下載次數時,只會直接回傳 404 Not Found,並不會主動刪除該檔案。

調整項目整理

根據以上 source code 分析,列出幾個可以調整的地方:

  1. 不要直接將 http-auth-user http-auth-pass 這個驗證機制直接寫死在 server struct 中,可以改用 interface 方式來處理。
  2. Metadata 的內容長度不大,如果改用 cache 的方式來記錄,可以提高性能。
  3. 上傳後的檔案有很大機會不會被刪除,所以可能需要在檢查 Metadata 時發現過期時可以主動刪除,或是定期掃上傳後的檔案,檢查哪些檔案已沒在使用。
  4. 目前 IP Filter 是針對整個 server,而 Basic Auth 只有在上傳檔案才會有,因此需要補足下載檔案的驗證機制。

接下來就會實際調整 code,來讓 code 更符合我的需求 XD