背景#
网上搜到的关于 SDWebImage 添加 token,亦或者 SDWebImage add header 的方法,都是直接使用SDWebImageDownloader
中的setValue:forHTTPHeaderField:
方法来设置。但是设置了之后笔者这边图片还是出不来,仔细研究后发现笔者这边的图片显示是先经过一次 302 跳转,然后跳转后才是真正的图片链接,第二次的这个链接是需要 token 的。
而直接设置SDWebImageDownloader
的HTTPHeaderField
设置到了第一个链接上面,302 重定向后第二个链接的HTTPHeaderField
仍是没有 token
解决方法#
一般来说,直接使用SDWebImageDownloader
中的setValue:forHTTPHeaderField:
方法设置即可。如下:
- (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
方法如下:
- (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 的方法,是使用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
这个方法,代码如下:
// 使用 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:
方法中,如下:
xxx
mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
xxx
而给链接添加 token 的方法是全局调用的,传值 token 到 header 中,所以想要控制某些链接不加 token,某些添加,就需要在createDownloaderOperationWithUrl:options:context:
这个方法之前控制,使用 AspectPositionBegore 参数,hook 这个方法保证在 header 赋值到 request 之前,判断域名决定是否添加 token,代码如下:
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];
完整代码如下:
- (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];
}