关于request.HTTPBody一点思考

大情景

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

2026235-cae17bd4d5784b8d.png

而不是Text格式或者Raw格式(这些格式下面会发现有%2F %2B %3D %2B等转义字符),上述代码获得就是这种格式。以下:spa

2026235-2c850f8074eb90dc.png

网上搜了不少,最终以下获取:代理

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)]);
        }
    }

后续问题记录

NSURLProtocol h5 post请求变成get请求问题

具体问题: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;
}