如何通过boto为s3浏览器上传设置content-length-range
How to set content-length-range for s3 browser upload via boto
问题
我正在尝试从浏览器将图像直接上传到 S3,但在通过 boto 的 S3Connection.generate_url 方法应用 content-length-range 权限时卡住了。
有很多关于 signing POST forms, setting policies in general and even a heroku method for doing a similar submission 的信息。我一辈子都想不通的是如何将 "content-length-range" 添加到已签名的 url.
使用 boto 的 generate_url 方法(下面的示例),我可以指定策略 headers 并使其适用于正常上传。我似乎无法添加的是对最大文件大小的政策限制。
服务器签名代码
## django request handler
from boto.s3.connection import S3Connection
from django.conf import settings
from django.http import HttpResponse
import mimetypes
import json
conn = S3Connection(settings.S3_ACCESS_KEY, settings.S3_SECRET_KEY)
object_name = request.GET['objectName']
content_type = mimetypes.guess_type(object_name)[0]
signed_url = conn.generate_url(
expires_in = 300,
method = "PUT",
bucket = settings.BUCKET_NAME,
key = object_name,
headers = {'Content-Type': content_type, 'x-amz-acl':'public-read'})
return HttpResponse(json.dumps({'signedUrl': signed_url}))
在客户端,我使用的是 ReactS3Uploader which is based on tadruj's s3upload.js script。它不应该影响任何东西,因为它似乎只是传递 signedUrls 涵盖的任何内容,但为简单起见复制在下面。
ReactS3Uploader JS 代码(简体)
uploadFile: function() {
new S3Upload({
fileElement: this.getDOMNode(),
signingUrl: /api/get_signing_url/,
onProgress: this.props.onProgress,
onFinishS3Put: this.props.onFinish,
onError: this.props.onError
});
},
render: function() {
return this.transferPropsTo(
React.DOM.input({type: 'file', onChange: this.uploadFile})
);
}
S3upload.js
S3Upload.prototype.signingUrl = '/sign-s3';
S3Upload.prototype.fileElement = null;
S3Upload.prototype.onFinishS3Put = function(signResult) {
return console.log('base.onFinishS3Put()', signResult.publicUrl);
};
S3Upload.prototype.onProgress = function(percent, status) {
return console.log('base.onProgress()', percent, status);
};
S3Upload.prototype.onError = function(status) {
return console.log('base.onError()', status);
};
function S3Upload(options) {
if (options == null) {
options = {};
}
for (option in options) {
if (options.hasOwnProperty(option)) {
this[option] = options[option];
}
}
this.handleFileSelect(this.fileElement);
}
S3Upload.prototype.handleFileSelect = function(fileElement) {
this.onProgress(0, 'Upload started.');
var files = fileElement.files;
var result = [];
for (var i=0; i < files.length; i++) {
var f = files[i];
result.push(this.uploadFile(f));
}
return result;
};
S3Upload.prototype.createCORSRequest = function(method, url) {
var xhr = new XMLHttpRequest();
if (xhr.withCredentials != null) {
xhr.open(method, url, true);
}
else if (typeof XDomainRequest !== "undefined") {
xhr = new XDomainRequest();
xhr.open(method, url);
}
else {
xhr = null;
}
return xhr;
};
S3Upload.prototype.executeOnSignedUrl = function(file, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', this.signingUrl + '&objectName=' + file.name, true);
xhr.overrideMimeType && xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var result;
try {
result = JSON.parse(xhr.responseText);
} catch (error) {
this.onError('Invalid signing server response JSON: ' + xhr.responseText);
return false;
}
return callback(result);
} else if (xhr.readyState === 4 && xhr.status !== 200) {
return this.onError('Could not contact request signing server. Status = ' + xhr.status);
}
}.bind(this);
return xhr.send();
};
S3Upload.prototype.uploadToS3 = function(file, signResult) {
var xhr = this.createCORSRequest('PUT', signResult.signedUrl);
if (!xhr) {
this.onError('CORS not supported');
} else {
xhr.onload = function() {
if (xhr.status === 200) {
this.onProgress(100, 'Upload completed.');
return this.onFinishS3Put(signResult);
} else {
return this.onError('Upload error: ' + xhr.status);
}
}.bind(this);
xhr.onerror = function() {
return this.onError('XHR error.');
}.bind(this);
xhr.upload.onprogress = function(e) {
var percentLoaded;
if (e.lengthComputable) {
percentLoaded = Math.round((e.loaded / e.total) * 100);
return this.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing.' : 'Uploading.');
}
}.bind(this);
}
xhr.setRequestHeader('Content-Type', file.type);
xhr.setRequestHeader('x-amz-acl', 'public-read');
return xhr.send(file);
};
S3Upload.prototype.uploadFile = function(file) {
return this.executeOnSignedUrl(file, function(signResult) {
return this.uploadToS3(file, signResult);
}.bind(this));
};
module.exports = S3Upload;
任何帮助将不胜感激,因为我已经用头撞墙好几个小时了。
您不能将其添加到已签名的 PUT
URL。这仅适用于与 POST
一起使用的已签名策略,因为这两种机制非常不同。
签署 URL 是一个有损(因为没有更好的术语)的过程。您生成要签名的字符串,然后对其进行签名。您将签名与请求一起发送,但您丢弃并且不发送要签名的字符串。然后,S3 针对它收到的请求重建要签名的字符串应该是什么,并生成您应该随该请求发送的签名。只有一个正确答案,而且 S3 不知道您实际签名的字符串是什么。签名匹配或不匹配,要么是因为您构建的字符串签名不正确,要么是您的凭据不匹配,并且它不知道是哪种可能性。它只知道,根据您发送的请求,您 应该签名的字符串 以及签名 应该是什么 。
考虑到这一点,要让 content-length-range
与签名的 URL 一起工作,客户端需要实际发送这样的 header 请求......这不会'没有多大意义。
相反,对于 POST
次上传,有更多信息传送到 S3。这不仅与您的签名是否有效有关,而且还有您的政策文件……因此可以在请求中包含指令 - 政策。它们受到签名的保护,不会被更改,但它们未加密或散列——整个策略可由 S3 读取(因此,相比之下,我们将其称为相反的,"lossless.")
这个区别就是为什么你不能用 PUT
做你想做的事情,而你可以用 POST
.
问题
我正在尝试从浏览器将图像直接上传到 S3,但在通过 boto 的 S3Connection.generate_url 方法应用 content-length-range 权限时卡住了。
有很多关于 signing POST forms, setting policies in general and even a heroku method for doing a similar submission 的信息。我一辈子都想不通的是如何将 "content-length-range" 添加到已签名的 url.
使用 boto 的 generate_url 方法(下面的示例),我可以指定策略 headers 并使其适用于正常上传。我似乎无法添加的是对最大文件大小的政策限制。
服务器签名代码
## django request handler
from boto.s3.connection import S3Connection
from django.conf import settings
from django.http import HttpResponse
import mimetypes
import json
conn = S3Connection(settings.S3_ACCESS_KEY, settings.S3_SECRET_KEY)
object_name = request.GET['objectName']
content_type = mimetypes.guess_type(object_name)[0]
signed_url = conn.generate_url(
expires_in = 300,
method = "PUT",
bucket = settings.BUCKET_NAME,
key = object_name,
headers = {'Content-Type': content_type, 'x-amz-acl':'public-read'})
return HttpResponse(json.dumps({'signedUrl': signed_url}))
在客户端,我使用的是 ReactS3Uploader which is based on tadruj's s3upload.js script。它不应该影响任何东西,因为它似乎只是传递 signedUrls 涵盖的任何内容,但为简单起见复制在下面。
ReactS3Uploader JS 代码(简体)
uploadFile: function() {
new S3Upload({
fileElement: this.getDOMNode(),
signingUrl: /api/get_signing_url/,
onProgress: this.props.onProgress,
onFinishS3Put: this.props.onFinish,
onError: this.props.onError
});
},
render: function() {
return this.transferPropsTo(
React.DOM.input({type: 'file', onChange: this.uploadFile})
);
}
S3upload.js
S3Upload.prototype.signingUrl = '/sign-s3';
S3Upload.prototype.fileElement = null;
S3Upload.prototype.onFinishS3Put = function(signResult) {
return console.log('base.onFinishS3Put()', signResult.publicUrl);
};
S3Upload.prototype.onProgress = function(percent, status) {
return console.log('base.onProgress()', percent, status);
};
S3Upload.prototype.onError = function(status) {
return console.log('base.onError()', status);
};
function S3Upload(options) {
if (options == null) {
options = {};
}
for (option in options) {
if (options.hasOwnProperty(option)) {
this[option] = options[option];
}
}
this.handleFileSelect(this.fileElement);
}
S3Upload.prototype.handleFileSelect = function(fileElement) {
this.onProgress(0, 'Upload started.');
var files = fileElement.files;
var result = [];
for (var i=0; i < files.length; i++) {
var f = files[i];
result.push(this.uploadFile(f));
}
return result;
};
S3Upload.prototype.createCORSRequest = function(method, url) {
var xhr = new XMLHttpRequest();
if (xhr.withCredentials != null) {
xhr.open(method, url, true);
}
else if (typeof XDomainRequest !== "undefined") {
xhr = new XDomainRequest();
xhr.open(method, url);
}
else {
xhr = null;
}
return xhr;
};
S3Upload.prototype.executeOnSignedUrl = function(file, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', this.signingUrl + '&objectName=' + file.name, true);
xhr.overrideMimeType && xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var result;
try {
result = JSON.parse(xhr.responseText);
} catch (error) {
this.onError('Invalid signing server response JSON: ' + xhr.responseText);
return false;
}
return callback(result);
} else if (xhr.readyState === 4 && xhr.status !== 200) {
return this.onError('Could not contact request signing server. Status = ' + xhr.status);
}
}.bind(this);
return xhr.send();
};
S3Upload.prototype.uploadToS3 = function(file, signResult) {
var xhr = this.createCORSRequest('PUT', signResult.signedUrl);
if (!xhr) {
this.onError('CORS not supported');
} else {
xhr.onload = function() {
if (xhr.status === 200) {
this.onProgress(100, 'Upload completed.');
return this.onFinishS3Put(signResult);
} else {
return this.onError('Upload error: ' + xhr.status);
}
}.bind(this);
xhr.onerror = function() {
return this.onError('XHR error.');
}.bind(this);
xhr.upload.onprogress = function(e) {
var percentLoaded;
if (e.lengthComputable) {
percentLoaded = Math.round((e.loaded / e.total) * 100);
return this.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing.' : 'Uploading.');
}
}.bind(this);
}
xhr.setRequestHeader('Content-Type', file.type);
xhr.setRequestHeader('x-amz-acl', 'public-read');
return xhr.send(file);
};
S3Upload.prototype.uploadFile = function(file) {
return this.executeOnSignedUrl(file, function(signResult) {
return this.uploadToS3(file, signResult);
}.bind(this));
};
module.exports = S3Upload;
任何帮助将不胜感激,因为我已经用头撞墙好几个小时了。
您不能将其添加到已签名的 PUT
URL。这仅适用于与 POST
一起使用的已签名策略,因为这两种机制非常不同。
签署 URL 是一个有损(因为没有更好的术语)的过程。您生成要签名的字符串,然后对其进行签名。您将签名与请求一起发送,但您丢弃并且不发送要签名的字符串。然后,S3 针对它收到的请求重建要签名的字符串应该是什么,并生成您应该随该请求发送的签名。只有一个正确答案,而且 S3 不知道您实际签名的字符串是什么。签名匹配或不匹配,要么是因为您构建的字符串签名不正确,要么是您的凭据不匹配,并且它不知道是哪种可能性。它只知道,根据您发送的请求,您 应该签名的字符串 以及签名 应该是什么 。
考虑到这一点,要让 content-length-range
与签名的 URL 一起工作,客户端需要实际发送这样的 header 请求......这不会'没有多大意义。
相反,对于 POST
次上传,有更多信息传送到 S3。这不仅与您的签名是否有效有关,而且还有您的政策文件……因此可以在请求中包含指令 - 政策。它们受到签名的保护,不会被更改,但它们未加密或散列——整个策略可由 S3 读取(因此,相比之下,我们将其称为相反的,"lossless.")
这个区别就是为什么你不能用 PUT
做你想做的事情,而你可以用 POST
.