# PDNSCurl

## 集成PDNSCurl

本章节介绍如何将PDNSCurl添加到您的应用中。

### 本地手动集成依赖

#### 将PDNSCurl文件夹添加到工程中

#### 添加Linker Flags

PDNSCurl会使用`zlib`进行HTTP压缩与解压，因此您需要为应用的TARGETS -> Build Settings -> Linking -> Other Linker Flags添加上`-lz`与`-ObjC`。

#### 添加CA证书文件路径（如果使用自签名证书）

如果您使用自签名证书，还需将CA证书文件路径设置到PDNSCurl中，具体请参考[使用PDNSCurl](#使用PDNScurl)章节中的相关内容。

## 使用PDNSCurl

### 开启PDNSCurl拦截

目前PDNSCurl有两种开启方式，第一种方式是拦截指定`NSURLSessionConfiguration`创建的`NSURLSession`发起的请求，第二种方式是拦截全局的`sharedSession`发起的请求。

#### 拦截`NSURLSessionConfiguration`

```objc
+ (void)installIntoSessionConfiguration:(nonnull NSURLSessionConfiguration *)sessionConfiguration;
```

首先，为您的`NSURLSessionConfiguration`安装PDNSCurl。

```objc
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
[PDNSCurlProtocol installIntoSessionConfiguration:config];
```

之后，PDNSCurl可以拦截此`NSURLSessionConfiguration`创建的`NSURLSession`发起的请求。

```objc
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];

NSURL *url = [NSURL URLWithString:@"https://httpbin.org/anything"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (error) {
        NSLog(@"请求失败，错误信息: %@", error.localizedDescription);
        return;
    }
    NSLog(@"响应: %@", response);
    NSString *body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"响应体: %@", body);
}];

[dataTask resume];
```

#### 拦截`sharedSession`

```objc
+ (void)registerCurlProtocol;
```

首先，开启对于`sharedSession`的拦截。

```objc
[PDNSCurlProtocol registerCurlProtocol];
```

之后，PDNSCurl可以拦截`sharedSession`发起的请求。

```objc
NSURLSession *session = [NSURLSession sharedSession];

NSURL *url = [NSURL URLWithString:@"https://httpbin.org/anything"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (error) {
        NSLog(@"请求失败，错误信息: %@", error.localizedDescription);
        return;
    }
    NSLog(@"响应: %@", response);
    NSString *body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"响应体: %@", body);
}];

[dataTask resume];
```

假如您想取消对于`sharedSession`的拦截，可以调用以下API：

```objc
+ (void)unregisterCurlProtocol;
```

### 与公共DNS配合使用

PDNSCurl开放了便捷的DNS hook接口，便于与公共DNS配合使用。只需要实现以下的DNS接口：

```objc
@protocol PDNSCurlProtocolDNSResolver <NSObject>

+ (nullable NSString *)resolveDomain:(nonnull NSString *)domain;

@end
```

例如：

```objc
@interface MyDNSResolver : NSObject <PDNSCurlProtocolDNSResolver>

@end

@implementation MyDNSResolver

+ (nullable NSString *)resolveDomain:(nonnull NSString *)domain {
    NSArray *ips = [[DNSResolver share] getIpv4ByCacheWithDomain:domain andExpiredIPEnabled:YES];
    if (ips && ips.count>0) {
        NSString *combinedIPs = [ips componentsJoinedByString:@","];
        return combinedIPs;
    }
    return nil;
}

@end
```

然后调用以下方法为PDNSCurl设置DNS解析器：

```objc
+ (void)setDNSResolver:(nonnull Class<PDNSCurlProtocolDNSResolver>)dnsResolver;
```

例如：

```objc
[PDNSCurlProtocol setDNSResolver:[MyDNSResolver class]];
```

### 选择HTTP版本

```objc
+ (void)setHTTPVersion:(HTTPVersion)version;
```

PDNSCurl默认使用HTTP2版本，更高版本会包含低版本的能力。需要注意的是，HTTP3需要特殊的编译方式支持，且会引入更大的包体积，请开发者自行编译libcurl库使用。

**HTTP1**: 使用HTTP1.1
**HTTP2**: 首先尝试使用HTTP2，如果与服务器的HTTP2协商失败，则会退回到HTTP1.1

### 设置CA证书文件路径

```objc
+ (void)setSelfSignedCAFilePath:(nonnull NSString *)selfSignedCAFilePath;
```

如果您的服务器使用自签名证书，您需要设置CA证书文件的路径，以确保PDNSCurl能够正确验证SSL/TLS连接。

例如：

```objc
NSString *caFilePath = [[NSBundle mainBundle] pathForResource:@"my_ca" ofType:@"pem"];
[PDNSCurlProtocol setSelfSignedCAFilePath:caFilePath];
```

### 设置连接超时

```objc
+ (void)setConnectTimeoutIntervalForRequest:(nonnull NSMutableURLRequest *)request connectTimeoutInterval:(NSTimeInterval)connectTimeoutInSeconds;
```

`NSURLSession`未提供设置连接超时的方式，因此PDNSCurl单独提供了此功能。对于请求的整体超时时间，请直接配置`NSURLRequest`中的`timeoutInterval`进行设置，默认是60s。

例如：

```objc
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//设置整体超时时间为20s
request.timeoutInterval = 20;
//设置连接超时时间为10s
[PDNSCurlProtocol setConnectTimeoutIntervalForRequest:request connectTimeoutInterval:10.0];
```

### 设置上传进度回调

```objc
typedef void(^PDNSCurlUploadProgressUpdateBlock)(NSURLRequest * _Nonnull request,
                                         int64_t bytesSent,
                                         int64_t totalBytesSent,
                                         int64_t totalBytesExpectedToSend);

+ (void)setUploadProgressUpdateBlockForRequest:(nonnull NSMutableURLRequest *)request uploadProgressUpdateBlock:(nonnull PDNSCurlUploadProgressUpdateBlock)uploadProgressUpdateBlock;
```

由于`NSURLProtocol`并未提供合适的机制来提供上传进度的跟踪，PDNSCurl提供了一个额外的上传进度处理方式。您可以为每个请求设置上传进度回调。

例如：

```objc
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[PDNSCurlProtocol setUploadProgressUpdateBlockForRequest:request uploadProgressUpdateBlock:^(NSURLRequest * _Nonnull request, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
    NSLog(@"上传进度: 已发送 %lld / 总共 %lld 字节", totalBytesSent, totalBytesExpectedToSend);
}];
```

### 设置性能指标回调

```objc
typedef void(^PDNSCurlMetricsObserverBlock)(NSURLRequest * _Nonnull request,
                                   BOOL success,
                                   NSError * _Nullable error,
                                   double nameLookUpTimeMS,
                                   double connectTimeMs,
                                   double appConnectTimeMs,
                                   double preTransferTimeMs,
                                   double startTransferTimeMs,
                                   double totalTimeMs);

+ (void)setMetricsObserverBlockForRequest:(nonnull NSMutableURLRequest *)request metricsObserverBlock:(nonnull PDNSCurlMetricsObserverBlock)metricsObserverBlock;
```

性能指标回调参数说明：
- `request`: 发起请求使用的请求实例
- `success`: 请求是否成功
- `error`: 如果请求失败，包含错误信息
- `nameLookUpTimeMS`: DNS解析耗时，单位毫秒
- `connectTimeMs`: TCP连接耗时，单位毫秒
- `appConnectTimeMs`: SSL/TLS握手耗时，单位毫秒
- `preTransferTimeMs`: 从开始到传输前准备完成的耗时，单位毫秒
- `startTransferTimeMs`: 从开始到收到第一个字节的耗时，单位毫秒
- `totalTimeMs`: 整个请求的总耗时，单位毫秒

网络请求性能指标回调，可以帮助您监控请求的各项耗时指标。

例如：

```objc
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[PDNSCurlProtocol setMetricsObserverBlockForRequest:request metricsObserverBlock:^(NSURLRequest * _Nonnull request, BOOL success, NSError * _Nullable error, double nameLookUpTimeMS, double connectTimeMs, double appConnectTimeMs, double preTransferTimeMs, double startTransferTimeMs, double totalTimeMs) {
    if (!success) {
        NSLog(@"请求失败，错误信息: %@", error.localizedDescription);
        return;
    }
    NSLog(@"性能指标:");
    NSLog(@"DNS解析耗时: %.2f ms", nameLookUpTimeMS);
    NSLog(@"TCP连接耗时: %.2f ms", connectTimeMs);
    NSLog(@"SSL/TLS握手耗时: %.2f ms", appConnectTimeMs);
    NSLog(@"传输前准备耗时: %.2f ms", preTransferTimeMs);
    NSLog(@"收到第一个字节耗时: %.2f ms", startTransferTimeMs);
    NSLog(@"总耗时: %.2f ms", totalTimeMs);
}];
```

### 开启调试日志

```objc
+ (void)setDebugLogEnabled:(BOOL)debugLogEnabled;
```

开启后会打印出日志记录，便于调试。

例如：

```objc
[PDNSCurlProtocol setDebugLogEnabled:YES];
```

### 设置请求拦截域名白名单和黑名单

```objc
+ (void)setHijackDomainWhiteList:(nullable NSArray<NSString *> *)domainWhiteList;
+ (void)setHijackDomainBlackList:(nullable NSArray<NSString *> *)domainBlackList;
```

PDNSCurl允许您设置域名白名单和黑名单来控制哪些请求会被拦截处理：
- 处理请求时，PDNSCurl会先检查黑名单，再检查白名单
- 白名单：只拦截白名单中的域名请求
- 黑名单：不拦截黑名单中的域名请求
- 传入nil时，会清除相应的名单

例如：

```objc
// 只拦截这些域名的请求
[PDNSCurlProtocol setHijackDomainWhiteList:@[@"example.com", @"api.example.com"]];

// 不拦截这些域名的请求
[PDNSCurlProtocol setHijackDomainBlackList:@[@"analytics.example.com"]];

// 清除白名单
[PDNSCurlProtocol setHijackDomainWhiteList:nil];
```

### 设置Gzip压缩

```objc
+ (void)setBuiltInGzipEnabled:(BOOL)enabled;
```

PDNSCurl默认开启内部Gzip压缩。开启后，请求的header中会自动添加`Accept-Encoding: deflate, gzip`，并自动解压响应内容。若关闭，则需要自行处理请求/响应中的gzip字段。

例如：

```objc
// 关闭内置Gzip支持
[PDNSCurlProtocol setBuiltInGzipEnabled:NO];
```

### 设置内部重定向支持

```objc
+ (void)setBuiltInRedirectionEnabled:(BOOL)enabled;
```

PDNSCurl可以配置是否自动处理HTTP重定向（如301、302等状态码），默认开启。

例如：

```objc
// 开启内部重定向支持
[PDNSCurlProtocol setBuiltInRedirectionEnabled:YES];
```

