1 公司最近准备上线银行存管,相关的对接都已完成,如今进入测试尾声,准备近期发布新版上线。
2 存管相关目前都是经过加载h5,主要包括银行开户,充值,提现,投标等大功能模块。(细节不少,就不在这里说了)
3 对h5结果处理大概逻辑:存管页面成功以后会返回一个结果url,客户端截取到这个url,再作相应的处理,例如:投资成功就跳转到投资成功结果展现页,充值成功就跳转到充值结果倒计时页面等等(看需求,具体问题再具体处理)
4 基于以前须要更改uiwebview中ajax请求,程序中使用了NSURLProtocol(这是个抽象类,不能够被实例化)的子类,结果url的拦截就是在该类的canonicalRequestForRequest的方法进行的。web
当用户点击投标,跳转到投标存管页面输入交易密码等一系列操做完成以后,测试发现不论是投资成功仍是投资失败(交易密码错误次数过多,帐户被冻结),存管发的结果连接都是同样的,也就是说不论是投资成功仍是失败,只要用户结束h5页面的操做,回到客户端本地时都是投资成功的页面,这显然是不对的。ajax
和领导沟通以后,决定以下解决:
步骤一,客户端拦截获取request.HTTPBody,从而截取结果url的tm和data值;
步骤二,请求接口将tm和data发送给后台;
步骤三,后台解密tm和data的到操做状态(成功或者失败),再返回给客户端
步骤四,客户端得到后台结果,成功则到本地投资成功页,失败则到申购页并toast 投资失败框架
意外状况:
1 h5投标页,当交易密码错误次数过多帐号被冻结时,存管那边tm和data返回空
准备当tm和data为空的时候,客户端判断为冻结状况,可是发现出来 2 意外状况
2 发现程序能拦截到2次结果url,第一次有值,第二次没值
处理过程:
步骤一, Charles抓包发现,h5确实只发了一个请求(提醒同事是否是NSURLProtocol的问题?)
步骤二, 同事尝试使用uiwebview的shouldStartLoadWithRequest代理来截取结果url,发现真的只发了一次请求,确实是NSURLProtocol的问题post
在uiwebview的shouldStartLoadWithRequest中拦截结果url,截取request.HTTPBody,从而获得tm和data值,发送通知相应页面处理。充值模块大概代码以下(具体需求具体分析,咱们项目大概逻辑是这样的):测试
//处理充值完成结果(成功和失败返回的url同样问题) if ([request.URL.absoluteString rangeOfString:DepositeCharge].length > 0) { NSString *HTTPBodyString = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]; NSMutableDictionary *payResultInfo = [NSMutableDictionary dictionaryWithCapacity:1]; [payResultInfo setObject:HTTPBodyString forKey:@"HTTPBodyString"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"DepositRechargeResultNotification" object:nil userInfo:payResultInfo]; }
相应页面处理逻辑:ui
//监听充值返回结果通知,放在viewdidload里面 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doResult:) name:@"DepositRechargeResultNotification" object:nil]; - (void)doResult:(NSNotification *)sender{ NSDictionary *dic = sender.userInfo; NSString *httpBodyString = [dic objectForKey:@"HTTPBodyString"]; if (!(httpBodyString.length>0)) { //交易密码错误累计5次帐号被冻结以后,返回的url的httpBody tm和data都为空,此时客户端作的处理 [self.navigationController popToRootViewControllerAnimated:NO]; return; } else{ [[PersonalCenterLogicManager sharedInstance] reqResultInfoWithHttpBody:httpBodyString Success:^(id model) { NSLog( @" 充值结果返回%@",model); NSDictionary *dataDic= [model objectForKey:@"data"]; if ([[dataDic objectForKey:@"status" ]isEqualToString:@"S"]) { //充值成功跳到充值过渡页 [self.navigationController pushViewController:[[ChargeSuccessVC alloc] initWithTitle:@"充值结果"] animated:NO]; } else { //失败回到充值页面 [self.navigationController popViewControllerAnimated:NO]; [MBProgressHUD showError:[dataDic objectForKey:@"msg"]]; } } failure:^(NSError *err) { [MBProgressHUD showError:ServerError]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.navigationController popToRootViewControllerAnimated:NO]; }); }]; } }
上面说到客户端截取结果url,以后获取tm和data值发送给后台,
开始咱们是以下获得其值,以后解析并获取相应的tm和data值,AFN以post方式将两个参数传到后台,发现不行编码
NSString *HTTPBodyString = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
问题:
1 后台接收到值,没法解析(后台接收的值和客户端发送的值是否一致?若一致,后台解析问题或者压根这样的值是没法解析的;若不一致,说明这个请求的方式是否有问题?AFN post请求过程当中 一些字符被自动转义了?。同事开发,具体状况不是很清楚,猜想应该是这样。)
2 程序里如何获取Charles抓包时Contents里面Form格式的数据,以下图:url
而不是Text格式或者Raw格式(这些格式下面会发现有%2F %2B %3D %2B等转义字符),上述代码获得就是这种格式。以下:spa
网上搜了不少,最终以下获取:代理
NSString *HTTPBodyString = [[[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
3 最后同事那边跟后台调试以后,是以afn post方式,参数追加到url后面,真正参数为nil发送请求,ok。(有点乱,待调)
无论什么问题,最终总有解决的办法(不论是去解决这个问题,仍是经过别的方案去绕开这个问题),而咱们须要的是静下心来分析,尽可能从多个方面去分析(例如上面说到的程序截取到2次请求问题,也许咱们会去让后台找缘由,让他们不要发两次请求,固然这也是在解决问题。但同时咱们也要想,是否是客户端这边的问题,是否是拦截的框架本身有问题呢,因此经过抓包,咱们最后发现,后台确实只发了一次请求,是客户端截取url的框架自身问题。那个编码问题也是如此)
附:
如下是截取子串经常使用方法,先记录在这里,备之后须要,哈哈
NSString *strBody = [[NSString alloc]initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]; NSLog(@"request.allHTTPbody = %@",strBody); if ([request.URL.absoluteString rangeOfString:DepositRecharge].length > 0) { NSRange rangeAnd = [strBody rangeOfString:@"&"]; if (rangeAnd.length > 0) { //substringFromIndex:截取从location开始到结束,包括location位置上的字符 NSString *strData = [strBody substringFromIndex:rangeAnd.location+1]; //substringToIndex:截取开始到location,不包括loacation位置上的字符 NSString *strTm = [strBody substringToIndex:rangeAnd.location]; NSLog(@"tm = %@",[strTm substringWithRange:NSMakeRange(3, strTm.length-3)]); NSLog(@"data = %@",[strData substringWithRange:NSMakeRange(5, strData.length-5)]); } }
具体问题:h5页面发送post请求,抓包确实也是post请求 但通过客户端以后就变成了get请求。
客户端对h5的处理:
基于有的活动h5页面,须要判断用户登陆状态 从而显示相应的信息。此时h5连接会含有某个标识字符串(例如:auth 字符串),当客户端截取到该字段,会拦截该请求 添加token信息,以后再从新发送该请求。(基于webview不能拦截全部的请求,故项目使用NSURLProtocol子类来拦截请求)
最终解决:
经过不断的调试和分析,发现是由于以前的处理是 不论是get请求仍是post请求,token信息一概都是追加到url后面。 咱们只要对get和post作分别处理,get请求追加在url后,post请求追加在body里面,如此处理便可。代码以下:
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { NSMutableURLRequest *mutableReqeust = [request mutableCopy]; NSString *strURL = request.URL.absoluteString; //h5 ajax请求中含authentication,截取token信息后重定向 if (([strURL rangeOfString:@"auth"].length > 0) && ([request.HTTPMethod rangeOfString:@"POST"].length > 0)) { NSString *strBody = [[NSMutableString alloc]initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]; [mutableReqeust setHTTPBody: [[StaticTools addParaWithOriginUrl:strBody] dataUsingEncoding: NSUTF8StringEncoding]]; } if (([strURL rangeOfString:@"auth"].length > 0) && ([request.HTTPMethod rangeOfString:@"GET"].length > 0)) { NSString *str = [StaticTools addParaWithOriginUrl:strURL]; mutableReqeust = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:str]]; } return mutableReqeust; }