在我的应用程序中,在我的 API 类的 NSURLSession 中可以同时(异步)对我的服务器进行 2-4 个 API 调用。为了向我的服务器发出 API 请求,我必须在每个 NSURLRequest 的 HTTPHeaderField 中提供身份验证 token 。 token有效期为一天,如果过了一天就失效了,我需要刷新token。
我在 API 类的以下代码中执行此操作:
/*!
* @brief sends a request as an NSHTTPURLResponse. This method is private.
* @param request The request to send.
* @param success A block to be called if the request is successful.
* @param error A block to be called if the request fails.
*/
-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
[self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
{
//if auth token expired and getting "not authenticated" error (status 401)
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 401) {
[self refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
self.authToken = response[@"token"];
//attempt to re-try the request that failed due to token expiration
[self sendTask:request successCallback:success errorCallback:errorCallback];
} errorCallback:^(NSString *error) {
//two weeks have passed and the token is no longer refreshable
NSLog(@"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
}];
}
}];
}];
[task resume];
}
这个 sendTask 方法会随着我在应用程序中发出的每个 API 请求而执行,所以我才意识到这是一种糟糕的做法。如果 3 个 API 请求由于 token 无效而失败(一天过去了),那么这 3 个 API 请求都将尝试进行 API 调用以刷新身份验证 token 。
如果其中一个 API 请求失败,我有没有办法只刷新一次身份验证 token ,然后重新尝试失败的 API 调用?
编辑
我编辑了问题的标题以表明我正在使用 NSURLSession
进展
到目前为止,为了防止多个失败的 API 请求同时尝试刷新身份验证 token ,我有一个用于所有失败请求的 NSArray 和一个 NSNumber用作锁以确保身份验证 token 仅尝试刷新一次。我在以下代码中执行此操作:
-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
MyAPIInterface *__weak weakSelf = self;
[self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
{
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 401) {
if ([error isEqualToString:@"invalid_credentials"]) {
errorCallback(@"Invalid username and/or password");
}
else if ([error isEqualToString:@"Unknown error"]) {
errorCallback(error);
}
else {
if (!weakSelf.alreadyRefreshingToken.boolValue) {
//lock alreadyRefreshingToken boolean
weakSelf.alreadyRefreshingToken = [NSNumber numberWithBool:YES];
NSLog(@"NOT REFRESHING TOKEN");
// add failed request to failedRequests array
NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
[mutableFailedRequests addObject:request];
weakSelf.failedRequests = [mutableFailedRequests copy];
// refresh auth token
[weakSelf refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
//store authToken
weakSelf.authToken = response[@"token"];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:weakSelf.authToken forKey:@"authToken"];
[defaults synchronize];
//attempt to re-try all requests that failed due to token expiration
for (NSURLRequest *failedRequest in weakSelf.failedRequests) {
[weakSelf sendTask:failedRequest successCallback:success errorCallback:errorCallback];
}
//clear failedRequests array and unlock alreadyRefreshingToken boolean
[weakSelf clearFailedRequests];
weakSelf.alreadyRefreshingToken = [NSNumber numberWithBool:NO];
NSLog(@"TOKEN REFRESHING SUCCESSFUL THO");
} errorCallback:^(NSString *error) {
NSLog(@"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
//clear failedRequests array
[weakSelf clearFailedRequests];
errorCallback(@"Your login session has expired");
}];
}
else {
NSLog(@"ALREADY REFRESHING TOKEN. JUST ADD TO FAILED LIST");
NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
[mutableFailedRequests addObject:request];
weakSelf.failedRequests = [mutableFailedRequests copy];
}
}
}
else {
NSLog(@"ERROR STRING THO: %@", error);
errorCallback(error);
}
}];
}];
[task resume];
}
#pragma mark Custom Methods
-(void)clearFailedRequests {
NSMutableArray *mutableFailedRequests = [self.failedRequests mutableCopy];
[mutableFailedRequests removeAllObjects];
self.failedRequests = [mutableFailedRequests copy];
}
我的做法是否正确?我偏执的一个部分是我并没有真正在某些点调用 success 或 error 回调。这会导致问题吗?
最佳答案
尝试使用 [weakSelf sendTask] 而不是 [self sendTask:]。检查以下代码:
-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
__weak __typeof(self)weakSelf = self;
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
[self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
{
//if auth token expired and getting "not authenticated" error (status 401)
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 401) {
[self refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
self.authToken = response[@"token"];
//attempt to re-try the request that failed due to token expiration
[weakSelf sendTask:request successCallback:success errorCallback:errorCallback];
} errorCallback:^(NSString *error) {
//two weeks have passed and the token is no longer refreshable
NSLog(@"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
}];
}
}];
}];
[task resume];
}
关于iOS - 在 NSURLSession 中重新尝试失败的 NSURLRequests,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34823840/
我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie
我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的
我早就知道Ruby中的“常量”(即大写的变量名)不是真正常量。与其他编程语言一样,对对象的引用是唯一存储在变量/常量中的东西。(侧边栏:Ruby确实具有“卡住”引用对象不被修改的功能,据我所知,许多其他语言都没有提供这种功能。)所以这是我的问题:当您将一个值重新分配给常量时,您会收到如下警告:>>FOO='bar'=>"bar">>FOO='baz'(irb):2:warning:alreadyinitializedconstantFOO=>"baz"有没有办法强制Ruby抛出异常而不是打印警告?很难弄清楚为什么有时会发生重新分配。 最佳答案
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
我正在尝试在Rails上安装ruby,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf