本文介绍了NSURLSession和Amazon S3上传的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个应用程序,它目前正在上传图片到Amazon S3。我一直在试图将其从使用NSURLConnection的到NSURLSession这样的上传可以继续,而应用程序是在后台切换!我似乎被打了一下一个问题。所述的NSURLRequest被创建并传递给NSURLSession但亚马逊返送403 - 禁止响应中,如果我传递相同的请求到一个NSURLConnection的它文件上载完美

I have an app which is currently uploading images to amazon S3. I have been trying to switch it from using NSURLConnection to NSURLSession so that the uploads can continue while the app is in the background! I seem to be hitting a bit of an issue. The NSURLRequest is created and passed to the NSURLSession but amazon sends back a 403 - forbidden response, if I pass the same request to a NSURLConnection it uploads the file perfectly.

下面是code创建的响应:

Here is the code that creates the response:

NSString *requestURLString = [NSString stringWithFormat:@"http://%@.%@/%@/%@", BUCKET_NAME, AWS_HOST, DIRECTORY_NAME, filename];
NSURL *requestURL = [NSURL URLWithString:requestURLString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL
                                                       cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
                                                   timeoutInterval:60.0];
// Configure request
[request setHTTPMethod:@"PUT"];
[request setValue:[NSString stringWithFormat:@"%@.%@", BUCKET_NAME, AWS_HOST] forHTTPHeaderField:@"Host"];
[request setValue:[self formattedDateString] forHTTPHeaderField:@"Date"];
[request setValue:@"public-read" forHTTPHeaderField:@"x-amz-acl"];
[request setHTTPBody:imageData];

和那么这个标志的响应(我认为这是来自另外一个SO回答):

And then this signs the response (I think this came from another SO answer):

NSString *contentMd5  = [request valueForHTTPHeaderField:@"Content-MD5"];
NSString *contentType = [request valueForHTTPHeaderField:@"Content-Type"];
NSString *timestamp   = [request valueForHTTPHeaderField:@"Date"];

if (nil == contentMd5)  contentMd5  = @"";
if (nil == contentType) contentType = @"";

NSMutableString *canonicalizedAmzHeaders = [NSMutableString string];

NSArray *sortedHeaders = [[[request allHTTPHeaderFields] allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];

for (id key in sortedHeaders)
{
    NSString *keyName = [(NSString *)key lowercaseString];
    if ([keyName hasPrefix:@"x-amz-"]){
        [canonicalizedAmzHeaders appendFormat:@"%@:%@\n", keyName, [request valueForHTTPHeaderField:(NSString *)key]];
    }
}

NSString *bucket = @"";
NSString *path   = request.URL.path;
NSString *query  = request.URL.query;

NSString *host  = [request valueForHTTPHeaderField:@"Host"];

if (![host isEqualToString:@"s3.amazonaws.com"]) {
    bucket = [host substringToIndex:[host rangeOfString:@".s3.amazonaws.com"].location];
}

NSString* canonicalizedResource;

if (nil == path || path.length < 1) {
    if ( nil == bucket || bucket.length < 1 ) {
        canonicalizedResource = @"/";
    }
    else {
        canonicalizedResource = [NSString stringWithFormat:@"/%@/", bucket];
    }
}
else {
    canonicalizedResource = [NSString stringWithFormat:@"/%@%@", bucket, path];
}

if (query != nil && [query length] > 0) {
    canonicalizedResource = [canonicalizedResource stringByAppendingFormat:@"?%@", query];
}

NSString* stringToSign = [NSString stringWithFormat:@"%@\n%@\n%@\n%@\n%@%@", [request HTTPMethod], contentMd5, contentType, timestamp, canonicalizedAmzHeaders, canonicalizedResource];

NSString *signature = [self signatureForString:stringToSign];

[request setValue:[NSString stringWithFormat:@"AWS %@:%@", self.S3AccessKey, signature] forHTTPHeaderField:@"Authorization"];

那么,如果我使用这行code:

Then if I use this line of code:

[NSURLConnection connectionWithRequest:request delegate:self];

它的工作原理,并上传该文件,但如果我使用:

It works and uploads the file, but if I use:

NSURLSessionUploadTask *task = [self.session uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:filePath]];
[task resume];

我得到禁止错误..!?

I get the forbidden error..!?

有没有人试过上传到S3这点,创下类似的问题?我不知道它是做的方式在会议暂停和恢复上传,或者是做一些有趣的请求..?

Has anyone tried uploading to S3 with this and hit similar issues? I wonder if it is to do with the way the session pauses and resumes uploads, or it is doing something funny to the request..?

一个可能的解决方案是将文件上传到我的控制,并有将其转发到S3时,它是完整的一个临时服务器...但是这显然不是一个理想的解决方案!

One possible solution would be to upload the file to an interim server that I control and have that forward it to S3 when it is complete... but this is clearly not an ideal solution!

任何帮助是非常AP preciated !!

Any help is much appreciated!!

谢谢!

推荐答案

我做了基于兹夫的Vax回答工作。我想提供有关问题我遇到了一些见解,并提供小的改进。

I made it work based on Zeev Vax answer. I want to provide some insight on problems I ran into and offer minor improvements.

建立一个正常的PutRequest,比如

Build a normal PutRequest, for instance

S3PutObjectRequest* putRequest = [[S3PutObjectRequest alloc] initWithKey:keyName inBucket:bucketName];

putRequest.credentials = credentials;
putRequest.filename = theFilePath;

现在我们需要做一些工作的S3Client通常不会对我们

Now we need to do some work the S3Client usually does for us

// set the endpoint, so it is not null
putRequest.endpoint = s3Client.endpoint;

// if you are using session based authentication, otherwise leave it out
putRequest.securityToken = messageTokenDTO.securityToken;

// sign the request (also computes md5 checksums etc.)
NSMutableURLRequest *request = [s3Client signS3Request:putRequest];

现在复制这一切到一个新的请求。亚马逊使用自己的NSURLRequest类,它会导致异常

Now copy all of that to a new request. Amazon use their own NSUrlRequest class which would cause an exception

NSMutableURLRequest* request2 = [[NSMutableURLRequest alloc]initWithURL:request.URL];
[request2 setHTTPMethod:request.HTTPMethod];
[request2 setAllHTTPHeaderFields:[request allHTTPHeaderFields]];

现在我们可以开始实际传输

Now we can start the actual transfer

NSURLSession* backgroundSession = [self backgroundSession];
_uploadTask = [backgroundSession uploadTaskWithRequest:request2 fromFile:[NSURL fileURLWithPath:theFilePath]];
[_uploadTask resume];

这是在code创建背景会话:

This is the code that creates the background session:

- (NSURLSession *)backgroundSession {
    static NSURLSession *session = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.example.my.unique.id"];
        session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    });

    return session;
}

我花了一段时间才能弄清楚,会话/任务委托需要处理的auth挑战(我们实际上认证S3)。因此,只要落实

It took me a while to figure out that the session / task delegate needs to handle an auth challenge (we are in fact authentication to s3). So just implement

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
    NSLog(@"session did receive challenge");
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}

这篇关于NSURLSession和Amazon S3上传的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-05 23:14