Package: pd-httprequest-util
pd_httprequest_util协助发http请求的工具特点支持设置并发数自动重试三次支持三种发送模式一次发起全部请求（应对请求数量不多的情况）一次发起全部请求并当第一个出现失败时退出（应对请求数量不多&必须要全部成功的情况）iterator返回模式（应对请求数量太多、需要顾虑内存大小的情况）若connection出问题会自动切换同时支持同步/异步(async)调用方式支持打开日志记录每个请求的情况用于后续分析支持设定失败请求的默认返回值概念说明下面的使用例子会使用到Connection、Request、RequestManager三个类，先提前说明下其命名的逻辑：每个具体http请求被命名为一个Request，其使用的httpConnectionpool是作为参数被依赖注入的，而外层使用RequestManager来管理一堆http request的访问特征异步访问举例阻塞访问一批请求# 对应demos/async_demos/all_fetch.pyfrompd_httprequest_util.requestimportRequestfrompd_httprequest_util.connection.async_importAsyncConnectionfrompd_httprequest_util.request_managerimportAsyncRequestManagerfromdemos.configimportvalid_url,invalid_urlasyncdefmain():# 此批访问的并发数为60, 失败request的返回空字符串manager=AsyncRequestManager(parral_amount=60,fail_return='')# 此批访问全部复用此connectionconnection=awaitAsyncConnection.create()# 构建100个请求、其中第五个故意设置为无效的urlforiinrange(100):url=valid_urlifi==5:url=invalid_urlrequest=Request(http_conn=connection,# 依赖注入connection，多有request都复用此connectionmethod='GET',url=url)manager.add_request(request)# 访问全部，resp: ['each resp text'], errorresp,error=awaitmanager.visit_all()suc,fail=0,0foriinresp:iflen(i)>0:suc+=1else:fail+=1print(suc,fail)assertfail==1connection.close()if__name__=='__main__':importasyncioasyncio.run(main())阻塞访问一批请求，并在出现第一个失败request时便返回# 对应demos/async_demos/all_fetch_but_stopwhenfirstfail.pyfrompd_httprequest_util.requestimportRequestfrompd_httprequest_util.connection.async_importAsyncConnectionfrompd_httprequest_util.request_managerimportAsyncRequestManagerfromdemos.configimportvalid_url,invalid_urlasyncdefmain():# 这里通过设置cancel_if_fail=True参数使得第一个失败便返回manager=AsyncRequestManager(parral_amount=2,cancel_if_fail=True)connection=awaitAsyncConnection.create()foriinrange(10):url=valid_urlifi==5:url=invalid_urlrequest=Request(http_conn=connection,method='GET',url=url)manager.add_request(request)resp,error=awaitmanager.visit_all()print(resp,type(resp),error)connection.close()if__name__=='__main__':importasyncioasyncio.run(main())async iterator访问一批请求# 对应demos/async_demos/iter_fetch.pyfrompd_httprequest_util.requestimportRequestfrompd_httprequest_util.connection.async_importAsyncConnectionfrompd_httprequest_util.request_managerimportAsyncRequestManagerfromdemos.configimportvalid_url,invalid_urlasyncdefmain():manager=AsyncRequestManager(parral_amount=4)connection=awaitAsyncConnection.create()foriinrange(10):url=valid_urlifi==5:url=invalid_url# a fail requestrequest=Request(http_conn=connection,method='GET',url=url)manager.add_request(request)# 这里，区别于阻塞的调用的是visit_all方法，此地调用visit_iter方法cursor_resp=manager.visit_iter()asyncforrespincursor_resp:print('suc'iflen(resp)>0else'fail')connection.close()if__name__=='__main__':importasyncioasyncio.run(main())以"阻塞访问一批请求"为例，演示打开日志记录。余下同理# 对应demos/async_demos/set_log.pyfrompd_httprequest_util.requestimportRequestfrompd_httprequest_util.request_managerimportAsyncRequestManagerfrompd_httprequest_util.connection.async_importAsyncConnectionasyncdefmain():manager=AsyncRequestManager(parral_amount=2)# 显式调用set_log方法，设置日志相关参数即打开了日志记录manager.set_log(name='http_connuest_log',dir_path='./',clear=True,clear_days=60)connection=awaitAsyncConnection.create()foriinrange(10):url='http://httpbin.org/ip?whatever=1'ifi==5:url='http://web_not_exist.org/'# a fail requestrequest=Request(http_conn=connection,method='GET',url=url,log_flag=f'req{i}',)manager.add_request(request)resp,error=awaitmanager.visit_all()foriinresp:print('suc'iflen(i)>0else'fail')connection.close()if__name__=='__main__':importasyncioasyncio.run(main())同步访问举例同步访问的核心就是把上面使用的AsyncRequestManager替换为SyncRequestManager,AsyncConnection替换为SyncHttp阻塞访问一批请求# 对应demos/sync_demos/all_fetch.pyfrompd_httprequest_util.requestimportRequestfrompd_httprequest_util.connection.sync_importSyncConnectionfrompd_httprequest_util.request_managerimportSyncRequestManagerfromdemos.configimportvalid_url,invalid_urldefmain():manager=SyncRequestManager(parral_amount=3,fail_return='')connection=SyncConnection.create()foriinrange(10):url=valid_urlifi==5:url=invalid_urlrequest=Request(http_conn=connection,method='GET',url=url)manager.add_request(request)resp,error=manager.visit_all()foriinresp:print('-->','suc'iflen(i)>0else'fail')connection.close()if__name__=='__main__':main()阻塞访问一批请求，并在出现第一个失败request时便返回# 对应demos/sync_demos/all_fetch_but_stopwhenfirstfail.py...iterator访问一批请求# 对应demos/sync_demos/iter_fetch.py...iterator访问一批请求# 对应demos/sync_demos/iter_fetch.py...设置日志# 对应demos/sync_demos/set_log.py...补充设计说明Connection：http链接，独立Connection是为了避免http链接数不可控，一般建议能够在app初始化时候考虑好整体链接的总数以及单个host的链接总数上限（Connection的设置参数支持设置这两个参数）Request：对应业务层看到的具体的每个http请求，其向上承接具体业务、向下依赖Connection进行发送，故对应的http请求需要的参数在此设定，如http method、timeout...;特别需要说明的一个参数是log_flag，此就是用于标示具体业务、如A、B、C，在打开日志记录后此标示会在每条请求日志里，从而可以分辨各个具体业务RequestManager: 用于管理一批同质的Request请求的发送编排，其参数有：并发数、是否在第一个Request出现失败时就放弃并返回、失败的request的默认返回值举例说明背景：一个承接上游各方发送短信请求的应用，其下游有x家能够通过http api调用发起实际短信商户。而上游发送的模式有两种：一种是单条直接发送（一般为验证码等重要信息需要立即发送）；一种是允许滞后发送的信息（如广告类信息、特点是量大且集中）存在的问题发送模式1：如果是每来一个发送请求就选择一个下游商并起一个http connection进行发送就肯能出现链接数爆炸的情况；发送模式2：同理存在链接爆炸的情况； 下游x家各家的性能不一样，对于每家可以使用不同的并发量进行发送解决问题链接爆炸：在全局起server的时候，仅初始化一个connection（并设置每个host最多y个链接）并将此connection绑定到全局的app实例上，从而在各个发送场景下都能够通过app获取到此唯一的connection，保证connnection上限值调整并发量：先维护一个各个商户默认的并发量的对应表。假设此时来的两批待发送广告短信，一批使用商户A，一批使用商户B（不同商户提供的调用其发短信的http规则不一样）分别构造Request实例（依赖注入全局的connection、填入对应的log_flag区分、其他按商户要求进行）A_Requests和B_Requests查出A、B商户的默认并发量，将其作为参数及上面的Requests分别构造再使用A_RequestManager和B_RequestManager从而实现使用不同的并发量进行发送这里还存在一个与此项目无关的点 - 自动调节各个商户并发数: 可能某商户需要发送的短信太多挤压太严重而默认值比较保守需要上调，或者某商户出现某些问题不能再抗住默认并发需要下调；具体解决方案可参考