ip访问 webstorem_常见问题-iOS WebView IP直连 如何处理 Cookie
WKWebView 無法使用 NSURLProtocol 攔截請求
針對該問題方案如下:
換用 UIWebView
使用私有 API 進行注冊攔截
換用 UIWebView 方案不做贅述,說明下使用私有 API 進行注冊攔截的方法 :
// 注冊自己的 protocol
[NSURLProtocol registerClass:[CustomProtocol class]];
// 創建 WKWebview
WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc] init];
WKWebView * wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) configuration:config];
[wkWebView loadRequest:webViewReq];
[self.view addSubview:wkWebView];
//注冊 scheme
Class cls = NSClassFromString(@"WKBrowsingContextController");
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([cls respondsToSelector:sel]) {
// 通過 http 和 https 的請求,同理可通過其他的 Scheme 但是要滿足 ULR Loading System
[cls performSelector:sel withObject:@"http"];
[cls performSelector:sel withObject:@"https"];
}
使用私有 API 的另一風險是兼容性問題,比如上面的 `browsingContextController` 就只能在 iOS 8.4 以后才能用,反注冊 scheme 的方法 `unregisterSchemeForCustomProtocol :` 也是在 iOS 8.4 以后才被添加進來的,要支持 iOS 8.0 ~ 8.3 機型的話,只能通過動態生成字符串的方式拿到 `WKBrowsingContextController`,而且還不能反注冊,不過這些問題都不大。至于向后兼容,這個也不用太擔心,因為 iOS 發布新版本之前都會有開發者預覽版的,那個時候再測一下也不遲。對于本文的例子來說,如果將來哪個 iOS 版本移除了這個 API,那很可能是因為官方提供了完整的解決方案,到那時候自然也不需要本文介紹的方法了 。
注意: 避免執行太晚,如果在 `- (void)viewDidLoad` 中注冊,可能會因為注冊太晚,引發問題。建議在 `+load` 方法中執行 。
然后同樣會遇到 [《 HTTPS SNI 業務場景 “ IP直連 ” 方案說明 》](https://help.aliyun.com/document_detail/60147.html) 里提到的各種 NSURLProtocol 相關的問題,可以參照里面的方法解決 。
WebView 中的 Cookie 處理業務場景 IP直連 方案說明
下文將討論類似這樣的問題 :
WKWebView 對于 Cookie 的管理一直是它的短板,那么 iOS11 是否有改進,如果有,如何利用這樣的改進 ?
采用 IP 直連方案后,服務端返回的 Cookie 里的 Domain 字段也會使用 IP 。如果 IP 是動態的,就有可能導致一些問題:由于許多 H5 業務都依賴于 Cookie 作登錄態校驗 ,而 WKWebView 上請求不會自動攜帶 Cookie 。
WKWebView 使用 NSURLProtocol 攔截請求無法獲取 Cookie 信息
iOS 11 推出了新的 API WKHTTPCookieStore 可以用來攔截 WKWebView 的 Cookie 信息
用法示例如下 :
WKHTTPCookieStore *cookieStroe = self.webView.configuration.websiteDataStore.httpCookieStore;
// get cookies
[cookieStroe getAllCookies:^(NSArray * _Nonnull cookies) {
NSLog(@"All cookies %@",cookies);
}];
// set cookie
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSHTTPCookieName] = @"userid";
dict[NSHTTPCookieValue] = @"123";
dict[NSHTTPCookieDomain] = @"xxxx.com";
dict[NSHTTPCookiePath] = @"/";
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:dict];
[cookieStroe setCookie:cookie completionHandler:^{
NSLog(@"set cookie");
}];
// delete cookie
[cookieStroe deleteCookie:cookie completionHandler:^{
NSLog(@"delete cookie");
}];
利用 iOS 11 API WKHTTPCookieStore 解決 WKWebView 首次請求不攜帶 Cookie 的問題
問題說明:由于許多 H5 業務都依賴于 Cookie 作登錄態校驗,而 WKWebView 上請求不會自動攜帶 Cookie 。比如,如果你在 Native 層面做了登陸操作,獲取了 Cookie 信息,也使用 NSHTTPCookieStorage 存到了本地,但是使用 WKWebView 打開對應網頁時,網頁依然處于未登陸狀態。如果是登陸也在 WebView 里做的,就不會有這個問題。
iOS 11 的 API 可以解決該問題,只要是存在 WKHTTPCookieStore 里的 cookie,WKWebView 每次請求都會攜帶,存在 NSHTTPCookieStorage 的 cookie,并不會每次都攜帶。于是會發生首次 WKWebView 請求不攜帶 Cookie 的問題。
解決方法:
在執行 -[WKWebView loadReques:] 前將 NSHTTPCookieStorage 中的內容復制到 WKHTTPCookieStore 中,以此來達到 WKWebView Cookie 注入的目的。示例代碼如下:
[self copyNSHTTPCookieStorageToWKHTTPCookieStoreWithCompletionHandler:^{
NSURL *url = [NSURL URLWithString:@"https://www.v2ex.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[_webView loadRequest:request];
}];
- (void)copyNSHTTPCookieStorageToWKHTTPCookieStoreWithCompletionHandler:(nullable void (^)())theCompletionHandler; {
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
WKHTTPCookieStore *cookieStroe = self.webView.configuration.websiteDataStore.httpCookieStore;
if (cookies.count == 0) {
!theCompletionHandler ?: theCompletionHandler();
return;
}
for (NSHTTPCookie *cookie in cookies) {
[cookieStroe setCookie:cookie completionHandler:^{
if ([[cookies lastObject] isEqual:cookie]) {
!theCompletionHandler ?: theCompletionHandler();
return;
}
}];
}
}
這個是 iOS 11 的 API ,針對 iOS 11 之前的系統 ,需要另外處理。
作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這有個iOS交流群:642363427,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術,iOS開發者一起交流學習成長!
利用 iOS 11 之前的 API 解決 WKWebView 首次請求不攜帶 Cookie 的問題
通過讓所有 WKWebView 共享同一個 WKProcessPool 實例,可以實現多個 WKWebView 之間共享 Cookie(session Cookie and persistent Cookie)數據。不過 WKWebView WKProcessPool 實例在 app 殺進程重啟后會被重置,導致 WKProcessPool 中的 Cookie、session Cookie 數據丟失,目前也無法實現 WKProcessPool 實例本地化保存。可以采取 cookie 放入 Header 的方法來做。
WKWebView * webView = [WKWebView new];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx.com/login"]];
[request addValue:@"skey=skeyValue" forHTTPHeaderField:@"Cookie"];
[webView loadRequest:request];
其中對于 skey=skeyValue 這個 cookie 值的獲取,也可以統一通過 domain 獲取,獲取的方法,可以參照下面的工具類:
HTTPDNSCookieManager.h
#ifndef HTTPDNSCookieManager_h
#define HTTPDNSCookieManager_h
// URL匹配Cookie規則
typedef BOOL (^HTTPDNSCookieFilter)(NSHTTPCookie *, NSURL *);
@interface HTTPDNSCookieManager : NSObject
+ (instancetype)sharedInstance;
/**
指定URL匹配Cookie策略
@param filter 匹配器
*/
- (void)setCookieFilter:(HTTPDNSCookieFilter)filter;
/**
處理HTTP Reponse攜帶的Cookie并存儲
@param headerFields HTTP Header Fields
@param URL 根據匹配策略獲取查找URL關聯的Cookie
@return 返回添加到存儲的Cookie
*/
- (NSArray *)handleHeaderFields:(NSDictionary *)headerFields forURL:(NSURL *)URL;
/**
匹配本地Cookie存儲,獲取對應URL的request cookie字符串
@param URL 根據匹配策略指定查找URL關聯的Cookie
@return 返回對應URL的request Cookie字符串
*/
- (NSString *)getRequestCookieHeaderForURL:(NSURL *)URL;
/**
刪除存儲cookie
@param URL 根據匹配策略查找URL關聯的cookie
@return 返回成功刪除cookie數
*/
- (NSInteger)deleteCookieForURL:(NSURL *)URL;
@end
#endif /* HTTPDNSCookieManager_h */
HTTPDNSCookieManager.m
#import
#import "HTTPDNSCookieManager.h"
@implementation HTTPDNSCookieManager
{
HTTPDNSCookieFilter cookieFilter;
}
- (instancetype)init {
if (self = [super init]) {
/**
此處設置的Cookie和URL匹配策略比較簡單,檢查URL.host是否包含Cookie的domain字段
通過調用setCookieFilter接口設定Cookie匹配策略,
比如可以設定Cookie的domain字段和URL.host的后綴匹配 | URL是否符合Cookie的path設定
細節匹配規則可參考RFC 2965 3.3節
*/
cookieFilter = ^BOOL(NSHTTPCookie *cookie, NSURL *URL) {
if ([URL.host containsString:cookie.domain]) {
return YES;
}
return NO;
};
}
return self;
}
+ (instancetype)sharedInstance {
static id singletonInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!singletonInstance) {
singletonInstance = [[super allocWithZone:NULL] init];
}
});
return singletonInstance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return [self sharedInstance];
}
- (id)copyWithZone:(struct _NSZone *)zone {
return self;
}
- (void)setCookieFilter:(HTTPDNSCookieFilter)filter {
if (filter != nil) {
cookieFilter = filter;
}
}
- (NSArray *)handleHeaderFields:(NSDictionary *)headerFields forURL:(NSURL *)URL {
NSArray *cookieArray = [NSHTTPCookie cookiesWithResponseHeaderFields:headerFields forURL:URL];
if (cookieArray != nil) {
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in cookieArray) {
if (cookieFilter(cookie, URL)) {
NSLog(@"Add a cookie: %@", cookie);
[cookieStorage setCookie:cookie];
}
}
}
return cookieArray;
}
- (NSString *)getRequestCookieHeaderForURL:(NSURL *)URL {
NSArray *cookieArray = [self searchAppropriateCookies:URL];
if (cookieArray != nil && cookieArray.count > 0) {
NSDictionary *cookieDic = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieArray];
if ([cookieDic objectForKey:@"Cookie"]) {
return cookieDic[@"Cookie"];
}
}
return nil;
}
- (NSArray *)searchAppropriateCookies:(NSURL *)URL {
NSMutableArray *cookieArray = [NSMutableArray array];
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [cookieStorage cookies]) {
if (cookieFilter(cookie, URL)) {
NSLog(@"Search an appropriate cookie: %@", cookie);
[cookieArray addObject:cookie];
}
}
return cookieArray;
}
- (NSInteger)deleteCookieForURL:(NSURL *)URL {
int delCount = 0;
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [cookieStorage cookies]) {
if (cookieFilter(cookie, URL)) {
NSLog(@"Delete a cookie: %@", cookie);
[cookieStorage deleteCookie:cookie];
delCount++;
}
}
return delCount;
}
@end
使用方法示例:
發送請求
WKWebView * webView = [WKWebView new];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx.com/login"]];
NSString *value = [[HTTPDNSCookieManager sharedInstance] getRequestCookieHeaderForURL:url];
[request setValue:value forHTTPHeaderField:@"Cookie"];
[webView loadRequest:request];
接收處理請求:
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
// 解析 HTTP Response Header,存儲cookie
[[HTTPDNSCookieManager sharedInstance] handleHeaderFields:[httpResponse allHeaderFields] forURL:url];
}
}];
[task resume];
通過 document.cookie 設置 Cookie 解決后續頁面(同域)Ajax、iframe 請求的 Cookie 問題;
WKUserContentController* userContentController = [WKUserContentController new];
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"document.cookie = 'skey=skeyValue';" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userContentController addUserScript:cookieScript];
Cookie包含動態 IP 導致登陸失效問題
關于 COOKIE 失效的問題,假如客戶端登錄 session 存在 COOKIE,此時這個域名配置了多個 IP,使用域名訪問會讀對應域名的 COOKIE,使用IP訪問則去讀對應 IP 的 COOKIE,假如前后兩次使用同一個域名配置的不同 IP 訪問,會導致 COOKIE 的登錄 session 失效,
如果 APP 里面的 webview 頁面需要用到系統 COOKIE 存的登錄 session,之前 APP 所有本地網絡請求使用域名訪問,是可以共用 COOKIE 的登錄 session 的,但現在本地網絡請求使用 httpdns后改用 IP 訪問,導致還使用域名訪問的 webview 讀不到系統 COOKIE 存的登錄 session 了(系統 COOKIE 對應 IP 了)。IP 直連后,服務端返回 Cookie 包含動態 IP 導致登陸失效。
使用 IP 訪問后,服務端返回的 cookie 也是 IP。導致可能使用對應的域名訪問,無法使用本地 cookie,或者使用隸屬于同一個域名的不同 IP 去訪問,cookie 也對不上,導致登陸失效 。
我這邊的思路是這樣的,
應該得干預 cookie 的存儲,基于域名。
根源上,api 域名返回單 IP
第二種思路將失去 DNS 調度特性,故不考慮。第一種思路更為可行。
基于 iOS11 API WKHTTPCookieStore 來解決 WKWebView 的 Cookie 管理問題
當每次服務端返回cookie后,在存儲前都進行下改造,使用域名替換下IP。之后雖然每次網絡請求都是使用IP訪問,但是host我們都手動改為了域名,這樣本地存儲的 cookie 也就能對得上了。
代碼演示:
在網絡請求成功后,或者加載網頁成功后,主動將本地的 domain 字段為 IP 的 Cookie 替換 IP 為 host 域名地址。
- (void)updateWKHTTPCookieStoreDomainFromIP:(NSString *)IP toHost:(NSString *)host {
WKHTTPCookieStore *cookieStroe = self.webView.configuration.websiteDataStore.httpCookieStore;
[cookieStroe getAllCookies:^(NSArray * _Nonnull cookies) {
[[cookies copy] enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull cookie, NSUInteger idx, BOOL * _Nonnull stop) {
if ([cookie.domain isEqualToString:IP]) {
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:cookie.properties];
dict[NSHTTPCookieDomain] = host;
NSHTTPCookie *newCookie = [NSHTTPCookie cookieWithProperties:[dict copy]];
[cookieStroe setCookie:newCookie completionHandler:^{
[self logCookies];
[cookieStroe deleteCookie:cookie
completionHandler:^{
[self logCookies];
}];
}];
}
}];
}];
}
iOS 11 中也提供了對應的 API 供我們來處理替換 Cookie 的時機,那就是下面的 API:
@protocol WKHTTPCookieStoreObserver
@optional
- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore;
@end
//WKHTTPCookieStore
/*! @abstract Adds a WKHTTPCookieStoreObserver object with the cookie store.
@param observer The observer object to add.
@discussion The observer is not retained by the receiver. It is your responsibility
to unregister the observer before it becomes invalid.
*/
- (void)addObserver:(id)observer;
/*! @abstract Removes a WKHTTPCookieStoreObserver object from the cookie store.
@param observer The observer to remove.
*/
- (void)removeObserver:(id)observer;
用法如下:
@interface WebViewController ()
- (void)viewDidLoad {
[super viewDidLoad];
[NSURLProtocol registerClass:[WebViewURLProtocol class]];
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
[cookieStorage setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
WKHTTPCookieStore *cookieStroe = self.webView.configuration.websiteDataStore.httpCookieStore;
[cookieStroe addObserver:self];
[self.view addSubview:self.webView];
//... ...
}
#pragma mark -
#pragma mark - WKHTTPCookieStoreObserver Delegate Method
- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore {
[self updateWKHTTPCookieStoreDomainFromIP:CYLIP toHost:CYLHOST];
}
-updateWKHTTPCookieStoreDomainFromIP 方法的實現,在上文已經給出。
這個方案需要客戶端維護一個IP —> HOST的映射關系,需要能從 IP 反向查找到 HOST,這個維護成本還時挺高的。下面介紹下,更通用的方法,也是iOS11 之前的處理方法:
iOS 11 之前的處理方法:NSURLProtocal 攔截后,手動管理 Cookie 的存儲:
步驟:做 IP 替換時將原始 URL 保存到 Header 中
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReq = [request mutableCopy];
NSString *originalUrl = mutableReq.URL.absoluteString;
NSURL *url = [NSURL URLWithString:originalUrl];
// 異步接口獲取IP地址
NSString *ip = [[HttpDnsService sharedInstance] getIpByHostAsync:url.host];
if (ip) {
NSRange hostFirstRange = [originalUrl rangeOfString:url.host];
if (NSNotFound != hostFirstRange.location) {
NSString *newUrl = [originalUrl stringByReplacingCharactersInRange:hostFirstRange withString:ip];
mutableReq.URL = [NSURL URLWithString:newUrl];
[mutableReq setValue:url.host forHTTPHeaderField:@"host"];
// 添加originalUrl保存原始URL
[mutableReq addValue:originalUrl forHTTPHeaderField:@"originalUrl"];
}
}
NSURLRequest *postRequestIncludeBody = [mutableReq cyl_getPostRequestIncludeBody];
return postRequestIncludeBody;
}
然后獲取到數據后,手動管理 Cookie :
- (void)handleCookiesFromResponse:(NSURLResponse *)response {
NSString *originalURLString = [self.request valueForHTTPHeaderField:@"originalUrl"];
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSDictionary *allHeaderFields = httpResponse.allHeaderFields;
if (originalURLString && originalURLString.length > 0) {
NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:allHeaderFields forURL: [[NSURL alloc] initWithString:originalURLString]];
if (cookies && cookies.count > 0) {
NSURL *originalURL = [NSURL URLWithString:originalURLString];
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies forURL:originalURL mainDocumentURL:nil];
}
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)newRequest completionHandler:(void (^)(NSURLRequest *))completionHandler {
NSString *location = response.allHeaderFields[@"Location"];
NSURL *url = [[NSURL alloc] initWithString:location];
NSMutableURLRequest *mRequest = [newRequest mutableCopy];
mRequest.URL = url;
if (location && location.length > 0) {
if ([[newRequest.HTTPMethod lowercaseString] isEqualToString:@"post"]) {
// POST重定向為GET
mRequest.HTTPMethod = @"GET";
mRequest.HTTPBody = nil;
}
[mRequest setValue:nil forHTTPHeaderField:@"host"];
// 在這里為 request 添加 cookie 信息。
[self handleCookiesFromResponse:response];
[XXXURLProtocol removePropertyForKey:XXXURLProtocolHandledKey inRequest:mRequest];
completionHandler(mRequest);
} else{
completionHandler(mRequest);
}
}
發送請求前,向請求中添加Cookie信息:
+ (void)handleCookieWithRequest:(NSMutableURLRequest *)request {
NSString* originalURLString = [request valueForHTTPHeaderField:@"originalUrl"];
if (!originalURLString || originalURLString.length == 0) {
return;
}
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
if (cookies && cookies.count >0) {
NSDictionary *cookieHeaders = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
NSString *cookieString = [cookieHeaders objectForKey:@"Cookie"];
[request addValue:cookieString forHTTPHeaderField:@"Cookie"];
}
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReq = [request mutableCopy];
//...
[self handleCookieWithRequest:mutableReq];
return [mutableReq copy];
}
推薦👇:
如果你想一起進階,不妨添加一下交流群642363427
總結
以上是生活随笔為你收集整理的ip访问 webstorem_常见问题-iOS WebView IP直连 如何处理 Cookie的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ssm 转发请求_SSM框架碰到的问题
- 下一篇: cmap参数 plt_Matplotli