HTTPDNS域名解析場景下如何使用Cookie?

1. Cookie

由於HTTP協議是無狀態的,爲了維護服務端和客戶端的會話狀態,客戶端可存儲服務端返回的Cookie,之後請求中可攜帶Cookie標識狀態。

客戶端根據服務端返回的攜帶Set-Cookie的HTTP Header來創建一個Cookie,Set-Cookie爲字符串,主要字段如下:

Set-Cookie: [name1=value1, name2=value2;],[expires=date;],[path=path;],[domain=domain;]
  • Cookie信息爲形如name=value的字符串;
  • expires,Cookie過期時間;
  • domain,Cookie適用域名;
  • path,請求資源URL中必須存在指定的路徑時,纔會發送該Cookie。

2. Cookie存儲策略

  • 基於iOS平臺說明服務端Set-Cookie配置和客戶端Cookie存儲策略。

2.1 準備工作

  • 訪問域名test.com,假定HTTPDNS域名解析結果爲201.87.1.125
  • Web服務器設置Cookie如下,domain字段待定:
"Set-Cookie" = "name1=value1; expires=Wed, 15-Nov-17 15:41:02 GMT; path=/"
  • 客戶端發送普通HTTP請求:
- (void)connectToUrlString:(NSString *)urlString {
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"error: %@", error);
        } else {
            NSLog(@"response: %@", response);
            NSLog(@"data: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        }
    }];
    [task resume];
}
  • 客戶端使用HTTPDNS服務發送HTTP請求:
- (void)connectToUrlStringUsingHTTPDNS:(NSString *)urlString {
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    NSString *ip = [[HttpDnsService sharedInstance] getIpByHost:url.host];
    if (ip) {
      NSLog(@"Get IP(%@) for host(%@) from HTTPDNS Successfully!", ip, url.host);
      NSRange hostFirstRange = [urlString rangeOfString:url.host];
      if (hostFirstRange.location != NSNotFound) {
        NSString * newUrlString = [urlString stringByReplacingCharactersInRange:hostFirstRange withString:ip];
        request.URL = [NSURL URLWithString:newUrlString];
        [request setValue:url.host forHTTPHeaderField:@"host"];
      }
    }
    NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"error: %@", error);
        } else {
            NSLog(@"response: %@", response);
            NSLog(@"data: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        }
    }];
    [task resume];
}
  • 查詢本App存儲全部Cookie:
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [cookieStorage cookies]) {
    NSLog(@"cookie: %@", cookie);
}
  • 查詢本App存儲指定URL對應適配Cookie:
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSDictionary *cookiesDic = [NSHTTPCookie requestHeaderFieldsWithCookies:[cookieStorage cookiesForURL:url]];

2.2 指定domain的Cookie

服務端配置Set-Cookie如下,domain設置爲.test.com

"Set-Cookie" = "name1=value1; expires=Wed, 15-Nov-17 15:41:02 GMT; path=/; domain=.test.com"

客戶端調用[self connectToUrlString:@"http://test.com"];發送HTTP請求後查詢本地Cookie如下;再次訪問時,HTTP頭部自動添加該Cookie併發送到服務端。

name1 = value1;
expires = Wed, 15-Nov-17 15:41:02 GMT;
path = /;
domain = .test.com;

客戶端調用[self connectToUrlStringUsingHTTPDNS:@"http://test.com"];,使用HTTPDNS服務發送HTTP請求,客戶端同樣收到上述domain.test.com的Cookie,iOS網絡庫關於Cookie的默認存儲策略爲NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomaindomainURL無法匹配時(使用HTTPDNS服務發送HTTP請求時,原生URL.host被替換爲IP地址),該Cookie不會存儲,因此再次發送請求時無法使用Cookie。

2.3 未指定domain的Cookie

若服務端配置Set-Cookiedomain不配置,

"Set-Cookie" = "name1=value1; expires=Wed, 15-Nov-17 15:41:02 GMT; path=/;

客戶端發送HTTP請求返回Cookie如下,domain字段爲空。

name1 = value1;
expires = Wed, 15-Nov-17 15:41:02 GMT;
path = /;

iOS網絡庫存儲該Cookie時,自動填充Cookie的domain字段爲HTTP請求的URL.host,即普通HTTP請求存儲Cookie如下:

name1 = value1;
expires = Wed, 15-Nov-17 15:41:02 GMT;
path = /;
domain = .test.com;

使用HTTPDNS訪問的Cookie存儲如下,再次使用HTTPDNS進行HTTP請求時,網絡庫默認Cookie匹配規則可以匹配到該Cookie。(此場景下使用HTTPDNS服務發送HTTP請求,雖然默認Cookie匹配規則可正確匹配Cookie,但是該場景依賴服務端Cookie的配置,爲了安全性,通常服務端返回Set-Cookie的domain字段不爲空。)

name1 = value1;
expires = Wed, 15-Nov-17 15:41:02 GMT;
path = /;
domain = 201.87.1.125;

3. 適配策略

適配目的是在使用HTTPDNS服務,可以像通用HTTP請求對Cookie進行存儲匹配發送,這就需要自行管理使用HTTPDNS服務的HTTP請求的Cookie。

  • 存儲,收到服務端返回的HTTP Header Set-Cookie,可以正確解析並存儲;
  • 匹配,發送HTTP請求前,可正確搜索匹配Cookie;
  • 發送,將匹配的Cookie放入HTTP請求中發送到服務端。

3.1 iOS適配

  • 根據蘋果文檔說明,按照以下方式可以改變Cookie接受策略,文檔說明默認策略爲NSHTTPCookieAcceptPolicyAlways,經測試發現默認策略其實爲NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain;使用以下方式手動將策略修改爲NSHTTPCookieAcceptPolicyAlways,測試發現Accept Cookie Always沒有生效,原因需要進一步確定。
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
[cookieStorage setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
  • 實現了簡單的HTTPDNS專用的HTTPDNSCookieManager(【注意】此處僅提供Cookie管理的示例,Cookie管理細節如生命週期、匹配規則等不涉及,如有需要可參考RFC 2965),適用於使用HTTPDNS服務的HTTP請求,

  • 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<NSHTTPCookie *> *)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 <Foundation/Foundation.h>
#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<NSHTTPCookie *> *)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

  • 使用HTTPDNS處理Cookie示例(使用上述默認簡單匹配策略):
- (void)connectToUrlStringUsingHTTPDNS:(NSString *)urlString {
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    NSString *ip = [[HttpDnsService sharedInstance] getIpByHost:url.host];
    if (ip) {
        NSLog(@"Get IP(%@) for host(%@) from HTTPDNS Successfully!", ip, url.host);
        NSRange hostFirstRange = [urlString rangeOfString:url.host];
        if (hostFirstRange.location != NSNotFound) {
            NSString *newUrlString = [urlString stringByReplacingCharactersInRange:hostFirstRange withString:ip];
            NSLog(@"New URL: %@", newUrlString);
            request.URL = [NSURL URLWithString:newUrlString];
            [request setValue:url.host forHTTPHeade點擊打開鏈接rField:@"host"];
            // 匹配合適Cookie添加到request中,這裏傳入的是原生URL
            [request setValue:[[HTTPDNSCookieManager sharedInstance] getRequestCookieHeaderForURL:url] forHTTPHeaderField:@"Cookie"];
            // 刪除Cookie
            [[HTTPDNSCookieManager sharedInstance] deleteCookieForURL:url];
        }
    }
    NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"error: %@", error);
        } else {
            NSLog(@"response: %@", response);
            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
            // 解析HTTP Response Header,存儲cookie
            [[HTTPDNSCookieManager sharedInstance] handleHeaderFields:[httpResponse allHeaderFields] forURL:url];
            NSLog(@"data: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
            NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        }
    }];
    [task resume];
}

3.2 Android適配

String urlStr = "http://test.com";
// HTTPDNS解析"test.com"域名,發HTTP請求訪問
new DownloadWebPageTask().execute(urlStr);

private class DownloadWebPageTask extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String... params) {
            try {
                return downloadUrl(params[0]);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            if (result != null) {
                Log.e(TAG, "Get response: " + result);
            } else {
                Log.e(TAG, "Get response error.");
            }
        }
    }

    private String downloadUrl(String urlStr) throws IOException {
        try {
            URL url = new URL(urlStr);
            conn = (HttpURLConnection) url.openConnection();
            String ip = httpDnsService.getIpByHost(urlStr);
            if (urlStr.equals(url2)) {
                ip = IP;
            }
            if (ip != null) {
                Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
                String newUrlStr = urlStr.replaceFirst(url.getHost(), ip);
                conn = (HttpURLConnection) new URL(newUrlStr).openConnection();
                conn.setRequestProperty("Host", url.getHost());
            }
            conn.setConnectTimeout(1000 * 15);
            int responseCode = conn.getResponseCode();
            if (responseCode == 200) {
                Log.i(TAG, "Reponse code is 200.");
                dis = new DataInputStream(conn.getInputStream());
                int len;
                byte[] buff = new byte[4096];
                StringBuilder response = new StringBuilder();
                while ((len = dis.read(buff)) != -1) {
                    response.append(new String(buff, 0, len));
                }
                return response.toString();
            } else {
                Log.e(TAG, "Response code is " + responseCode);
                return null;
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return null;
    }
  • CookieManager配置如下:
/**
 *  配置cookie manager
 */
private void setCookieHandler() {
    /**
     *  指定cookie存儲policy
     *  系統已定義策略有:
     *  CookiePolicy.ACCEPT_ALL,存儲全部cookie
     *  CookiePolicy.ACCEPT_NONE,不存儲cookie
     *  CookiePolicy.ACCEPT_ORIGINAL_SERVER,按照RFC 2965 3.3節標準域名匹配存儲cookie
     * */
    cookieManager.setCookiePolicy(new CookiePolicy() {
        @Override
        public boolean shouldAccept(URI uri, HttpCookie cookie) {
            // 爲方便測試,此處都返回true,存儲全部cookie
            Log.i(TAG, "Uri: " + uri.toString() + ", cookie: " + cookie.toString() + ", domain: " + cookie.getDomain());
            return true;
        }
    });
    CookieHandler.setDefault(cookieManager);  }
  • 使用CookieManager管理App的Cookie緩存,當對應Cookie滿足策略存儲到本地時,針對使用HTTPDNS域名解析進行HTTP訪問的場景,不用修改即可完成適配。
  • 基於HTTPDNS訪問http://test.com,獲取到Cookie如下,shouldAccept()返回true後網絡庫自動緩存該Cookie;
name1 = value1;
expires = Wed, 15-Nov-17 15:41:02 GMT;
path = /;
domain = .test.com;
  • Cookie存儲時,該Cookie直接和訪問的URL(http://201.87.1.125)相關聯,並且Cookie的domaintest.com;所以,再次使用HTTPDNS服務防僞URL(http://201.87.1.125)時,系統可自動獲取到該Cookie。Cookie存儲後可按照下面代碼測試,基於域名/IP的URL查詢緩存Cookie,都可以正確獲取該Cookie,因此使用HTTPDNS服務時可自動完成適配。
String url1 = "http://test.com";
String url2 = "http://201.87.1.125";
CookieStore cookieStore = cookieManager.getCookieStore();
try {
    Log.e(TAG, "store cookie is " + cookieStore.get(new URI(url1)));
    Log.e(TAG, "store cookie is " + cookieStore.get(new URI(url2)));
} catch (URISyntaxException e) {
    e.printStackTrace();
}	
點擊打開鏈接
發佈了35 篇原創文章 · 獲贊 109 · 訪問量 93萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章