WWDC 15 提出的 ATS (App Transport Security) 是 Apple 在推进网络通讯安全的一个重要方式。在 iOS 9 和 OS X 10.11 中,默认情况下非 HTTPS 的网络访问是被禁止的,但可以在 Info.plist 中添加 NSAppTransportSecurity
字典并且将NSAllowsArbitraryLoads
设置为 YES
来禁用 ATS。
不过,WWDC 16 中,Apple 表示将继续在 iOS 10 和 macOS 10.12 里收紧对普通 HTTP 的访问限制。从 2017 年 1 月 1 日起,所有的新提交 app 默认是不允许使用 NSAllowsArbitraryLoads
来绕过 ATS
限制的,也就是说,我们最好保证 app 的所有网络请求都是 HTTPS
加密的,否则可能会在应用审核时遇到麻烦。
虽然12月21号苹果宣布推迟强制ATS
,具体截止时间未定,但我们还是要做好准备来应对苹果的强制政策,那有些时候,其实我们的业务还是不想由HTTP
改为HTTPS
,有没有什么更好的办法来绕过ATS呢?这个方案就是使用TCP Socket
来实现HTTP
协议,直接绕过ATS
的限制。
以下以使用GCDAsyncSocket框架实现TCP Socket为例,
#import "GCDAsyncSocket.h"
@interface GSDKHttpRequest()
/**
* 待发送的json串
*/
@property (strong, readwrite, nonatomic) NSString* json;
/**
* http请求的域名
*/
@property (strong, readwrite, nonatomic) NSString* domain;
/**
* http请求的端口,如服务端未指定,一般http使用80端口,https使用443端口
*/
@property (strong, readwrite, nonatomic) NSString* port;
/**
* http请求的url中域名及端口之后的后缀
*/
@property (strong, readwrite, nonatomic) NSString* suffix;
/**
* tcp套接字
*/
@property (strong, readwrite, nonatomic) GCDAsyncSocket* socket;
@end
self.domain = ip;
self.port = port;
self.suffix = urlSuffix;
self.json = jsonString;
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:operation_processing_queue()];
operation_processing_queue()为自定义queue,定义如下:
static dispatch_queue_t operation_processing_queue() {
static dispatch_queue_t operation_processing_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
operation_processing_queue = dispatch_queue_create("com.tencent.tcpTest", DISPATCH_QUEUE_SERIAL);
});
return operation_processing_queue;
}
NSError *error = nil;
if (![self.socket connectToHost:self.domain onPort:[self.port intValue] withTimeout:10.0 error:&error]) {
NSLog(@"[Error]Error connecting: %@", error);
}
/**
* 以下send内容根据具体协议内容填写,此仅为示例
*/
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
NSLog(@"didConnectToHost, host:%@, port:%d", host, port);
NSString* sendData = [NSString stringWithFormat:@"POST %@ HTTP/1.1\r\n", self.suffix];
sendData = [sendData stringByAppendingString:[NSString stringWithFormat:@"Host: %@:%@\r\n", self.domain, self.port]];
sendData = [sendData stringByAppendingString:@"Content-Type: application/json; charset=UTF-8\r\n"];
sendData = [sendData stringByAppendingString:@"Connection: keep-alive\r\n"];
sendData = [sendData stringByAppendingString:@"Accept: */*\r\n"];
sendData = [sendData stringByAppendingString:@"User-Agent: QOSDemo/1 CFNetwork/808.2.16 Darwin/16.3.0\r\n"];
sendData = [sendData stringByAppendingString:@"Accept-Language: zh-cn\r\n"];
sendData = [sendData stringByAppendingString:@"Accept-Encoding: gzip, deflate\r\n"];
sendData = [sendData stringByAppendingString:[NSString stringWithFormat:@"Content-Length: %lu\r\n", [self.json length]]];
sendData = [sendData stringByAppendingString:@"\r\n"];
sendData = [sendData stringByAppendingString:self.json];
NSData* data = [sendData dataUsingEncoding:NSUTF8StringEncoding];
// write data, timeout 3s
[self.socket writeData:data withTimeout:3 tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
NSLog(@"socket:%p didWriteDataWithTag:%ld", sock, tag);
[self.socket readDataWithTimeout:3 tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSLog(@"socket:%p didReadData:withTag:%ld", sock, tag);
NSString* string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"检查一下接收到的数据:%@", string);
//和服务端协商下发数据格式,如本示例中“\r\n\r\n”之后的数据为所需要的json串
NSArray *array = [string componentsSeparatedByString:@"\r\n\r\n"];
if (array && [array count] == 2) {
NSData* tmp = [array[1] dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* result = [NSJSONSerialization JSONObjectWithData:tmp options:0 error:nil];
NSLog(@"所需要的json串:%@", result);
[self.socket disconnect];
}
}