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 */
#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] getIpByHostAsync: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 forHTTPHeaderField:
@"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适配
- 使用如下方式HTTPDNS解析"http://test.com" 域名进行访问,
String urlStr =
"http://test.com";
// HTTPDNS解析"test.com"域名,发HTTP请求访问
new
DownloadWebPageTask().execute(urlStr);
private
class DownloadWebPageTask
extends AsyncTask<String, Void,
String> {@Overrideprotected
String doInBackground(
String... params) {
try {
return downloadUrl(params[
0]);}
catch (IOException e) {e.printStackTrace();}
return
null;}@Overrideprotected 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.getIpByHostAsync(urlStr);
if (urlStr.equals(url2)) {ip = IP;}
if (ip !=
null) {Log.d(TAG,
"Get IP: " + ip +
" for host: " + url.getHost() +
" from HTTPDNS successfully!");
String
new
UrlStr = urlStr.replaceFirst(url.getHost(), ip);conn = (HttpURLConnection)
new
URL(
new
UrlStr).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;}
/*** 配置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的domain为test.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();}