用AFNetworking访问接口时附带多个cookie
问题:
最近在做一个登录相关的迭代开发时,后台对cookie的支持升级,变的更加“标准”,即:由 原来附在请求头的set-cookie是只对应一个value 变成 对应多个value。要求前段和移动端进行适配接收,保存新的set-cookie,并且在其他接口上传该结构的cookie。
分析:
问题分为两大部分,一是接收并解析保存,二是访问其他接口附带上传。因为接收解析相对简单,苹果有专用的NSHTTPCookie类支持,网上也有很多资料,本文就不多赘述,仅仅带一下。关于第二点,笔者百度了很久都没有合适的解决方案,最后还是在和后台的联调中摸索而出,故写本文以作记录。
一、接收,解析,保存
这个比较简单,无论是AFNetworking还是NSHTTPCookie类都简洁易用,直接上代码:
//从AFNetworking的post请求中获得请求头,该头的格式为NSDictionary
//其中operation是指AFHTTPRequestOperation *operation
NSDictionary *allHeaderFieldDict = [operation.response allHeaderFields];
//用NSHTTPCookie直接将请求头中的setCookie转成键值对形式的数组,后面用到。另外说下NSHTTPCookie会自动过滤非setCookie的键值对。
NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:allHeaderFieldDict forURL:[NSURL URLWithString:url]];
//用字符串的形式拿到set-Cookie对应的value
NSString *setCookie = [allHeaderFieldDict objectForKey:@"Set-Cookie"];
这里说下,后台给我的set-Cookie是多个的,多个是个什么概念呢?后台给我了一张图,便于理解
图1.多个set-Cookie.png
是不是感到很疑惑,如果是key-value的形式,众所周知一个Map(或者说是字典)里,key是唯一的,不可能是一个“set-Cookie”作为key对应多个value。验证这一点我们可以打印下上面代码中的setCookie字符串,看取到了什么? 图2.这是setCookie字符串的打印结果 为了方便阅读我把字符串粘出来并对其一下,*为我手动打码
id=*****; domain=.*******.com; expires=Fri, 08-Dec-2017 05:48:20 GMT; path=/,
mobile=***********; domain=.*******.com; expires=Fri, 08-Dec-2017 05:48:20 GMT; path=/,
timestart=2017/11/23 13:48:20; domain=.*******.com; expires=Fri, 08-Dec-2017 05:48:20 GMT; path=/,
timespan=15; domain=.*******.com; expires=Fri, 08-Dec-2017 05:48:20 GMT; path=/,
identifier=85EF24DAF969423EA129C6A15863936B; domain=.*******.com; expires=Fri, 08-Dec-2017 05:48:20 GMT; path=/,
token=ae9721de81f3b6c336ccd94abae9c698; domain=.*******.com; expires=Fri, 08-Dec-2017 05:48:20 GMT; path=/
这里要着重说明三点,这三点在上传时很重要。
- OC中key-value是标准的一对一,所以后台发的图1那种形式被转成了图2的样子。其vaule是包含上图所有value的字符串,这个字符串的格式很重要!!!
- 我进行了换行操作,分成了6行,注意从第二行开始位置错了一个空格。这不是我要逼死强迫症的操作,而是原字符串在第一行结尾和第二行开头中间本来就有一个空格。我实在想不出怎么体现这个空格就出此下策。要是还不信你就仔细看图2里面是不是真的有空格。为什么说这么多废话强调一个空格,因为格式很重要!!!
- 还是格式问题,每个;和,的后面空格,仔细观察。格式很重要!!!
太长了,截不下,不过不影响理解,从上图我们可以方便的看出图1的格式。而且这中方法比较好的点在于,不在乎后面的一个key对应了多少个value,有几个value数组就有几个元素。像我公司原来的后台就只有1个value还能用字符串去取,现在改成多个了,显然这个方法更利于理解。
接收到,解析完,储存的话,我相信你肯定会存字符串,也肯定会存数组吧。
二、访问时上传
一般的cookie上传如下:
//manager即[AFHTTPRequestOperationManager manager];返回的单例
//cookStr即cookie
[manager.requestSerializer setValue:cookStr forHTTPHeaderField:@"Cookie"];
事实上AFNetworking只有这一个方法将一个键值对加入请求头,同样value自然也必须是字符串。
对于我程序中遇到的多个cookie
- 我考虑过将几个键值对变成字典然后用上面的方法放入请求头,但显然字典不符合value必须是String的设定。
- 然后我有考虑将字典转成JSONString放入value,但后台拿到的是带有转义字符的字符串,无法解析。
- 如果将cookie分别以自己的key(比如id,mobile,timestart等)分别加入HTTPHeaderField又不符合后台的标准化需求。
- 后台反复强调将cookie以发送给我的方式返回,经过再三调试和思考,发现拿到的cookie其本质是一个字符串,但这个字符是有格式的!(所以说格式很重要!!!)
最后经过调试,返回了如下格式的字符串,*为手动打码
NSString * cookStr = @"id=*****; mobile=***********; timestart=2017/11/23 13:48:20; timespan=15; identifier=85EF24DAF969423EA129C6A15863936B; token=ae9721de81f3b6c336ccd94abae9c698"
注意,id前面与token最后面没有空格,但每个;后面有一个空格。token字符串最后没有;
[manager.requestSerializer setValue:cookStr forHTTPHeaderField:@"Cookie"];
将以上字符放入请求头后,经过请求后台反馈可以解析返回的cookie。
长求总:
如果你的后台需要在请求头里返回一个包含若干个cookie的set-Cookie,你可以按以下格式尝试返回。务必切记字符串中哪里有空格哪里没空格。
NSString *cookStr = @"key1=value1; key2=value2; key3=value3; key4=value4"
[manager.requestSerializer setValue:cookStr forHTTPHeaderField:@"Cookie"];
最后夹带点私货 /滑稽
- 这个是将图2字符串处理成最后上传的字符串的方法,按需自取
/**
* 其中的需要传入参数在本文第一段中有详细代码
*/
-(NSString *)quickLoginMakeCookieWithCookiesArray:(NSArray *)cookies withCookieString:(NSString *)setCookie {
NSMutableArray *titleArray = [[NSMutableArray alloc] init];
for (NSHTTPCookie *cookie in cookies) {
[titleArray addObject:[NSString stringWithFormat:@"%@=",cookie.name]];
}
NSMutableString *lastString = [[NSMutableString alloc] init];
for (NSString *str in titleArray) {
NSRange tokenRange = [setCookie rangeOfString:str];
NSInteger beginLoc = tokenRange.location+tokenRange.length;
NSInteger count = 0;
for (;;) {
count++;
NSString *emunStr = [setCookie substringWithRange:NSMakeRange(beginLoc++, 1)];
if ([emunStr isEqualToString:@";"]) {
break;
}
}
NSString *valueString = [setCookie substringWithRange:NSMakeRange(tokenRange.location+tokenRange.length, --count)];
NSString *resultString = [NSString stringWithFormat:@"%@%@",str,valueString];
if ([str isEqualToString:@"id="]) {
[lastString appendFormat:@"%@",resultString];
}
else {
[lastString appendFormat:@"; %@",resultString];
}
}
return lastString;
}
- 解决联调中格式或者参数不对时候的一个小Tip,一般来说后台自己在开发完代码后也会用浏览器插件/模拟器发起一个网络请求来调试代码。当然这个东西也是可以打印参数的。如果搞不定你的发送参数的话可以让他把参数打印出来给你,你对比你发送的参数,就能找到问题的所在了。
- 最后,若有错误,恳请斧正。