您将需要编辑目标 S3 存储桶的一些权限属性,以便最终请求具有足够的权限来写入存储桶。登录 AWS 控制台并选择 S3 部分。选择适当的存储桶,然后单击“属性”选项卡。选择权限部分并提供三个选项(添加更多权限、编辑存储桶策略和编辑 CORS 配置)。
CORS(跨域资源共享)将允许您的应用程序访问 S3 存储桶中的内容。每个规则都应指定一组域,从这些域中授予对存储桶的访问权限,以及从这些域中允许的方法和标头。
要在您的应用程序中使用此功能,请单击“添加 CORS 配置”并输入以下 XML:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>yourdomain.com</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
在 CORS 窗口中单击“保存”,然后在存储桶的“属性”选项卡中再次单击“保存”。
这告诉 S3 允许任何域访问存储桶,并且请求可以包含任何标头。为了安全起见,您可以将“AllowedOrigin”更改为仅接受来自您的域的请求。
如果您希望专门为此应用程序使用 S3 凭证,则可以在 AWS 账户页面中生成更多密钥。这提供了进一步的安全性,因为您可以指定这组密钥能够执行的一组非常具体的请求。如果这对您更有利,那么您还需要在 S3 存储桶的编辑存储桶策略选项中设置一个 IAM 用户。 AWS 的网页上有各种指南,详细说明了如何实现这一点。
设置客户端代码
此设置不需要任何额外的非标准 Python 库,但需要一些脚本来完成客户端的实现。
本文介绍了 s3upload.js 脚本的使用。从项目的 repo 中获取此脚本(使用 Git 或其他方式)并将其存储在应用程序的静态目录中适当的位置。该脚本当前依赖于 JQuery 和 Lo-Dash 库。本指南稍后将介绍在您的应用程序中包含这些内容。
现在可以创建 HTML 和 JavaScript 来处理文件选择,从 Python 应用程序获取请求和签名,然后最终发出上传请求。
首先,在应用程序的模板目录中创建一个名为 account.html 的文件,并为应用程序适当地填充 head 和其他必要的 HTML 标记。在此 HTML 文件的正文中,包含一个文件输入和一个元素,该元素将包含上传进度的状态更新。
<input type="file" id="file" onchange="s3_upload();"/>
<p id="status">Please select a file</p>
<div id="preview"><img src="/static/default.png" /></div>
<form method="POST" action="/submit_form/">
<input type="hidden" id="" name="" value="/static/default.png" />
<input type="text" name="example" placeholder="" /><br />
<input type="text" name="example2" placeholder="" /><br /><br />
<input type="submit" value="" />
</form>
预览元素最初包含一个默认图像。当用户选择新图像时,这两者都由 JavaScript 更新,如下所述。
因此,当用户最终单击提交按钮时,图像的 URL 与用户的其他详细信息一起被提交到您所需的端点以进行服务器端处理。当用户选择文件时,将调用 JavaScript 方法 s3_upload()。下面介绍了此方法的创建和填充。
接下来,在您的 HTML 文件 account.html 中包含三个依赖脚本。如果将此文件放在 /static 以外的目录中,则可能需要调整 files3upload.js 的 src 属性:
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="https://raw.github.com/bestiejs/lodash/v1.1.1/dist/lodash.min.js"></script>
<script type="text/javascript" src="/static/s3upload.js"></script>
脚本的顺序很重要,因为需要按此顺序满足依赖关系。如果您希望托管自己的 JQuery 和 Lo-Dash 版本,请相应地调整 src 属性。
最后,在一个块中,再次在同一个文件中声明一个 JavaScript 函数 s3_upload() 来处理文件上传。此块需要存在于包含三个依赖项的下方:
function s3_upload(){
var s3upload = new S3Upload({
file_dom_selector: 'file',
s3_sign_put_url: '/sign_s3_upload/',
onProgress: function(percent, message) {
$('#status').html('Upload progress: ' + percent + '%' + message);
},
onFinishS3Put: function(url) {
$('#status').html('Upload completed. Uploaded to: '+ url);
$("#image_url").val(url);
$("#preview").html('<img src="'+url+'" style="width:300px;" />');
},
onError: function(status) {
$('#status').html('Upload error: ' + status);
}
});
}
此函数创建一个新的 S3Upload 实例,向其传递文件输入元素、从中检索签名请求的 URL 和三个函数。
最初,该函数向由 s3_sign_put_url 参数表示的 URL 发出请求,将文件名和 mime 类型作为 GET 参数传递。服务器端代码(将在下一节中介绍)解释请求并以要上传到 S3 的文件的 URL 预览和签名请求进行响应,然后此函数使用该 URL 将文件异步上传到您的存储桶。
该函数会将上传更新发布到 onProgress() 函数,如果上传成功,则调用 onFinishS3Put() 并接收 Python 应用程序视图返回的 URL 作为参数。如果由于任何原因上传失败,onError() 将被调用,status 参数将描述错误。
如果在实施系统后发现页面没有按预期工作,请考虑使用 console.log() 记录 onError() 回调中发生的任何错误,并使用浏览器的错误控制台来帮助诊断问题。
如果成功,预览 div 现在将使用用户选择的图像进行更新,隐藏的输入字段将包含图像的 URL。现在,一旦用户完成了表单的其余部分并单击提交,所有信息都可以发布到同一个端点。
将任何形式的应用程序(基于 Web 或基于设备的)中的任何长时间活动通知用户并显示更改的更新是一种很好的做法。因此,可以使用 status 方法,例如,显示加载 GIF 以指示上传正在进行中,然后可以在上传完成时隐藏它。如果没有此类信息,用户可能会怀疑页面已崩溃,并可能尝试刷新页面或以其他方式中断上传过程。
设置服务器端 Python 代码
生成可以用来签署上传请求的临时签名。此临时签名使用账户详细信息(AWS 访问密钥和秘密访问密钥)作为签名的基础,但用户无法直接访问此信息。签名过期后,相同签名的上传请求将不会成功。
如前所述,本文介绍了 Flask 框架的应用程序的生产,尽管其他 Python 框架的步骤类似。使用 Python 3 的读者在继续之前应该考虑 Flask 网站上的相关信息。
首先创建您的主应用程序文件 application.py,然后适当地设置您的框架应用程序:
from flask import Flask, render_template, request
from hashlib import sha1
import time, os, json, base64, hmac, urllib
app = Flask(__name__)
if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port)
稍后将需要当前未使用的导入语句。
使用 Python 3 的读者应该导入 urllib.parse 来代替 urllib。
接下来,在同一个文件中,您需要创建视图,负责在对各种 URL 发出请求时将正确的信息返回给用户的浏览器。首先定义对/account的请求的视图,返回页面account.html,其中包含供用户填写的表单:
@app.route("/account/")
def account():
return render_template('account.html')
请注意,应用程序的视图需要放在application.py 中的app = Flask(__name__) and if __name__ == '__main__': 行之间。
现在在同一个 Python 文件中创建视图,该文件负责生成和返回客户端 JavaScript 可以用来上传图像的签名。这是客户端在尝试上传到 S3 之前发出的第一个请求。此视图响应对 /sign_s3/ 的请求:
@app.route('/sign_s3/')
def sign_s3():
AWS_ACCESS_KEY = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
S3_BUCKET = os.environ.get('S3_BUCKET')
object_name = request.args.get('s3_object_name')
mime_type = request.args.get('s3_object_type')
expires = int(time.time()+10)
amz_headers = "x-amz-acl:public-read"
put_request = "PUT\n\n%s\n%d\n%s\n/%s/%s" % (mime_type, expires, amz_headers, S3_BUCKET, object_name)
signature = base64.encodestring(hmac.new(AWS_SECRET_KEY, put_request, sha1).digest())
signature = urllib.quote_plus(signature.strip())
url = 'https://%s.s3.amazonaws.com/%s' % (S3_BUCKET, object_name)
return json.dumps({
'signed_request': '%s?AWSAccessKeyId=%s&Expires=%d&Signature=%s' % (url, AWS_ACCESS_KEY, expires, signature),
'url': url
})
使用 Python 3 的读者应该使用 urllib.parse.quote_plus() 来引用签名。
此代码执行以下步骤:
• 将请求接收到/sign_s3/,并从环境中加载AWS 密钥和S3 存储桶名称。
• 要上传的对象的名称和mime 类型是从请求的GET 参数中提取的(此阶段在其他框架中可能有所不同)。
• 设置了签名的到期时间,并构成了签名的临时性质的基础。如图所示,这最好用作相对于当前 UNIX 时间的函数。在此示例中,签名将在 Python 执行该行代码后 10 秒过期。
• 标题行告诉 S3 要授予什么访问权限。在这种情况下,该对象将公开供下载。
• 现在可以根据对象信息、标头和到期时间来构造 PUT 请求。
• 签名生成为已编译 AWS 密钥和实际 PUT 请求的 SHA 哈希。
• 此外,从签名中去除周围的空格并转义特殊字符(使用 quote_plus),以便通过 HTTP 进行更安全的传输。
• 要上传的对象的预期 URL 由 S3 存储桶名称和对象名称的组合生成。
• 最后,可以将签名的请求连同预期的 URL 以 JSON 格式返回给浏览器。
您可能希望为对象分配另一个自定义名称,而不是使用已用于命名文件的名称,这对于防止 S3 存储桶中的意外覆盖很有用。例如,此名称可能与用户帐户的 ID 相关。如果没有,您应该提供一些正确引用名称的方法,以防出现空格或其他尴尬字符。此外,您可以在此阶段对上传的文件进行检查,以限制对某些文件类型的访问。例如,可以实施一个简单的检查以仅允许 .png 文件在此之后继续进行。
对于由包含特殊字符的临时签名签名的请求,S3 有时可能会响应 403(禁止)错误。因此,如上所示,适当地引用签名很重要。
最后,在application.py中,在用户上传图片、填写表单、点击提交后,创建负责接收账户信息的视图。由于这将是一个 POST 请求,因此还需要将其定义为“允许的访问方法”。此方法将响应对 URL /submit_form/ 的请求:
@app.route("/submit_form/", methods=["POST"])
def submit_form():
example = request.form[""]
example2 = request.form[""]
image_url = request.form["image_url"]
update_account(example, example2, image_url)
return redirect(url_for('profile'))
在此示例中,调用了 update_account() 函数,但本文未介绍如何创建此方法。在您的应用程序中,您应该在此阶段提供一些功能,以允许应用程序将这些帐户详细信息存储在某种形式的数据库中,并将这些信息与用户的其余帐户详细信息正确关联。
此外,配置文件页面的 URL 尚未在本文(或伴随代码)中定义。理想情况下,例如,在更新帐户后,用户将被重定向回他们自己的个人资料,以便他们可以看到更新后的信息。
欲了解更多信息http://www.tivix.com/blog/easy-user-uploads-with-direct-s3-uploading/