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