背景
在源網(wǎng)頁通過服務(wù)器重定向打開某個三方網(wǎng)頁,網(wǎng)絡(luò)層出現(xiàn)了 -1005 (NSURLErrorNetworkConnectionLost) 錯誤碼唇跨,排查差異后發(fā)現(xiàn)是由于給這個三方服務(wù)帶了源網(wǎng)頁特有的 HTTP Header停局,導(dǎo)致服務(wù)器檢查異常從而斷開連接尿瞭。
核心原因是跨域重定向場景透傳了 Header 帶到了三方服務(wù)瑟枫,這有些不符合常理,會帶來兩個明顯的問題:
- 敏感 HTTP Header 傳遞給三方服務(wù)逗堵,存在隱私安全問題;
- 服務(wù)收到未預(yù)期的 HTTP Header眷昆,可能被視為非法訪問蜒秤,導(dǎo)致網(wǎng)頁異常;
系統(tǒng)庫如何設(shè)計的
NSURLSession 在跨域重定向場景默認(rèn)會透傳 HTTP Header亚斋,參考 Swift 在 _HTTPURLProtocol 的相關(guān)處理:
/// If the response is a redirect, return the new request
/// RFC 7231 section 6.4 defines redirection behavior for HTTP/1.1
/// - SeeAlso: <https://tools.ietf.org/html/rfc7231#section-6.4>
func redirectRequest(for response: HTTPURLResponse, fromRequest: URLRequest) -> URLRequest? {
//TODO: Do we ever want to redirect for HEAD requests?
guard
let location = response.value(forHeaderField: .location),
let targetURL = URL(string: location)
else {
// Can't redirect when there's no location to redirect to.
return nil
}
var request = fromRequest
// Check for a redirect:
switch response.statusCode {
case 301...302 where request.httpMethod == "POST", 303:
// Change "POST" into "GET" but leave other methods unchanged:
request.httpMethod = "GET"
request.httpBody = nil
case 301...302, 305...308:
// Re-use existing method:
break
default:
return nil
}
// If targetURL has only relative path of url, create a new valid url with relative path
// Otherwise, return request with targetURL ie.url from location field
guard targetURL.scheme == nil || targetURL.host == nil else {
request.url = targetURL
return request
}
… (后面是相對路徑處理)
}
大致處理流程為:
- 取出響應(yīng)頭 Location 字段作為目標(biāo) URL作媚;
- 若為 POST 請求改為 GET 請求并清空其 Body;
- 若目標(biāo) URL 為相對路徑帅刊,補(bǔ)齊完整 URL纸泡;
可以看到重定向后的請求會直接繼承 HTTP Header,這個處理遵循了 RFC 7231 的規(guī)范厚掷,大致去翻了一下弟灼,只描述了 Location header field 的處理方法级解,而沒有說明其它請求頭該如何處理,在 Chrome 下重定向場景應(yīng)該是直接丟棄之前的 Header 的田绑。
我們知道有一個公開代理方法…willPerformHTTPRedirection…
可以去改變重定向請求勤哗,但如果不借助網(wǎng)絡(luò)攔截技術(shù),WebKit 里面的請求也無法修改掩驱,順便看一下 WebKit 內(nèi)部是否對這種場景有所處理芒划。
WebKit 是在 NetworkSessionCocoa 類里面承載 NSURLSession 請求的,實現(xiàn)了URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:
協(xié)議欧穴,順著處理鏈路從 Network 進(jìn)程跟到 Web 進(jìn)程再跟到 APP 進(jìn)程民逼,都沒有找到關(guān)于跨域重定向清理 HTTP Header 的處理,更不用說公開配置能力了涮帘。
解決方案
針對 WebView 需要跨域重定向的場景拼苍,如何避免私有 HTTP Header 傳遞給目標(biāo)請求服務(wù)?
方案一
如果前置請求是為了做統(tǒng)計上報调缨,那可以直接跳轉(zhuǎn)到目標(biāo) URL疮鲫,前置請求旁路去處理;如果前置請求是為了獲取跳轉(zhuǎn)的地址弦叶,那么可以發(fā)起一個 Ajax 請求拿到回包后去跳轉(zhuǎn)目標(biāo) URL俊犯。
或者更直接的,把 server-side redirect 改為 client-side redirect伤哺,讓前置請求返回文檔燕侠,文檔內(nèi)部進(jìn)行document.replace()
等函數(shù)跳轉(zhuǎn)到目標(biāo) URL,但這種處理會讓性能劣化立莉,并且會導(dǎo)致前置請求關(guān)聯(lián)的 Web 進(jìn)程歷史棧緩存被清理(參考:http://www.reibang.com/p/af67ed9af995)绢彤。
核心思想就是避免服務(wù)器跨域重定向,由于和 Chrome 內(nèi)核表現(xiàn)不一致且前端改造成本較大蜓耻,一般較難實施杖虾,但這對于沒有 WebKit 網(wǎng)絡(luò)攔截技術(shù)的 APP 來說可能是唯一思路。
方案二
如果有 WebKit 網(wǎng)絡(luò)攔截技術(shù)媒熊,那處理就比較簡單了奇适,只需要保證在重定向請求發(fā)起之前,如果主域名發(fā)生變化芦鳍,就把 APP 私有的請求頭清理掉嚷往,較簡單的規(guī)避系統(tǒng)設(shè)計問題。