Что не так с моим кодом для загрузки файла в AWS S3 с помощью предварительно подписанного URL-адреса?

Я хочу загрузить файл из приложения iOS в корзину AWS S3 с помощью предварительно подписанного URL-адреса. URL-адрес правильный, потому что он работает с curl в командной строке.

curl -v -k --upload-file FILENAME "https://MYBUCKET.amazonaws.com:443/KEYNAME?Signature=...&Expires=1391691489&AWSAccessKeyId=..."

со следующим кодом Objective-C...

- (void)upload:(NSString *)url fileData:(NSData *)fileData
{
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:[NSURL URLWithString:url]];
    [request setHTTPMethod:@"PUT"];
    [request setHTTPBody:fileData];
    [request setValue:[NSString stringWithFormat:@"%d", [fileData length]] forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"audio/mpeg" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"public-read" forHTTPHeaderField:@"x-amz-acl"];
    [request setValue:@"iPhone-OS/6.0 fr_FR NE" forHTTPHeaderField:@"User-Agent"];

    _connection = [NSURLConnection connectionWithRequest:request delegate:self];
    [_connection start];
}

... Я получаю эту ошибку:

Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo=0x9c49560 {NSErrorFailingURLStringKey=https://MYBUCKET.s3.amazonaws.com:443/KEYNAME?Signature=...&Expires=1391703958&AWSAccessKeyId=..., NSErrorFailingURLKey=https://MYBUCKET.amazonaws.com:443/KEYNAME?Signature=...&Expires=1391703958&AWSAccessKeyId=..., NSLocalizedDescription=The request timed out., NSUnderlyingError=0x9c48c80 "The request timed out."}

Я использовал WireShark, чтобы увидеть, есть ли трафик, и есть много трафика.

Я понятия не имею, что не так с моим кодом. Похоже, передача файлов не завершается правильно.

1 ответов


Я решил это сам. The Content-Type заголовок был виновником. В полном отчаянии я протестировал свой код с очень маленьким текстовым файлом и получил 403 в качестве кода состояния HTTP от S3. Нет тайм-аута. Такой прогресс. Я также получил очень информативное сообщение об ошибке:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message><StringToSignBytes>...</StringToSignBytes><RequestId>...</RequestId><HostId>...</HostId><SignatureProvided>...</SignatureProvided>
    <StringToSign>PUT

    text/plain
    1391784394
    KEYNAME</StringToSign>
    <AWSAccessKeyId>...</AWSAccessKeyId>
</Error>

очевидно, строка типа контента (text/plain в этом случае) ожидается в строке для подписи, если она предоставляется как HTTP-заголовок от клиента. Не спрашивайте меня, почему это вызывает тайм-аут с большой (5.5 MB?) файлы. Надеюсь, это сэкономит кому-то еще несколько часов жизни.

самое простое исправление - просто удалить строку

[request setValue:@"..." forHTTPHeaderField:@"Content-Type"];

если вы знаете тип контента при создании предварительно подписанного URL-адреса, вы можете, конечно, добавить строку в строку для подписи.