在开发过程中,经常需要修改Hosts访问测试环境,而对于非越狱iOS设备,无法修改设备Host文件/etc/hosts
;如果在代码中直接将域名修改为ip地址,存在两个问题:
- 破坏了网络代码的结构。
- HTTP Header中的”Host”字段也被修改为了ip地址,而后台对”Host”字段有校验,会导致请求出错。
可以通过对NSURLRequest
的initWithURL:cachePolicy:timeoutInterval:
方法和NSMutableURLRequest
的setURL:
方法进行
method swizzle来动态的将URL中的host修改为ip,并保持HTTP Header中的”Host”字段不变来解决。
使用NLHosts类来保存host到ip的转换
1
2
3
4
5
| interface NLHosts : NSObject
+(NSString*)ipByHost:(NSString*)host;
@end
|
+ipByHost:
方法接收host参数,如果对应host有匹配的ip地址,则返回ip地址,否则返回nil。
NSURLRequest
、NSMutableURLRequest
的method swizzle处理
1
2
3
4
5
6
7
8
| @interface NSURLRequest (Swizzle)
@end
@interface NSMutableURLRequest (Swizzle)
@end
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
| #import "NSURLRequest+Swizzle.h"
#import <objc/runtime.h>
#import "NLHosts.h"
@implementation NSURLRequest (Swizzle)
+(void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class clazz = NSClassFromString(@"NSURLRequest");
Method origMethod = class_getInstanceMethod(clazz, @selector(initWithURL:cachePolicy:timeoutInterval:));
Method newMethod = class_getInstanceMethod(clazz, @selector(initWithNewURL:cachePolicy:timeoutInterval:));
if (origMethod && newMethod) {
method_exchangeImplementations(origMethod, newMethod);
}else{
//NSLog(@"origMethod:%@ newMethod:%@",origMethod,newMethod);
}
});
}
-(id)initWithNewURL:(NSURL *)URL cachePolicy:(NSURLRequestCachePolicy)cachePolicy timeoutInterval:(NSTimeInterval)timeoutInterval
{
NSString *scheme = URL.scheme;
if ([scheme compare:@"http" options:NSCaseInsensitiveSearch] == NSOrderedSame || [scheme compare:@"https" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
NSString *host = URL.host;
NSString *ip = [NLHosts ipByHost:host];
if (ip) {
//NSLog(@"NSURLRequest: host:%@->ip:%@",host,ip);
NSString *absoluteString = [URL absoluteString];
NSRange hostRange = [absoluteString rangeOfString:host];
if (hostRange.location != NSNotFound) {
absoluteString = [absoluteString stringByReplacingCharactersInRange:hostRange withString:ip];
NSURL *newURL = [NSURL URLWithString:absoluteString];
NSMutableURLRequest *newRequest = [[NSMutableURLRequest alloc]initWithURL:newURL cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
[newRequest setValue:host forHTTPHeaderField:@"Host"];
self = newRequest;
return self;
}
}
}
return [self initWithNewURL:URL cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
}
@end
@implementation NSMutableURLRequest (Swizzle)
+(void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class clazz = NSClassFromString(@"NSMutableURLRequest");
Method origMethod = class_getInstanceMethod(clazz, @selector(setURL:));
Method newMethod = class_getInstanceMethod(clazz, @selector(setNewURL:));
if (origMethod && newMethod) {
method_exchangeImplementations(origMethod, newMethod);
}else{
NSLog(@"origMethod:%@ newMethod:%@",origMethod,newMethod);
}
});
}
-(void)setNewURL:(NSURL *)URL
{
NSString *scheme = URL.scheme;
if ([scheme compare:@"http" options:NSCaseInsensitiveSearch] == NSOrderedSame || [scheme compare:@"https" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
NSString *host = URL.host;
NSString *ip = [NLHosts ipByHost:host];
if (ip) {
//NSLog(@"NSMutableURLRequest setURL: host:%@->ip:%@",host,ip);
NSString *absoluteString = [URL absoluteString];
NSRange hostRange = [absoluteString rangeOfString:host];
if (hostRange.location != NSNotFound) {
absoluteString = [absoluteString stringByReplacingCharactersInRange:hostRange withString:ip];
NSURL *newURL = [NSURL URLWithString:absoluteString];
[self setNewURL:newURL];
[self setValue:host forHTTPHeaderField:@"Host"];
return;
}
}
}
return [self setNewURL:URL];
}
@end
|
在NSURLRequest
类的+load
方法中将-initWithURL:cachePolicy:timeoutInterval:
替换为initWithNewURL:cachePolicy:timeoutInterval:
;
在initWithNewURL:cachePolicy:timeoutInterval:
方法中,如果初始化URL中的host需要转换为ip地址,则使用ip地址替换host生成新的URL,使用新URL生成新的
NSMutableURLRequest
,并设置其HTTP Header的”Host”字段为原始值,将此NSMutableURLRequest
返回进行后续网络请求。
NSMutableURLRequest
是NSURLRequest
的子类,所以初始化方法在NSURLRequest
中进行method swizzle替换后,同样可适用在NSMutableURLRequest
。
这样在initWithNewURL:cachePolicy:timeoutInterval:
中生成新的NSMutableURLRequest
时同样也会调用swizzle过的初始化方法,但此时的
host已经是ip地址了,不需要再次进行转换,会直接调用原方法返回。
对NSMutableURLRequest
类的-setURL:
方法也需要进行类似的处理,具体见代码。