说明

一位著名的iOS大神Mattt Thompson在http://nshipster.com/nsurlprotocol/
博客里说过,说“NSURLProtocol is both the most obscure and the most powerful part of the URL Loading System.”NSURLProtocol是URL Loading System中功能最强大也是最晦涩的部分。

NSURLProtocol作为URL Loading System中的一个独立部分存在,能够拦截所有的URL Loading System发出的网络请求,拦截之后便可根据需要做各种自定义处理,是iOS网络层实现AOP(面向切面编程)的终极利器,所以功能和影响力都是非常强大的。

什么是 NSURLProtocol

NSURLProtocol是URL Loading System的重要组成部分。
首先虽然名叫NSURLProtocol,但它却不是协议。它是一个抽象类。我们要使用它的时候需要创建它的一个子类。
NSURLProtocol在iOS系统中大概处于这样一个位置:

NSURLProtocol能拦截哪些网络请求

NSURLProtocol能拦截所有基于URL Loading System的网络请求。
这里先贴一张URL Loading System的图:

可以拦截的网络请求包括NSURLSession,NSURLConnection以及UIWebVIew。
基于CFNetwork的网络请求,以及WKWebView的请求是无法拦截的。
现在主流的iOS网络库,例如AFNetworking,Alamofire等网络库都是基于NSURLSession或NSURLConnection的,所以这些网络库的网络请求都可以被NSURLProtocol所拦截。
还有一些年代比较久远的网络库,例如ASIHTTPRequest,MKNetwokit等网路库都是基于CFNetwork的,所以这些网络库的网络请求无法被NSURLProtocol拦截。

应用例子

NSURLProtocol是iOS网络加载系统中很强的一部分,它其实是一个抽象类,我们可以通过继承子类化来拦截APP中的网络请求。

举几个例子:

我们的APP内的所有请求都需要增加公共的头,像这种我们就可以直接通过

  1. NSURLProtocol来实现,当然实现的方式有很多种
  2. 再比如我们需要将APP某个API进行一些访问的统计
  3. 再比如我们需要统计APP内的网络请求失败率
  4. 网络请求缓存
  5. 网络请求mock stub,知名的库OHHTTPStubs
    就是基于NSURLProtocol
  6. 网络相关的数据统计
  7. URL重定向
  8. 配合实现HTTPDNS

等等,都可以用到

NSURLProtocol是一个抽象类,我们需要子类化才能实现网络请求拦截。

使用 NSURLProtocol

如上文所说,NSURLProtocol是一个抽象类。我们要使用它的时候需要创建它的一个子类。

@interface CustomURLProtocol : NSURLProtocol

使用NSURLProtocol的主要可以分为5个步骤:
注册—>拦截—>转发—>回调—>结束

注册:

对于基于NSURLConnection或者使用[NSURLSession sharedSession]创建的网络请求,调用registerClass方法即可。

[NSURLProtocol registerClass:[NSClassFromString(@"CustomURLProtocol") class]];

对于基于NSURLSession的网络请求,需要通过配置NSURLSessionConfiguration对象的protocolClasses属性。

NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.protocolClasses = @[[NSClassFromString(@"CustomURLProtocol") class]];

拦截

子类化NSURLProtocol
在NSURLProtocol中,我们需要告诉它哪些网络请求是需要我们拦截的,这个是通过方法canInitWithRequest:来实现的,比如我们现在需要拦截全部的HTTP和HTTPS请求,那么这个逻辑我们就可以在canInitWithRequest:中来定义

/**需要控制的请求@param request 此次请求@return 是否需要监控*/+ (BOOL)canInitWithRequest:(NSURLRequest *)request {if (![request.URL.scheme isEqualToString:@"http"] &&![request.URL.scheme isEqualToString:@"https"]) {return NO;}return YES;}

在方法canonicalRequestForRequest:中,我们可以自定义当前的请求request,当然如果不需要自定义,直接返回就行

/**设置我们自己的自定义请求可以在这里统一加上头之类的@param request 应用的此次请求@return 我们自定义的请求*/+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {return request;}

避免死循环

对于每个NSURLProtocol的子类,都有一个client,通过它来对iOS的网络加载系统进行一系列的操作,比如,通知收到response或者错误的网络请求等等

这样,我们通过这两个方法,就已经能够拦截住iOS的网络请求了,但是这里有个问题

在我们上层业务调用网络请求的时候,首先会调用我们的canInitWithRequest:方法,询问是否对该请求进行处理,接着会调用我们的canonicalRequestForRequest:来自定义一个request,接着又会去调用canInitWithRequest:询问自定义的request是否需要处理,我们又返回YES,然后又去调用了canonicalRequestForRequest:,这样,就形成了一个死循环了,这肯定是我们不希望看到的。

有个处理方法,我们可以对每个处理过的request进行标记,在判断如果这个request已经处理过了,那么我们就不再进行处理,这样就有效避免了死循环

在我们自定义request的方法中,我们来设置处理标志

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {NSMutableURLRequest *mutableReqeust = [request mutableCopy];[NSURLProtocol setProperty:@YESforKey:"zgpeaceInterceptor"inRequest:mutableReqeust];return [mutableReqeust copy];}

然后在我们的询问处理方法中,通过判断是否有处理过的标志,来进行拦截

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {if (![request.URL.scheme isEqualToString:@"http"] &&![request.URL.scheme isEqualToString:@"https"]) {return NO;}//如果是已经拦截过的  就放行if ([NSURLProtocol propertyForKey: "zgpeaceInterceptor" inRequest:request] ) {return NO;}return YES;}

这样,我们就避免了死循环

发送

接下来,就是需要将这个request发送出去了,因为如果我们不处理这个request请求,系统会自动发出这个网络请求,但是当我们处理了这个请求,就需要我们手动来进行发送了。

我们要手动发送这个网络请求,需要重写startLoading方法

- (void)startLoading {NSURLRequest *request = [[self class] canonicalRequestForRequest:self.request];self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];self.pps_request = self.request;}

解释一下上面的代码,因为我们拦截的这个请求是一个真实的请求,所以我们需要创建这样一个真实的网络请求,在第二行代码中,将我们自定义创建的request发了出了,第三行是为了保存当前的request,作为我们后面的处理对象。

当然,有start就有stop,stop就很简单了

- (void)stopLoading {[self.connection cancel];}

回调:

既是面向切面的编程,就不能影响到原来网络请求的逻辑。所以上一步将网络请求转发出去以后,当收到网络请求的返回,还需要再将返回值返回给原来发送网络请求的地方。
主要需要需要调用到

[self.client URLProtocol:self didFailWithError:error];
[self.client URLProtocolDidFinishLoading:self];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:data];

这四个方法来回调给原来发送网络请求的地方。
这里假设我们在转发过程中是使用NSURLSession发送的网络请求,那么在NSURLSession的回调方法中,我们做相应的处理即可。并且我们也可以对这些返回,进行定制化处理。

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {if (error) {[self.client URLProtocol:self didFailWithError:error];} else {[self.client URLProtocolDidFinishLoading:self];}
}- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];completionHandler(NSURLSessionResponseAllow);
}- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {[self.client URLProtocol:self didLoadData:data];
}

结束:

在一个网络请求完全结束以后,NSURLProtocol回调用到

- (void)stopLoading
在该方法里,我们完成在结束网络请求的操作。以NSURLSession为例:- (void)stopLoading {[self.session invalidateAndCancel];self.session = nil;
}

以上便是NSURLProtocol的基本流程。

接口方法

在startLoading中,我们发起了一个NSURLConnection的请求,因为NSURLProtocol使我们自己定义的,所以我们需要将网络请求的一系列操作全部传递出去,不然上层就不知道当前网络的一个请求状态,那我们怎么将这个网络状态传到上层?在之前,我们说过每个protocol有一个NSURLProtocolClient实例,我们就通过这个client来传递。

传递一个网络请求,无外乎就是传递请求的一些过程,数据,结果等等。 发起了发起了一个NSURLConnection的请求,实现它的delegate就能够知道网络请求的一系列操作

#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{[self.client URLProtocol:self didFailWithError:error];}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection{return YES;}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{[self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];}
- (void)connection:(NSURLConnection *)connection
didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {[self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];}
#pragma mark - NSURLConnectionDataDelegate
-(NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response{if (response != nil) {self.pps_response = response;[self.client URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];}return request;}
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];self.pps_response = response;}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {[self.client URLProtocol:self didLoadData:data];[self.pps_data appendData:data];}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connectionwillCacheResponse:(NSCachedURLResponse *)cachedResponse {return cachedResponse;}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {[[self client] URLProtocolDidFinishLoading:self];}

其实从上面的代码,我们可以看出,我们就是在我们自己自定义的protocol中进行了一个传递过程,其他的也没有做操作

这样,基本的protocol就已经实现完成,那么怎样来拦截网络。我们需要将我们自定义的PPSURLProtocol通过NSURLProtocol注册到我们的网络加载系统中,告诉系统我们的网络请求处理类不再是默认的NSURLProtocol,而是我们自定义的InterceptorProtocol

我们在InterceptorProtocol暴露两个方法

#import <Foundation/Foundation.h>
@interface InterceptorProtocol : NSURLProtocol
+ (void)start;
+ (void)end;
@end

然后在我们的APP启动的时候,调用start,就可以监听到我们的网络请求。

+ (void)start {PPSURLSessionConfiguration *sessionConfiguration = [PPSURLSessionConfiguration defaultConfiguration];[NSURLProtocol registerClass:[PPSURLProtocol class]];}
+ (void)end {PPSURLSessionConfiguration *sessionConfiguration = [PPSURLSessionConfiguration defaultConfiguration];[NSURLProtocol unregisterClass:[PPSURLProtocol class]];}

目前为止,我们上面的代码已经能够监控到绝大部分的网络请求,但是呢,有一个却是特殊的。

对于NSURLSession发起的网络请求,我们发现通过shared得到的session发起的网络请求都能够监听到,但是通过方法sessionWithConfiguration:delegate:delegateQueue:得到的session,我们是不能监听到的,原因就出在NSURLSessionConfiguration上,我们进到NSURLSessionConfiguration里面看一下,他有一个属性

@property(nullable, copy)NSArray<Class>*protocolClasses;

我们能够看出,这是一个NSURLProtocol数组,上面我们提到了,我们监控网络是通过注册NSURLProtocol来进行网络监控的,但是通过sessionWithConfiguration:delegate:delegateQueue:得到的session,他的configuration中已经有一个NSURLProtocol,所以他不会走我们的protocol来,怎么解决这个问题呢? 其实很简单,我们将NSURLSessionConfiguration的属性protocolClasses的get方法hook掉,通过返回我们自己的protocol,这样,我们就能够监控到通过sessionWithConfiguration:delegate:delegateQueue:得到的session的网络请求

-(void)load {self.isSwizzle=YES;Class cls =NSClassFromString(@"__NSCFURLSessionConfiguration")?:NSClassFromString(@"NSURLSessionConfiguration");[self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[selfclass]];
}
-(void)unload {self.isSwizzle=NO;Class cls =NSClassFromString(@"__NSCFURLSessionConfiguration")?:NSClassFromString(@"NSURLSessionConfiguration");[self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[selfclass]];
}
-(void)swizzleSelector:(SEL)selector fromClass:(Class)original toClass:(Class)stub {Method originalMethod =class_getInstanceMethod(original,selector);Method stubMethod =class_getInstanceMethod(stub,selector);if (!originalMethod ||!stubMethod){[NSExceptionraise:NSInternalInconsistencyExceptionformat:@"Couldn't load NEURLSessionConfiguration."];}method_exchangeImplementations(originalMethod,stubMethod);
}-(NSArray*)protocolClasses{return @[[PPSURLProtocol class]];//如果还有其他的监控protocol,也可以在这里加进去
}

在启动的时候,将这个方法替换掉,在移除监听的时候,恢复之前的方法

至此,我们的监听就完成了,如果我们需要将这所有的监听存起来,在protocol的start或者stop中获取到request和response,将他们存储起来就行,需要说明的是,据苹果的官方说明,因为请求参数可能会很大,为了保证性能,请求参数是没有被拦截掉的,就是post的HTTPBody是没有的,我没有获取出来,如果有其他的办法,还请告知

demo

新建playground,拦截请求的内容。

import UIKit
import Foundationfinal class PrintProtocol: URLProtocol {override class func canInit(with request: URLRequest) -> Bool {print("? Running request: \(request.httpMethod ?? "") - \(request.url?.absoluteString ?? "")")return false}
}URLProtocol.registerClass(PrintProtocol.self)URLSession.shared.dataTask(with: URL(string: "https://www.avanderlee.com/feed/")!) {data, urlResponse, error inprint("response > \(String(describing: urlResponse))")if let localData = data {let str = String(decoding: localData, as: UTF8.self)print("data > \(String(describing: str))")}print("error > \(String(describing: error))")print("✅ Request completed")
}.resume()

打印结果如下:

? Running request: GET - https://www.avanderlee.com/feed/
response > Optional(<NSHTTPURLResponse: 0x60000105c220> { URL: https://www.avanderlee.com/feed/ } { Status Code: 200, Headers {Age =     (48263);"Cache-Control" =     ("max-age=3600");"Content-Type" =     ("application/rss+xml; charset=UTF-8");Date =     ("Mon, 18 May 2020 06:36:44 GMT");Expires =     ("Sun, 17 May 2020 18:12:19 GMT");"Last-Modified" =     ("Sun, 17 May 2020 16:58:58 GMT");Link =     ("<https://www.avanderlee.com/wp-json/>; rel=\"https://api.w.org/\"");Server =     (cloudflare);Vary =     ("Accept-Encoding,User-Agent");Via =     ("1.1 varnish (Varnish/6.4)");"cf-cache-status" =     (HIT);"cf-ray" =     ("595390c719930530-LAX");"cf-request-id" =     (02c818d06d00000530ac304200000001);"expect-ct" =     ("max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"");"x-powered-by" =     ("PHP/7.4.6");"x-varnish" =     (306420337);
} })
data > <?xml version="1.0" encoding="UTF-8"?><rss version="2.0"xmlns:content="http://purl.org/rss/1.0/modules/content/"xmlns:wfw="http://wellformedweb.org/CommentAPI/"xmlns:dc="http://purl.org/dc/elements/1.1/"xmlns:atom="http://www.w3.org/2005/Atom"xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"xmlns:slash="http://purl.org/rss/1.0/modules/slash/">...
error > nil
✅ Request completed

接口中文说明

// `canInitWithRequest:`是整个逻辑的入口,
// 当它返回`YES`时,URL Loading System会创建一个对应的实例,意味着该请求就会被该Protocol实例控制
// 当它返回`NO`时,则直接跳入下一个Protocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;// 如果想要用特定的某个方式来修改一个请求,应该使用该方法。
// 一般来说,每一个subclass都应该依据某一个规范,一个protocol应该保证只有唯一的规范范式
//
// 补充:具体来说,`canonicalRequestForRequest`返回值会作为Protocol实例
// 构造器`initWithRequest:cachedResponse:client:`的参数
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;// 这几个方法允许添加、获取、删除一个request对象的任意metedata,而不需要私有扩展或者method swizzling。
// Objective-C中,通过extension可以为NSURLRequest新增方法,但是不能直接新增属性;如何解决这个问题?
// 一种很自然能想到的方案是:关联对象。
// 而这几个方法提供了另外一种方案。相较于关联对象,这种方式更干净,且更简单,且与Runtime无关。
+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;
+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;
+ (void)removePropertyForKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;// Designated Initializer
// URL Loading System使用该构造器来构建Protocol实例,关于这几个参数:
//   * 每一个Protocol实例对应一个request
//   * 每一个Protocol实例对应一个client,该client用于和URL Loading System进行通信
- (instancetype)initWithRequest:(NSURLRequest *)requestcachedResponse:(NSCachedURLResponse *)cachedResponseclient:(id <NSURLProtocolClient>)client;// 这两个方法顾名思义是与loading request有关,前者开启一个request请求,后者则结束/取消。
// 在自定义URLProtocol时,这两个方法是必须要实现的。简单来说,在实现时:
// 1. `startLoading`的实现逻辑里,要启动一个request,对于`NSURLConnection`而言,要创建一个`NSURLConnection`对象,譬如:
//      self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
// 2. `stopLoading`的实现逻辑里,一般都是cancel掉request,譬如:
//      [self.connection cancel];
//
// `startLoading`中构建connection的request从哪里来?在构建protocol实例时,URL Loading System会传入一个request...
- (void)startLoading;
- (void)stopLoading;// 创建一个NSURLProtocol子类后,需要使用`registerClass:`注册到URL Loading System,
// 当URL Loading System开始load request时,会询问已注册的Protocol是否要处理对应的request,
// 如果处理(`canInitWithRequest:`的返回值),则会初始化该Protocol对应的实例,以便后续处理。
// 需要注意的是:并不是所有已注册的Protocol都会被询问,URL Loading System根据Protocols的注册
// 顺序逆序询问,当某个Protocol的`canInitWithRequest:`返回`YES`时,后续的Protocol便不再被询问。
//
// P.S: 关于逆序询问,Doc的描述:Classes are consulted in the reverse order of their registration。
// P.S: 如何理解「当URL Loading System开始load request时」呢?对于`NSURLConnection`框架而言,
// 这指的是一个`NSURLConnection`实例被创建了。
+ (BOOL)registerClassass:(Class)protocolClass;
+ (void)unregisterClass:(Class)protocolClass;/**************** NSURLProtocol实例的常用属性 ****************/
// 上文已有描述,此处不再赘述 */
@property (readonly, copy) NSURLRequest *request;
// client用来干嘛?其作用主要是与URL Loading System进行通信,它的方法和`NSURLConnectionDelegate`非常类似
// 用户需要做的事情是在合适的时候调用其中的方法...
@property (nullable, readonly, retain) id <NSURLProtocolClient> client;

demo代码:

https://github.com/RicardoFerreira10/InterceptorSampleProject

https://github.com/yangqian111/PPSNetworkMonitor

参考

https://nshipster.cn/nsurlprotocol/

https://www.jianshu.com/p/02781c0bbca9

https://juejin.im/entry/58ed8c6344d904005772e8c7

https://www.raywenderlich.com/2509-nsurlprotocol-tutorial

https://www.raywenderlich.com/2292-using-nsurlprotocol-with-swift

https://blog.codavel.com/how-to-intercept-http-requests-on-an-ios-app

https://www.avanderlee.com/swift/printing-data-requests/

https://zhangbuhuai.com/post/nsurlprotocol.html

实战NSURLProtocol 拦截 APP网络请求NSURLConnection, NSURLSession, Alamofire相关推荐

  1. Method-Swizzling实战-实现iOS原生网络请求性能采集

    一.方法交换实现步骤(实例方法和类方法处理逻辑类似) 1.检查原类(要被替换方法的类)的原实例方法是否存在,如果不存在,则不交换: 2.检查新类(最终被使用的方法所在的类)的新实例方法是否存在,如果不 ...

  2. 鸿蒙开发实战系列之三:网络请求(原生+ Retrofit)

    鸿蒙开发实战系列之一:鸿蒙开发实战系列之一:圆角 鸿蒙开发实战系列之二:鸿蒙开发实战系列之二:事件总线EventBus/RxBus 前言 过了一个漫长的中秋+国庆假期,大家伙的鸿蒙内功修炼的怎么样了? ...

  3. 安卓项目实战之强大的网络请求框架okGo使用详解(一):实现get,post基本网络请求,下载上传进度监听以及对Callback自定义的深入理解

    1.添加依赖 //必须使用 compile 'com.lzy.net:okgo:3.0.4'//以下三个选择添加,okrx和okrx2不能同时使用,一般选择添加最新的rx2支持即可 compile ' ...

  4. 【Mitmproxy】Mac + Python + mitmproxy透明代理配置,拦截所有网络请求

    Mac OS Monterey 版本 12.1 mitmproxy==8.0.0 Mitmproxy --version Mitmproxy: 8.0.0.dev Python: 3.9.13 Ope ...

  5. Fiddler抓取APP网络请求

    安装Fiddler 一路next就行了. 配置Fiddler 1.允许解析https 2.允许抓取远程流量 3.配置只抓取app流量 4.关闭pc端防火墙或开通相关规则 Android端配置 1.配置 ...

  6. HttpInterceptor 拦截器 - 网络请求超时与重试的简单实现

    ... 拦截器在Angular项目中其实有着十分重要的地位,拦截器可以统一对 HTTP 请求进行拦截处理,我们可以在每个请求体或者响应后对应的流添加一系列动作或者处理数据,再返回给使用者调用. 每个 ...

  7. Charles 抓包夜神模拟器,实现对App网络请求的监控

    移动端的开发相对于Web开发,有一个很大的缺陷就是看不到网络请求.Web端开发,可以在控制台轻松看到所有的网络请求,测试人员能轻松看出来接口是否有问题.但是移动端就比较麻烦,需要代理网络请求进行抓包, ...

  8. iOS —— 奇葩问题一 iOS15 首次启动app网络请求失败

    背景: 在iOS15上 首次启动app,如图显示本地网络弹框提示,并且此时所有的网络请求都是失败的. 原因 通过不断测试发现是 手机开通代理导致的, 关闭代理后就不会有如图弹框. iOS15之前的系统 ...

  9. app显示服务器连接超时,APP网络请求超时反馈设计与思考

    最近我负责了一个网络请求超时的反馈设计,借此机会我也顺便通过此文记录了一下整个思考过程,整理一下自己的思路. 当我们在使用APP的时候,偶尔会碰到网络状态不好的情况.那么对于网络状态不好的情况有哪些分 ...

  10. 爬虫实战学习笔记_6 网络请求request模块:基本请求方式+设置请求头+获取cookies+模拟登陆+会话请求+验证请求+上传文件+超时异常

    1 requests requests是Python中实现HTTP请求的一种方式,requests是第三方模块,该模块在实现HTTP请求时要比urlib.urllib3模块简化很多,操作更加人性化. ...

最新文章

  1. 基于Nginx的负载均衡
  2. 单指手势,旋转,缩放
  3. 需要排序的最短子数组长度
  4. Exception in thread “main“ java.lang.IllegalArgumentException: http://www.dmg.org/PMML-4_4(没搞定)
  5. oracle 游标循环 while,Oracle的游标使用方法 三种循环
  6. halcon深度学习算子,持续更新
  7. 开机预读快还是不预读快_WIN 7下的超级预读比VISTA要好,改进不少!推荐开启超级预读!...
  8. 白银TD盈亏计算实例介绍
  9. WPF之DataTemplate(转)
  10. Git 简介和命令行操作
  11. Python pip windows安装
  12. input type类型_005 类型转换,我的存款是负值
  13. 移动端---媒体查询
  14. SSD目标检测网络tensorRT推理【附代码】
  15. 50句哲理 语录(二)
  16. python 尖括号表示什么类型,Python的小括弧,中括号,大括号还有尖括号的区别...
  17. SpringBoot 整合Smart-doc生成接口文档
  18. 【微信小程序】生命周期
  19. CPT101计算机系统概念
  20. 阿里云容器kubernetes发布nacos2.0.3步骤

热门文章

  1. abb外部轴零位校准_【ABB】ABB机器人外部轴参数(KpKvTi)调试
  2. WPF设置控件获得焦点FocusManager
  3. 详细介绍ASP.NET状态保存方法
  4. goflyway安装
  5. 以太坊智能合约gas如何估计?
  6. 《iOS取证实战:调查、分析与移动安全》一3.6 iPhone操作系统
  7. Swift开发教程--怎样设置状态栏的文字颜色
  8. CHIL-SQL-LEFT JOIN 关键字
  9. Debian Gnu/Linux8.5安装GOLANG环境笔记
  10. 如何去掉windows2003的自动锁定(每离开一会都会出现这个界面,不想让它出现)...