AFNetworking 遇上 ASP.NET 2.0:ERROR!

AFNetworking 是一套總是會出現在 Top 10 iOS open source libraries 推薦列表中的一個開源專案。很完整的處理了 HTTP 相關的 Operations,在現在的行動應用上由於很難會沒有使用到 WebApi,所以我想大部分的 iOS App 應該都用了這套(早期應該是 ,但因為停止更新了,所以現在通常都推薦 AFN),雖然有些討論指出 ASI 的效能其實比較好而且比較完整,但這就不在本文討論範圍了,有興趣的讀者可以自行 Google 看看。

鍵人(聽說現在不能自稱筆者,只能稱鍵人)最近在寫一個小小 App ,有需要爬一些網頁資料,於是使用了 AFNetworking 來解除我對於 NSURLConnection的使用焦慮。但使用了 AFNetworking 之後卻一開始就卡住了,只要我一下 HttpClient get 去 request 某一個網頁,AFN 就會回傳下圖所示的錯誤,主要的錯誤資訊是:

Culture “zh-Hant” is a neutral culture. It cannot be used in formatting and parsing and therefore cannot be set as the thread”s current culture.

這實在讓我有點困惑,因為我用瀏覽器去瀏覽這個網頁是正常的,所以我看了一下 Chrome 的開發工具中的 Network 分頁上顯示的 request header,猜測應該是 Accept-Language 導致的(雖然這邊講得很容易,但鍵人是看了很久才猜測到的啦)。於是去看了一下 AFN 填的 Accept-Language,發現真的是填 zh-Hant 耶。但根據 HTTP/1.1 的 spec 裡面寫的 subtag 應該是 ISO 3166 縮寫,所以如果是在台灣應該就是要 zh-TW,在香港的話是 zh-HK,新加坡是 zh-SG,怎麼樣也不會是  zh-Hant (註)啊。

再來就是去看看 zh-Hant 這個字串是怎麼來的。

在 AFHttpClient.m 中一開始的 initWithBaseURL 中有這麼一段  code,所以 zh-Hant 這個值是 iOS 的 Locale object 回報出來的,這剛好對應到 iOS 的語言設定,如果設定為繁體中文就是 zh-Hant,而簡體中文就會是 zh-Hans,而這兩個剛好都會導致 ASP.NET 2.0 的程式在分析 CultureInfo 出錯(據說 3.0 已經有 handle zh-Hant / zh-Hans)

    
// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    float q = 1.0f - (idx * 0.1f);
    [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
    *stop = q
}];
[self setDefaultHeader:@"Accept-Language" value:[acceptLanguagesComponents componentsJoinedByString:@", "]];

 

那既然 HTTP/1.1 spec 都說是要用 zh-TW / zh-HK … ,所以修正這個問題的方法就是去把對應的 language tag 做 mapping,於是我做了如下修改:

NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    float q = 1.0f - (idx * 0.1f);

    if ([(NSString*)obj compare:@"zh-Hant"] == NSOrderedSame)
        obj = @"zh-TW";
    else if ([(NSString*)obj compare:@"zh-Hans"] == NSOrderedSame)
        obj = @"zh-CN";

    [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
    *stop = q 
}];
[self setDefaultHeader:@"Accept-Language" value:[acceptLanguagesComponents componentsJoinedByString:@", "]];

這麼一來就可以保證送出去的 header 是符合 spec 而且也可以順利地得到正確的  HTTP response。但這改法有個缺點是我還找不到方法從 iOS API 拿到 TW / HK / SG 的 ISO 3166 code,因此只能寫死 zh-TW。另外,很令人困惑的一點是其實 accept-language 指定的是一串 list,不是只有單一語言,理論上 ASP 遇到第一個錯誤應該再往後看其他語言才是?但鍵人對於 ASP 不甚瞭解,所以就只先打住了 🙂

BTW,雖然這改法似乎不怎麼漂亮,但我還是送了 Pull Request 出去了  XD

註:zh-Hant 以及 zh-Hans 是新制定的 Langauge Tag 用來取代以前的 zh-Cht 以及 zh-Chs,但我還沒看到這邊跟 HTTP/1.1 Accept-Language 之間的對應關係。



Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *