Ответ 1
Наконец-то мне удалось. Ключевыми моментами были:
- Отпустите Angular
$http
и вместо этого используйте nativeXMLHttpRequest
. - Используйте
getSignedUrl
функцию AWS SDK, вместо этого реализуя мой собственный процесс генерации сигнатур, как это делают многие библиотеки. - Задайте конфигурацию AWS для использования правильной версии подписи (v4 на момент написания) и области (
'eu-central-1'
в случае Франкфурта).
Ниже приводится пошаговое руководство по тому, что я сделал; он использует AngularJS и NodeJS на сервере, но их довольно легко адаптировать к другим стекам, особенно потому, что он имеет дело с наиболее патологическими случаями (SPA в другом домене, который сервер с ведром в последнее время - во время запись - регион).
Краткое описание рабочего процесса
- Пользователь выбирает файл в браузере; ваш JavaScript сохраняет ссылку на него.
- клиент отправляет запрос на ваш сервер для получения подписанного URL-адреса для загрузки.
- Ваш сервер выбирает имя для объекта, помещаемого в ведро (обязательно избегайте коллизий имен!).
- Сервер получает подписанный URL-адрес для вашего объекта, используя AWS SDK, и отправляет его клиенту. Это включает имя объекта и учетные данные AWS.
- Учитывая файл и подписанный URL, клиент отправляет запрос PUT непосредственно в ваш S3 Bucket.
Перед началом
Убедитесь, что:
- На вашем сервере есть AWS SDK
- У вашего сервера есть учетные данные AWS с правильными правами доступа к вашему ведру.
- Ваш ведро S3 имеет правильную конфигурацию CORS для вашего клиента.
Шаг 1: настройте форму/виджет для загрузки в формате SPA.
Все, что имеет значение, - это иметь рабочий процесс, который в конечном итоге дает вам программный доступ к объекту File
- без его загрузки.
В моем случае я использовал директивы ng-file-select
и ng-file-drop
отличной библиотеки angular-file-upload. Но есть и другие способы сделать это (например, этот пост.).
Обратите внимание, что вы можете получить доступ к полезной информации в своем файловом объекте, таком как file.name
, file.type
и т.д.
Шаг 2: Получите подписанный URL-адрес для файла на вашем сервере
На вашем сервере вы можете использовать AWS SDK для получения защищенного временного URL-адреса PUT
вашего файла из другого места (например, ваш интерфейс).
В NodeJS я сделал это следующим образом:
// ---------------------------------
// some initial configuration
var aws = require('aws-sdk');
aws.config.update({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY,
signatureVersion: 'v4',
region: 'eu-central-1'
});
// ---------------------------------
// now say you want fetch a URL for an object named `objectName`
var s3 = new aws.S3();
var s3_params = {
Bucket: MY_BUCKET_NAME,
Key: objectName,
Expires: 60,
ACL: 'public-read'
};
s3.getSignedUrl('putObject', s3_params, function (err, signedUrl) {
// send signedUrl back to client
// [...]
});
Вероятно, вам захочется узнать URL-адрес для получения вашего объекта (обычно, если это изображение). Чтобы сделать это, я просто удалил строку запроса из URL-адреса:
var url = require('url');
// ...
var parsedUrl = url.parse(signedUrl);
parsedUrl.search = null;
var objectUrl = url.format(parsedUrl);
Шаг 3: отправьте запрос PUT от клиента
Теперь, когда ваш клиент имеет ваш объект File
и подписанный URL, он может отправить запрос PUT на S3. Мой совет в случае Angular состоит в том, чтобы просто использовать XMLHttpRequest вместо службы $http
:
var signedUrl, file;
// ...
var d_completed = $q.defer(); // since I'm working with Angular, I use $q for asynchronous control flow, but it not mandatory
var xhr = new XMLHttpRequest();
xhr.file = file; // not necessary if you create scopes like this
xhr.onreadystatechange = function(e) {
if ( 4 == this.readyState ) {
// done uploading! HURRAY!
d_completed.resolve(true);
}
};
xhr.open('PUT', signedUrl, true);
xhr.setRequestHeader("Content-Type","application/octet-stream");
xhr.send(file);
Выражение признательности
Я хотел бы поблагодарить emil10001 и Уилл Вебберли, публикации которого были очень ценны для меня для этой проблемы.