参考文章:http://www.cocoachina.com/articles/19683
啥也不说,先上代码
/// 处理POST请求相关POST 用HTTPBodyStream来处理HTTPBody
/// @param request 处理后的request
- (NSMutableURLRequest *)handlePostRequestBodyWithRequest:(NSURLRequest *)request {
NSMutableURLRequest * mutableRrequest = [request mutableCopy];
if ([request.HTTPMethod isEqualToString:@"POST"]) {
if (!request.HTTPBody) {
uint8_t d[1024] = {0};
NSInputStream *stream = request.HTTPBodyStream;
NSMutableData *data = [[NSMutableData alloc] init];
[stream open];
while ([stream hasBytesAvailable]) {
NSInteger len = [stream read:d maxLength:1024];
if (len > 0 && stream.streamError == nil) {
[data appendBytes:(void *)d length:len];
}
}
mutableRrequest.HTTPBody = [data copy];
[stream close];
}
}
return mutableRrequest;
}
/// 重定向url,重新生成一个request
/// @param request 重定向后的request
+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request
{
if ([request.URL host].length == 0) {
return request;
}
NSString *originUrlString = [request.URL absoluteString];
NSString *originHostString = [request.URL host];
NSRange hostRange = [originUrlString rangeOfString:originHostString];
if (hostRange.location == NSNotFound) {
return request;
}
// 替换host
if ([originHostString containsString:@"baoyinxiaofei"]) {
NSString *ip = @"app.baoyinxiaofei.com";
NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip];
NSURL *url = [NSURL URLWithString:urlString];
request.URL = url;
}
NSMutableURLRequest *mutableRequest = [[self new] handlePostRequestBodyWithRequest:request];
return mutableRequest;
}
//所有注册此Protocol的请求都会经过这个方法的判断,询问是否对该请求进行处理
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
if (![request.URL.scheme isEqualToString:@"http"] &&
![request.URL.scheme isEqualToString:@"https"]) {
return NO;
}
//看看是否已经处理过了,防止无限循环 根据业务来截取
if ([NSURLProtocol propertyForKey: URLProtocolHandledKey inRequest:request]) {
return NO;
}
return YES;
}
//可选方法,对需要拦截的请求进行自定的处理
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
[NSURLProtocol setProperty:@YES
forKey:URLProtocolHandledKey
inRequest:mutableReqeust];
mutableReqeust = [self redirectHostInRequset:mutableReqeust];
return [mutableReqeust copy];
}
/**
开始请求在这里需要我们手动的把请求发出去,可以使用原生的NSURLSessionDataTask,也可以使用的第三方网络库
同时设置"NSURLSessionDataDelegate"协议,接收Server端的响应
*/
- (void)startLoading {
NSMutableURLRequest *mutableRequest = [self handlePostRequestBodyWithRequest:self.request];
NSDictionary *dic = nil;
NSError *error = nil;
if (mutableRequest.HTTPBody) {
dic = [NSJSONSerialization JSONObjectWithData:mutableRequest.HTTPBody options:NSJSONReadingFragmentsAllowed error:&error];
}
self.startDate = [NSDate date];
self.data = [NSMutableData data];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
//此处不能用主线程,不然会导致卡顿,并且如果多个请求会导致请求延迟
self.sessionDelegateQueue = [[NSOperationQueue alloc] init];
self.sessionDelegateQueue.maxConcurrentOperationCount = 1;
self.sessionDelegateQueue.name = @"com.hujiang.wedjat.session.queue";
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:self.sessionDelegateQueue];
//此处request的HTTPBody为空,但是HTTPBodyStream有值,可以猜测当HTTPBody为空时候,会去找HTTPBodyStream里面的值,并且HTTPBodyStream的流本身也是HTTPBody转换过来的
self.dataTask = [session dataTaskWithRequest:self.request];
[self.dataTask resume];
self.startDateString = [[NSDate date] timeIntervalSince1970];
NSLog(@"%s --- %@ --- %f -- %@-- %@ -- %@ -- %f",__func__,mutableRequest.URL.absoluteString,mutableRequest.timeoutInterval,mutableRequest.HTTPBody,dic,error,self.startDateString);
}
- (void)stopLoading {
[self.dataTask cancel];
self.dataTask = nil;
NSString *endDateString = [BYDateTool stringToDate:[NSDate date] andFormatStr:@"yyyy-MM-dd HH:mm:ss"];
NSString *mimeType = self.response.MIMEType;
}
#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (!error) {
[self.client URLProtocolDidFinishLoading:self];
} else if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
} else {
[self.client URLProtocol:self didFailWithError:error];
}
self.dataTask = nil;
}
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
completionHandler(NSURLSessionResponseAllow);
self.response = response;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
if (response != nil){
self.response = response;
[[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
}
}
/*
如果实现这个代理方法,就可以通过该回调的 NSURLSessionTaskMetrics 类型参数获取到采集的网络指标,实现对网络请求中 DNS 查询/TCP 建立连接/TLS 握手/请求响应等各环节时间的统计。
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)){
NSTimeInterval startTime = [metrics.taskInterval.startDate timeIntervalSince1970];
NSTimeInterval endTime = [metrics.taskInterval.endDate timeIntervalSince1970];
NSTimeInterval duration = metrics.taskInterval.duration;;
NSLog(@"%s : %f --- %f ---- %f %f",__func__,startTime,endTime,duration,startTime - endTime);
NSArray *arr = metrics.transactionMetrics;
for (int i = 0; i < arr.count; i++) {
NSURLSessionTaskTransactionMetrics *metrics = arr[i];
NSLog(@"\n %@---\n网络协议 %@ \n---是否使用代理进行网络连接: %d \n---网络加载类型: %ld --- %f ",
metrics.request.URL.absoluteURL,
metrics.networkProtocolName,
metrics.isProxyConnection,
(long)metrics.resourceFetchType,
[metrics.fetchStartDate timeIntervalSince1970]);
}
}
这幅图示意了一次 HTTP 请求在各环节分别做了哪些工作
NSURLSessionTaskMetrics对象立马封装了 session task 的指标,每个 NSURLSessionTaskMetrics 对象有 taskInterval 和 redirectCount 属性,还有在执行任务时产生的每个请求/响应事务中收集的指标。
然后我们看下立马的属性:
transactionMetrics:数组里面对象是NSURLSessionTaskTransactionMetrics,包含了在执行任务时产生的每个请求/响应事务中收集的指标。
taskInterval:任务从创建到完成花费的总时间,任务的创建时间是任务被实例化时的时间;任务完成时间是任务的内部状态将要变为完成的时间
redirectCount:记录了被重定向的次数
###############################
NSURLSessionTaskTransactionMetrics对象封装了任务执行时收集的性能指标,包括了 request 和 response 属性,对应 HTTP 的请求和响应,还包括了从 fetchStartDate 开始,到 responseEndDate 结束之间的指标,当然还有 networkProtocolName 和 resourceFetchType 属
再看一下里面的属性:
request:表示了网络请求对象
response:表示了网络响应对象,如果网络出错或没有响应时,response 为 nil
networkProtocolName:获取资源时使用的网络协议,由 ALPN 协商后标识的协议,比如 h2, http/1.1, spdy/3.1
isProxyConnection:是否使用代理进行网络连接
isReusedConnection:是否复用已有连接
resourceFetchType:NSURLSessionTaskMetricsResourceFetchType 枚举类型,标识资源是通过网络加载,服务器推送还是本地缓存获取的
对于下面所有 NSDate 类型指标,如果任务没有完成,所有相应的 EndDate 指标都将为 nil。例如,如果 DNS 解析超时、失败或者客户端在解析成功之前取消,domainLookupStartDate 会有对应的数据,然而 domainLookupEndDate 以及在它之后的所有指标都为 nil
如果是复用已有的连接或者从本地缓存中获取资源,下面的指标都会被赋值为 nil:
domainLookupStartDate
domainLookupEndDate
connectStartDate
connectEndDate
secureConnectionStartDate
secureConnectionEndDate
fetchStartDate:客户端开始请求的时间,无论资源是从服务器还是本地缓存中获取
domainLookupStartDate:DNS 解析开始时间,Domain -> IP 地址
domainLookupEndDate:DNS 解析完成时间,客户端已经获取到域名对应的 IP 地址
connectStartDate:客户端与服务器开始建立 TCP 连接的时间
secureConnectionStartDate:HTTPS 的 TLS 握手开始时间
secureConnectionEndDate:HTTPS 的 TLS 握手结束时间
connectEndDate:客户端与服务器建立 TCP 连接完成时间,包括 TLS 握手时间
requestStartDate:开始传输 HTTP 请求的 header 第一个字节的时间
requestEndDate:HTTP 请求最后一个字节传输完成的时间
responseStartDate:客户端从服务器接收到响应的第一个字节的时间
responseEndDate:客户端从服务器接收到最后一个字节的时间