NSURLProtocol 拦截 NSURLSession 请求时body丢失问题解决方案探讨

阅读:636 2019-03-19 15:06:26 来源:新网

摘要:“ip直连方案”主要在于解决dns污染、省去dns解析时间,通常情况下我们可以在项目中使用nsurlprotocol拦截nsurlsession请求,下面将支持post请求中面临的一个挑战,以及应对策略介绍一下。

“ip直连方案”主要在于解决dns污染、省去dns解析时间,通常情况下我们可以在项目中使用nsurlprotocol拦截nsurlsession请求,下面将支持post请求中面临的一个挑战,以及应对策略介绍一下:

在支持post请求过程中会遇到丢失body的问题,有以下几种解决方法:

方案如下:

对方案做以下分析

////nsurlrequest+cylnsurlprotocolextension.h//////createdbyelonchanon28/07/2017.//copyright©2017chenyilong.allrightsreserved.//#import@interfacensurlrequest(cylnsurlprotocolextension)-(nsurlrequest*)cyl_getpostrequestincludebody;@end////nsurlrequest+cylnsurlprotocolextension.h//////createdbyelonchanon28/07/2017.//copyright©2017chenyilong.allrightsreserved.//#import"nsurlrequest+cylnsurlprotocolextension.h"@implementationnsurlrequest(cylnsurlprotocolextension)-(nsurlrequest*)cyl_getpostrequestincludebody{return[[selfcyl_getmutablepostrequestincludebody]copy];}-(nsmutableurlrequest*)cyl_getmutablepostrequestincludebody{nsmutableurlrequest*req=[selfmutablecopy];if([self.httpmethodisequaltostring:@"post"]){if(!self.httpbody){nsintegermaxlength=1024;uint8_td[maxlength];nsinputstream*stream=self.httpbodystream;nsmutabledata*data=[[nsmutabledataalloc]init];[streamopen];boolendofstreamreached=no;//不能用[streamhasbytesavailable])判断,处理图片文件的时候这里的[streamhasbytesavailable]会始终返回yes,导致在while里面死循环。while(!endofstreamreached){nsintegerbytesread=[streamread:dmaxlength:maxlength];if(bytesread==0){//文件读取到最后endofstreamreached=yes;}elseif(bytesread==-1){//文件读取错误endofstreamreached=yes;}elseif(stream.streamerror==nil){[dataappendbytes:(void*)dlength:bytesread];}}req.httpbody=[datacopy];[streamclose];}}returnreq;}@end

上面是我给出的实现,这里注意,刚开始有人做过这样的实现:

-(void)cyl_handlepostrequestbody{if([self.httpmethodisequaltostring:@"post"]){if(!self.httpbody){uint8_td[1024]={0};nsinputstream*stream=self.httpbodystream;nsmutabledata*data=[[nsmutabledataalloc]init];[streamopen];while([streamhasbytesavailable]){nsintegerlen=[streamread:dmaxlength:1024];if(len>0&&stream.streamerror==nil){[dataappendbytes:(void*)dlength:len];}}self.httpbody=[datacopy];[streamclose];}}}

这个实现的问题在于:不能用[streamhasbytesavailable])判断,处理图片文件的时候这里的[streamhasbytesavailable]会始终返回yes,导致在while里面死循环。

apple的文档也说得很清楚:

//returnsino(1)apointertothebufferin'buffer'andbyreferencein'len'howmanybytesareavailable.thisbufferisonlyvaliduntilthenextstreamoperation.subclassersmayreturnnoforthisifitisnotappropriateforthestreamtype.thismayreturnnoifthebufferisnotavailable.@property(readonly)boolhasbytesavailable;

给出了实现,下面介绍下使用方法:

在用于拦截请求的nsurlprotocol的子类中实现方法+canonicalrequestforrequest:并处理request对象:

+(nsurlrequest*)canonicalrequestforrequest:(nsurlrequest*)request{return[requestcyl_getpostrequestincludebody];}

下面介绍下相关方法的作用:

//nsurlprotocol.h/*!@methodcaninitwithrequest:@abstractthismethoddetermineswhetherthisprotocolcanhandlethegivenrequest.@discussionaconcretesubclassshouldinspectthegivenrequestanddeterminewhetherornottheimplementationcanperformaloadwiththatrequest.thisisanabstractmethod.sublassesmustprovideanimplementation.@paramrequestarequesttoinspect.@resultyesiftheprotocolcanhandlethegivenrequest,noifnot.*/+(bool)caninitwithrequest:(nsurlrequest*)request;/*!@methodcanonicalrequestforrequest:@abstractthismethodreturnsacanonicalversionofthegivenrequest.@discussionitisuptoeachconcreteprotocolimplementationtodefinewhat"canonical"means.however,aprotocolshouldguaranteethatthesameinputrequestalwaysyieldsthesamecanonicalform.specialconsiderationshouldbegivenwhenimplementingthismethodsincethecanonicalformofarequestisusedtolookupobjectsintheurlcache,aprocesswhichperformsequalitychecksbetweennsurlrequestobjects.

thisisanabstractmethod;sublassesmustprovideanimplementation.@paramrequestarequesttomakecanonical.@resultthecanonicalformofthegivenrequest.*/+(nsurlrequest*)canonicalrequestforrequest:(nsurlrequest*)request;

翻译下:

//nsurlprotocol.h/*!*@method:创建nsurlprotocol实例,nsurlprotocol注册之后,所有的nsurlconnection都会通过这个方法检查是否持有该http请求。@parma:@return:yes:持有该http请求no:不持有该http请求*/+(bool)caninitwithrequest:(nsurlrequest*)request/*!*@method:nsurlprotocol抽象类必须要实现。通常情况下这里有一个最低的标准:即输入输出请求满足最基本的协议规范一致。因此这里简单的做法可以直接返回。一般情况下我们是不会去更改这个请求的。如果你想更改,比如给这个request添加一个title,组合成一个新的http请求。@parma:本地httprequest请求:request@return:直接转发*/+(nsurlrequest*)canonicalrequestforrequest:(nsurlrequest*)request

简单说:

这里有一个注意点:+[nsurlprotocolcanonicalrequestforrequest:]的执行条件是+[nsurlprotocolcaninitwithrequest:]返回值为yes。

注意在拦截nsurlsession请求时,需要将用于拦截请求的nsurlprotocol的子类添加到nsurlsessionconfiguration中,用法如下:

nsurlsessionconfiguration*configuration=[nsurlsessionconfigurationdefaultsessionconfiguration];nsarray*protocolarray=@[[cylurlprotocolclass]];configuration.protocolclasses=protocolarray;nsurlsession*session=[nsurlsessionsessionwithconfiguration:configurationdelegate:selfdelegatequeue:[nsoperationqueuemainqueue]];换用其他提供了sni字段配置接口的更底层网络库

如果使用第三方网络库:curl,中有一个-resolve方法可以实现使用指定ip访问https网站,ios中集成curl库,参考curl文档;

另外有一点也可以注意下,它也是支持ipv6环境的,只需要你在build时添加上--enable-ipv6即可。

curl支持指定sni字段,设置sni时我们需要构造的参数形如:{https域名}:443:{ip地址}

假设你要访问.www.example.org,若ip为127.0.0.1,那么通过这个方式来调用来设置sni即可:

curl*--resolve'www.example.org:443:127.0.0.1'

使用libcurl来解决,libcurl/curl至少7.18.1(2008年3月30日)在sni支持下编译一个ssl/tls工具包,curl中有一个--resolve方法可以实现使用指定ip访问https网站。

在ios实现中,代码如下

//{https域名}:443:{ip地址}nsstring*curlhost=...;_hosts_list=curl_slist_append(_hosts_list,curlhost.utf8string);curl_easy_setopt(_curl,curlopt_resolve,_hosts_list);

其中curlhost形如:

{https域名}:443:{ip地址}

_hosts_list是结构体类型hosts_list,可以设置多个ip与host之间的映射关系。curl_easy_setopt方法中传入curlopt_resolve将该映射设置到https请求中。

这样就可以达到设置sni的目的。

我在这里写了一个demo:cylcurlnetworking,里面包含了编译好的支持ipv6的libcurl包,演示了下如何通过curl来进行类似nsurlsession。

注意以上讨论不涉及wkwebview中拦截nsurlsession请求的body丢失问题。

文中提到的几个概念:

文中部分提到的域名,如果没有特殊说明均指的是fqdn。

相关文章
{{ v.title }}
{{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
你可能感兴趣
推荐阅读 更多>
推荐商标

{{ v.name }}

{{ v.cls }}类

立即购买 联系客服