14

現在、Amazon S3 に画像をアップロードしているアプリがあります。アプリがバックグラウンドにある間にアップロードを続行できるように、NSURLConnection の使用から NSURLSession の使用に切り替えようとしています。少し問題が発生しているようです。NSURLRequest が作成されて NSURLSession に渡されますが、Amazon は 403 - 禁止された応答を返します。同じ要求を NSURLConnection に渡すと、ファイルが完全にアップロードされます。

応答を作成するコードは次のとおりです。

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回答から来たと思います):

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"];

次に、このコード行を使用すると:

[NSURLConnection connectionWithRequest:request delegate:self];

それは動作し、ファイルをアップロードしますが、私が使用する場合:

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

禁じられたエラーが表示されます..!?

これでS3にアップロードしようとした人はいますか?同様の問題に遭遇しましたか? セッションが一時停止してアップロードを再開する方法に関係しているのでしょうか、それともリクエストに対して何かおかしいことをしているのでしょうか..?

考えられる解決策の 1 つは、私が管理している中間サーバーにファイルをアップロードし、完了したら S3 に転送することです... しかし、これは明らかに理想的な解決策ではありません!

どんな助けでも大歓迎です!!

ありがとう!

4

7 に答える 7

8

Zeev Vaxの回答に基づいて機能させました。私が遭遇した問題についていくつかの洞察を提供し、マイナーな改善を提供したいと思います。

たとえば、通常の PutRequest を作成します。

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

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

ここで、S3Client が通常行ういくつかの作業を行う必要があります。

// 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];

そのすべてを新しいリクエストにコピーします。Amazon は独自の NSUrlRequest クラスを使用しているため、例外が発生します。

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

これで、実際の転送を開始できます

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

バックグラウンド セッションを作成するコードは次のとおりです。

- (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;
}

セッション/タスクデリゲートが認証チャレンジを処理する必要があることを理解するのにしばらく時間がかかりました(実際にはs3への認証です)。だから実装するだけ

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
    NSLog(@"session did receive challenge");
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
于 2014-04-07T16:04:11.430 に答える
3

まだよくわかりNSURLSessionUploadTaskませんが、これをデバッグする方法を教えてください。

Charlesのようなツールを使用して、アプリケーションが行う HTTP(S) リクエストを確認できます。この問題は、NSURLSessionUploadTask設定したヘッダーを が無視するか、Amazon の S3 がファイルのアップロードに期待するものとは異なる HTTP メソッドを使用している可能性があります。これは、インターセプト プロキシを使用して簡単に確認できます。

また、Amazon S3 が 403 のようなエラーを返す場合、実際にはエラーに関する詳細情報を含む XML ドキュメントを返します。NSURLSessionたぶん、応答本文を取得できるデリゲート メソッドがありますか? そうでない場合は、Charles がより多くの洞察を提供してくれるはずです。

于 2013-10-20T15:25:15.163 に答える
2

バックグラウンドでアップロード/ダウンロードするには、バックグラウンド構成で NSURLSession を使用する必要があります。AWS SDK 2.0.7 以降、署名済みのリクエストを使用できます。

署名済み URL ビルダー** - SDK には、署名済みの Amazon Simple Storage Service (S3) URL のサポートが含まれるようになりました。これらの URL を使用して、NSURLSession クラスを使用してバックグラウンド転送を実行できます。

バックグラウンド NSURLSession と AWS サービスの初期化

- (void)initBackgroundURLSessionAndAWS
{
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:AWSS3BackgroundSessionUploadIdentifier];
    self.urlSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    AWSServiceConfiguration *configuration = [AWSServiceConfiguration configurationWithRegion:DefaultServiceRegionType credentialsProvider:credentialsProvider];
    [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
    self.awss3 = [[AWSS3 alloc] initWithConfiguration:configuration];
}

ファイルアップロード機能の実装

- (void)uploadFile
{
    AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new];
    getPreSignedURLRequest.bucket = @"your_bucket";
    getPreSignedURLRequest.key = @"your_key";
    getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodPUT;
    getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:3600];
    //Important: must set contentType for PUT request
    getPreSignedURLRequest.contentType = @"your_contentType";

    [[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest] continueWithBlock:^id(BFTask *task) {
        if (task.error)
        {
            NSLog(@"Error BFTask: %@", task.error);
        }
        else
        {
            NSURL *presignedURL = task.result;
            NSLog(@"upload presignedURL is: \n%@", presignedURL);

            NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:presignedURL];
            request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
            [request setHTTPMethod:@"PUT"];
            [request setValue:contentType forHTTPHeaderField:@"Content-Type"];

//          Background NSURLSessions do not support the block interfaces, delegate only.
            NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request fromFile:@"file_path"];

            [uploadTask resume];
        }
        return nil;
    }];
}

NSURLSessionデリゲート関数:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    if (error)
    {
        NSLog(@"S3 UploadTask: %@ completed with error: %@", task, [error localizedDescription]);
    }
    else
    {
//      AWSS3GetPreSignedURLRequest does not contain ACL property, so it has to be set after file was uploaded
        AWSS3PutObjectAclRequest *aclRequest = [AWSS3PutObjectAclRequest new];
        aclRequest.bucket = @"your_bucket";
        aclRequest.key = @"yout_key";
        aclRequest.ACL = AWSS3ObjectCannedACLPublicRead;

        [[self.awss3 putObjectAcl:aclRequest] continueWithBlock:^id(BFTask *bftask) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (bftask.error)
                {
                    NSLog(@"Error putObjectAcl: %@", [bftask.error localizedDescription]);
                }
                else
                {
                    NSLog(@"ACL for an uploaded file was changed successfully!");
                }
            });
            return nil;
        }];
    }
}
于 2015-02-24T00:10:41.090 に答える
2

タスクを実行するための私のコードは次のとおりです。

AmazonS3Client *s3Client = [[AmazonS3Client alloc] initWithAccessKey:accessKey withSecretKey:secretKey];
S3PutObjectRequest *s3PutObjectRequest = [[S3PutObjectRequest alloc] initWithKey:[url lastPathComponent] inBucket:bucket];
s3PutObjectRequest.cannedACL = [S3CannedACL publicRead];
s3PutObjectRequest.endpoint = s3Client.endpoint;
s3PutObjectRequest.contentType = fileMIMEType([url absoluteString]);
[s3PutObjectRequest configureURLRequest];

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

NSURLSessionUploadTask *task = [[self backgroundURLSession] uploadTaskWithRequest:request2 fromFile:url];
[task resume];

アップロードした S3 バックグラウンドをオープンソース化しましたhttps://github.com/genadyo/S3Uploader/

于 2014-09-07T15:42:48.100 に答える
0

最近、Amazon は AWS API を 2.2.4 に更新しました。このアップデートの特徴は、バックグラウンド アップロードをサポートしていることです。NSURLSession を使用してビデオをアップロードする必要はありません。非常にシンプルです。次のソース ブロックを使用してテストできます。古いバージョンでテストしましたが、30 です。以前のバージョンより 40% 高速化

AppDelegate.m で didFinishLaunchingWithOptions メソッド // ~GM~ AWS V2 構成の cognito をセットアップします

AWSStaticCredentialsProvider *staticProvider = [[AWSStaticCredentialsProvider alloc] initWithAccessKey:@"xxxx secretKey:@"xxxx"];  

AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSWest2                                                                 credentialsProvider:staticProvider];

AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = configuration;

handleEventsForBackgroundURLSession メソッド内

[AWSS3TransferUtility interceptApplication:application
       handleEventsForBackgroundURLSession:identifier
                         completionHandler:completionHandler];

アップロードクラスで

NSURL *fileURL = // The file to upload.

AWSS3TransferUtilityUploadExpression *expression = [AWSS3TransferUtilityUploadExpression new];
expression.uploadProgress = ^(AWSS3TransferUtilityTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Do something e.g. Update a progress bar.
    });
};

AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler = ^(AWSS3TransferUtilityUploadTask *task, NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Do something e.g. Alert a user for transfer completion.
        // On failed uploads, `error` contains the error object.
    });
};

AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
[[transferUtility uploadFile:fileURL
                      bucket:@"YourBucketName"
                         key:@"YourObjectKeyName"
                 contentType:@"text/plain"
                  expression:expression
            completionHander:completionHandler] continueWithBlock:^id(AWSTask *task) {
    if (task.error) {
        NSLog(@"Error: %@", task.error);
    }
    if (task.exception) {
        NSLog(@"Exception: %@", task.exception);
    }
    if (task.result) {
        AWSS3TransferUtilityUploadTask *uploadTask = task.result;
        // Do something with uploadTask.
    }

    return nil;
}];

その他の参照: https://aws.amazon.com/blogs/mobile/amazon-s3-transfer-utility-for-ios/

于 2015-08-26T13:20:54.903 に答える