背景

网上搜到的关于SDWebImage 添加 token,亦或者 SDWebImage add header的方法,都是直接使用SDWebImageDownloader中的setValue:forHTTPHeaderField:方法来设置。但是设置了之后笔者这边图片还是出不来,仔细研究后发现笔者这边的图片显示是先经过一次302跳转,然后跳转后才是真正的图片链接,第二次的这个链接是需要 token 的。

而直接设置SDWebImageDownloaderHTTPHeaderField设置到了第一个链接上面,302重定向后第二个链接的HTTPHeaderField仍是没有 token

解决方法

一般来说,直接使用SDWebImageDownloader中的setValue:forHTTPHeaderField:方法设置即可。如下:

1
2
3
4
5
6
7
8

- (void)addSDWebImageToken {
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
NSString *tokenString =[[NSUserDefaults standardUserDefaults]valueForKey:YourToken];
tokenString = [NSString stringWithFormat:@"Bearer %@",tokenString];
[downloader setValue:tokenString forHTTPHeaderField:@"Authorization"];
}

对于经过302重定向的链接,则需要如下修改:

打开SDWebImageDownloader.m类,修改- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {

// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
NSMutableURLRequest *customRequest = [[NSMutableURLRequest alloc] initWithURL:request.URL];
customRequest.allHTTPHeaderFields = self.HTTPHeaders;
if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
[dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:customRequest completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(customRequest);
}
}
}

原理:简单的说是把 重定向的request 变为 CustomRequest, 然后给 CustomRequest 添加 header,最后回调 CustomRequest,而给 CustomRequest 设置 Header的方法,是使用SDWebImageDownloadersetValue:forHTTPHeaderField:设置的。

优化

上面的方法需要直接修改 SDWebImage类库,如果后续更新类库,则可能丢失或者每次都要修改一次。所以需要一个更好的解决方法:
使用 SwizzleMethod hook替换 SDWebImageDownloader.m类中- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler这个方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 使用 Aspects 库
#import <Aspects/Aspects.h>
- (void)setSDWebImageToken {
NSError *error;
[[SDWebImageDownloader sharedDownloader] aspect_hookSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> aspectInfo){
NSArray * param = aspectInfo.arguments;
if (param && param.count > 3) {
NSURLRequest * request = param[3];
NSString *hostStr = request.URL.host;
if ([self isUrlHostSameWithAppHost:hostStr]) {
// 这里判断只有域名和公司域名相同,才处理重定向,否则不处理
NSURLSessionTask * task = param[1];
NSMutableURLRequest *customRequest = [[NSMutableURLRequest alloc] initWithURL:request.URL];
customRequest.allHTTPHeaderFields = task.currentRequest.allHTTPHeaderFields;
request = customRequest;
NSInvocation * invocation = aspectInfo.originalInvocation;
[invocation setArgument:&request atIndex:5];
[invocation invoke];
}
}
} error:&error];
}

更进一步,假如某些图片链接需要token,某些不需要,要怎么处理。只修改上面的方法不行,因为上面的方法只有重定向 URL 才会访问,非重定向的链接不会走上面的方法,所以需要找到非重定向的链接走的方法,然后在那个方法里处理。

查看 SDWebImageDownloader类中的方法后发现,在请求的 header 的赋值是在createDownloaderOperationWithUrl:options:context:方法中,如下:

1
2
3
xxx
mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
xxx

而给链接添加 token 的方法是全局调用的,传值 token 到 header 中,所以想要控制某些链接不加 token,某些添加,就需要在createDownloaderOperationWithUrl:options:context:这个方法之前控制,使用 AspectPositionBegore参数,hook这个方法保证在header赋值到 request之前,判断域名决定是否添加 token,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

NSError *noRedirectError;
[[SDWebImageDownloader sharedDownloader] aspect_hookSelector:@selector(createDownloaderOperationWithUrl:options:context:) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo) {
NSArray *param = aspectInfo.arguments;
if (param && param.count > 2) {
NSURL *url = param[0];
NSString *hostStr = url.host;
if ([self isUrlHostSameWithAppHost:hostStr]) {
[downloader setValue:tokenString forHTTPHeaderField:@"Authorization"];
}
else {
[downloader setValue:nil forHTTPHeaderField:@"Authorization"];
}
}
} error:&noRedirectError];


完整代码如下:

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


- (void)setSDWebImageToken {
NSString *tokenString = @"xxx";
if (IsNilString(tokenString)) {
return;
}
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
tokenString = [NSString stringWithFormat:@"Bearer %@",tokenString];
[downloader setValue:tokenString forHTTPHeaderField:@"Authorization"];

NSError *noRedirectError;
[[SDWebImageDownloader sharedDownloader] aspect_hookSelector:@selector(createDownloaderOperationWithUrl:options:context:) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo) {
NSArray *param = aspectInfo.arguments;
if (param && param.count > 2) {
NSURL *url = param[0];
NSString *hostStr = url.host;
if ([self isUrlHostSameWithAppHost:hostStr]) {
[downloader setValue:tokenString forHTTPHeaderField:@"Authorization"];
}
else {
[downloader setValue:nil forHTTPHeaderField:@"Authorization"];
}
}
} error:&noRedirectError];

NSError *error;
[[SDWebImageDownloader sharedDownloader] aspect_hookSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> aspectInfo){
NSArray * param = aspectInfo.arguments;
if (param && param.count > 3) {
NSURLRequest * request = param[3];
NSString *hostStr = request.URL.host;
if ([self isUrlHostSameWithAppHost:hostStr]) {
NSURLSessionTask * task = param[1];
NSMutableURLRequest *customRequest = [[NSMutableURLRequest alloc] initWithURL:request.URL];
customRequest.allHTTPHeaderFields = task.currentRequest.allHTTPHeaderFields;
request = customRequest;
NSInvocation * invocation = aspectInfo.originalInvocation;
[invocation setArgument:&request atIndex:5];
[invocation invoke];
}
}
} error:&error];
}