天天看點

使用七牛雲存儲的一些經驗總結

近段時間将使用七牛雲存儲來存放使用者上傳的資料,用戶端通過七牛的js-sdk與七牛互動,服務端C#實作了七牛相關的接口。在這過程中多多少少遇到點問題,在這裡總結一下。原文: 使用七牛雲存儲的一些經驗總結

599錯誤處理

如果在與七牛的互動中出現http狀态碼為599的錯誤,一句話,不要猶豫,直接聯系七牛技術支援 。七牛的文檔也在很多地方提到這個錯誤,都是指導大家去聯系技術支援的。筆者是在分塊上傳後的 

mkfile

 調用時出現的,聯系技術支援後,說是調整了一下,讓我重試。後來就好了...

分塊上傳無法從回調中獲得檔案的原始名

簡單上傳采用的是multipart/form-data方式上傳,七牛服務端能夠從請求中獲得檔案的原始名,并支援使用魔法變量 

$(fname)

 回調業務伺服器。不過當使用分片上傳的時候情況有所不同。分片上傳需要在最後調用 

mkfile

 ,來将分片拼接起來。但是,

mkfile

 接口支援普通的請求,并沒有附帶檔案名,是以七牛也就無法獲得檔案名,此時從 

$(fname)

 中是取不到檔案名的。這個問題我也向七牛技術支援送出了問題,得到的結果是使用自定義變量 

mkfile

 支援将自定義變量放在url中,回調的時候自定義變量可以傳遞給業務伺服器。

慎用圖檔預處理

七牛雲支援很多對檔案的預處理,其中最常用的應該就是圖檔預處理了,可以對圖檔的大小做變換等。七牛推薦使用GET的方式直接指定圖檔處理結果的url,像這樣:

http://qiniuphotos.qiniudn.com/gogopher.jpg?imageView2/1/w/200/h/200
                

處理後的圖檔會自動緩存,使用者不用關心,隻要每次通路都用這個url就行了。然而,筆者在開始的時候,為了保持與其他檔案形式統一的處理方法,對圖檔使用了預處理(因為視訊什麼的隻能預處理),即在token中指定了預處理。此時問題出現了,從背景的日志看到,圖檔的預處理通知回調竟然比正常的上傳成功回調還要快!這就導緻預處理結果到來之前,我的業務伺服器的資料庫中還沒有這個圖檔,無法儲存預處理結果了。是以 推薦還是使用url直接處理,對圖檔要慎用預處理

視訊檔案無法快進播放

通常使用者在觀看視訊的時候都會根據自己的喜好,快速将視訊定位到指定的時間播放。實作這個功能,需要視訊本身有關鍵幀資訊、服務端需要支援關鍵幀播放請求,在 這篇文章 中有詳細讨論。

但是筆者發現,在使用七牛雲轉化後的視訊,這樣做是無效的。于是咨詢技術支援,得到的答案是:轉化的檔案是具有關鍵幀的,但七牛使用CDN加速,是以關鍵幀請求需要CDN的支援,如果想要用這個功能的話,需要單獨聯系銷售或技術支援在CDN上配置,而且時間比較長。筆者聯系了銷售和技術支援,說是幫我配置,但到現在還沒有搞定,因為最近這個也不是特别重要,是以也沒有跟下去。

Callback校驗

這是可選的一個步驟。由于七牛雲會在上傳完成之後回調業務伺服器,是以理論上說業務伺服器需要校驗這個回調的合理性。原理在七牛的 文檔 中有,需要用到 

HMAC-SHA1

 簽名函數。但是七牛的sdk中沒有提供直接的方式來做校驗,在研讀文檔、多次失敗和檢視sdk源碼後,筆者終于校驗成功了。關鍵的分歧在于,文檔中的這句話:

擷取明文:data = Request.URL.Path +”\n” +Request.Body

這裡的 

Request.URL.Path

 是否包含Querystring?答案是包含的!下面是筆者C#服務端的校驗代碼,使用的是ASP.NET Web Api:

```C#

byte[] key = System.Text.Encoding.UTF8.GetBytes(Qiniu.Conf.Config.SECRET_KEY);

using (HMACSHA1 hmac = new HMACSHA1(key))

{

var t = filterContext.Request.Content.ReadAsStringAsync();

t.Wait();

string rawbody = t.Result;

log.DebugFormat("request's rawbody : {0}", rawbody);

string text = filterContext.Request.RequestUri.PathAndQuery + "\n" + rawbody;

log.DebugFormat("PathAndQuery + \n + rawbody : {0}", text);

byte[] digest = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(text));

string computed = Qiniu.Util.Base64URLSafe.Encode(digest);

log.DebugFormat("Computed hash after base64 : {0}", computed);

IEnumerable<string> auths;
  if (filterContext.Request.Headers.TryGetValues("Authorization", out auths) && auths.Count() == 1)
  {
    string auth = auths.First();
    log.DebugFormat("Authorization in header : {0}", auth);
    if (auth.StartsWith("QBox "))
    {
      var arr = auth.Substring(5).Split(':');
      if (arr.Length == 2)
      {
        if (arr[1] != computed)
        {
          log.ErrorFormat("Authorization failed. Since auth from header {0} not equals computed {1}", arr[1], computed);
        }
        else
        {
          log.Debug("Authorization success.");
          //only pass can be return
          return;
        }
      }
      else
      {
        log.Error("Callback Authorization's format is invalid, can not find two part after split by ':'.");
      }
    }
    else
    {
      log.Error("Callback Authorization's format is invalid, missing leading 'QBox '.");
    }
  }
  else
  {
    log.Error("The request from qiniu callback is missing 'Authorization'");
  }

  filterContext.Response = filterContext.Request.CreateResponse(System.Net.HttpStatusCode.Forbidden);

}
           
如下幾個注意點:

- 明文應當是請求的path+querystring部分和rawbody
- 對于.NET而言,明文和key都需要用UTF-8編碼變換成位元組才能進行簽名。而php中的hash_hmac函數完全不用這麼複雜...
- 簽名的結果再用base64的url安全的方式編碼,再與請求的http頭部的Authorization比較

建議官方在文檔中加入一些相對底層一些的程式設計語言的實作,php太高端了...


## js-sdk實作略顯粗糙 ##

在使用過程中,我發現[官方的js-sdk](https://github.com/qiniupd/qiniu-js-sdk/)有幾個我覺得不好的地方:

**不能為每個檔案擷取UpToken**

試想,在檔案上傳過程中有擷取UpToken是必須的,而且UpToken又需要包含預處理指令,不同的檔案顯然需要不同的UpToken,而在js-sdk的實作中,隻在初始化這個上傳元件對象的時候請求一次上傳憑證,後面所有的上傳都需要使用這個預先得到的UpToken:

```javascript
    uploader.bind('Init', function(up, params) {
        getUpToken();
    });

           

于是我修改了這部分,在 

BeforeUpload

 事件中請求UpToken。建議官方考慮更改這個地方

隻能實作分片上傳,無法斷點續傳

js-sdk的實作在分片上傳的實作上,是很簡單的,不僅沒有使用分片,而是分塊(一塊4m,調用mkblk),而且沒有實作持久化ctx,或者類似的回調或接口。4m分塊這個問題還可以不追究,沒有實作持久化ctx就說不過去了,不持久化怎麼實作斷點續傳撒?!就算不實作,也應該給出回調的入口,讓調用者來實作持久化,而我實在無法找到這個'空子'可鑽,隻能直接在源碼上改動了。

沒有複用流行類庫的東西

這個其實算不上問題,因為作為一個不依賴jquery的sdk,當然不能使用jquery現成的東西,比如ajax。不依賴jquery就算了,依賴plupload是幾個意思嘛,還依賴全局對象...于是最後,我幹脆自己将sdk改成了Backbone的類,将不要的東西統統去掉,使用jquery和underscore簡化代碼了...

繼續閱讀