資源伺服器搭建

weixin_33936401發表於2018-01-14

閒的沒事,想著自己搭建一個資源伺服器可能以後能用到。選擇使用Nginx + Golang搭建,結果這一搞就搭進去了三個晚上,都是淚啊。所以決定寫一篇部落格來祭奠一下逝去的三個晚上。

環境依賴

  • Nginx我安裝的版本為nginx version: nginx/1.12.2
  • upload-nginx-module標準的Nginx是不包含upload-nginx-module的,所在編譯的時候記得新增
  • Golang環境go version go1.8.1 darwin/amd64

上面提及這些具體怎麼安裝,我就不在這詳細描述了,大家可以自行Google解決

資源伺服器搭建思路

  • 客戶端上傳檔案資源到Nginx伺服器
  • Nginx伺服器將資源儲存在臨時目錄,將請求轉發給Golang業務伺服器
  • 業務伺服器將資源移動位置,重新命名操作
  • 業務伺服器返回處理結果給客戶端

當然還有一些其他設計思路,比如可以將圖片直接上傳給業務伺服器,再由業務伺服器傳給資源伺服器。或者直接將圖片傳給業務伺服器,等客戶端向資源伺服器請求資源的時候,再由資源伺服器來業務伺服器取。但是後面說的這兩種,如果要做斷點續傳,都需要業務伺服器自己處理一些邏輯。但是第一種設計,就可以把斷電續傳等功能都交由資源伺服器處理了,因此我選擇了第一種設計。

nginx配置

    
        location /upload {
            #上傳完成後後端接受處理檔案 
            upload_pass @uploadImageGo;
            #檔案上傳路徑,檔案臨時路徑 
            upload_store /Users/liuhu/Desktop/nginxRoot/www/upload/images; 
            #上傳速度限制
            upload_limit_rate 2048k;
            #檔案讀寫許可權 
            upload_store_access user:rw;
            #表單中提交的資訊
            upload_set_form_field $upload_field_name.name "$upload_file_name";
            upload_set_form_field $upload_field_name.content_type "$upload_content_type";
            upload_set_form_field $upload_field_name.path "$upload_tmp_path";
            #儲存完畢後的資訊
            upload_aggregate_form_field "$upload_field_name.md5" "$upload_file_md5";
            upload_aggregate_form_field "$upload_field_name.size" "$upload_file_size";
            #轉發的時候只傳submit和description
            #upload_pass_form_field "^submit$|^description$";
            #開啟開關,意思就是把前端請求的引數會傳給後端
            upload_pass_args on; 
            #將表單中所有資訊傳遞給後端使用,客戶端提交的資訊也傳遞
            upload_pass_form_field "^.*$";
            #發生這些錯無的時候刪除資源
            upload_cleanup 400 404 499 500-505; 
        }
        
        location @uploadImageGo {
          proxy_method POST;
            proxy_pass http://localhost:8081;
        }

注意:必須使用Post請求/uoload介面,不能nginx會直接報錯

業務伺服器處理

三個晚上就坑在了這裡

  • 當時呼叫r.ParseForm()之後,就直接呼叫r.Form發現Form是一個空的Map,但是Content-Length又是大於0

  • 然而想看到底有沒有穿引數過來,就改了nginx的幾個配置。比如少傳幾個引數或者多傳幾個引數,發現Content-Length又是有變化的,證明引數是穿過來的

  • 沒有辦法只能就把整個httpdump出來了

    • dump整個http的程式碼如下

          httpData, _ := httputil.DumpRequest(r, true)
          log.Println(string(httpData))
      
    • dump的結果是

          POST /upload HTTP/1.0
          Host: localhost:8081
          Connection: close
          Accept: */*
          Connection: close
          Content-Length: 778
          Content-Type: multipart/form-data; boundary=------------------------767b3fb9efcce66b
          User-Agent: curl/7.54.0
      
          --------------------------767b3fb9efcce66b
          Content-Disposition: form-data; name="image.name"
      
          vim_keymap.png
          --------------------------767b3fb9efcce66b
          Content-Disposition: form-data; name="image.content_type"
      
          application/octet-stream
          --------------------------767b3fb9efcce66b
          Content-Disposition: form-data; name="image.path"
      
          /Users/liuhu/Desktop/nginxRoot/www/upload/images/0006817569
          --------------------------767b3fb9efcce66b
          Content-Disposition: form-data; name="image.md5"
      
          62519e639e490a69d0106bceb7e0f184
          --------------------------767b3fb9efcce66b
          Content-Disposition: form-data; name="image.size"
      
          176536
          --------------------------767b3fb9efcce66b
          Content-Disposition: form-data; name="sss"
      
          sss
          --------------------------767b3fb9efcce66b--
      
    • 發現引數全部放在了Content-Disposition裡面,並且引數還是用一個動態的boundary進行的分割。

  • 通過dump資訊發現請求的Content-Typemultipart/form-data。因此查了一下Golang的文件,發現multipart/form-data需要先將整個請求的body解析到記憶體中,才能從Form中獲取到相應的欄位。

    ParseMultipartForm parses a request body as multipart/form-data. The whole request body is parsed and up to a total of maxMemory bytes of its file parts are stored in memory, with the remainder stored on disk in temporary files. ParseMultipartForm calls ParseForm if necessary. After one call to ParseMultipartForm, subsequent calls have no effect.

  • 服務端的主要程式碼如下

    if r.Method == "POST" {
        r.ParseMultipartForm(32 << 20)
        log.Println(r.Form)
        //處理圖片
        //todo
        w.Write([]byte("上傳成功"))
    }
    

相關文章