【问题标题】:Django, Heroku, boto: direct file upload to Google cloud storageDjango、Heroku、boto:直接文件上传到谷歌云存储
【发布时间】:2016-12-20 15:54:03
【问题描述】:

在部署在 Heroku 上的 Django 项目中,我曾经通过 boto 将文件上传到 Google 云存储。但是,最近我必须上传大文件,这会导致 Heroku 超时。

我正在关注 Heroku 关于direct file upload to S3 的文档,并进行如下自定义:

Python:

conn = boto.connect_gs(gs_access_key_id=GS_ACCESS_KEY,
                       gs_secret_access_key=GS_SECRET_KEY)
presignedUrl = conn.generate_url(expires_in=3600, method='PUT', bucket=<bucketName>, key=<fileName>, force_http=True)

JS:

url = 'https://<bucketName>.storage.googleapis.com/<fileName>?Signature=...&Expires=1471451569&GoogleAccessId=...'; // "presignUrl"

postData = new FormData();
postData.append(...);
...

$.ajax({
  url: url,
  type: 'PUT',
  data: postData,
  processData: false,
  contentType: false,
});

我收到以下错误消息:

XMLHttpRequest cannot load http:/...  Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access.

编辑:

gsutil cors get gs://&lt;bucketName&gt;的输出:

[{"maxAgeSeconds": 3600, "method": ["GET", "POST", "HEAD", "DELETE", "PUT"], "origin": ["*"], "responseHeader": ["Content-Type"]}]

似乎 CORS 没问题。那么,我该如何解决这个问题呢?谢谢。

编辑 2:

来自 Firefox 的 OPTION 请求的标头:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.5,en;q=0.3
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: <bucketName>.storage.googleapis.com
Origin: http://localhost:8000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:48.0) Gecko/20100101 Firefox/48.0

来自 Chrome 的 OPTION 请求的标头:

Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-TW,zh;q=0.8,en;q=0.6,en-US;q=0.4,zh-CN;q=0.2
Access-Control-Request-Headers:
Access-Control-Request-Method:PUT
Connection:keep-alive
Host:directupload.storage.googleapis.com
Origin:http://localhost:8000
Referer:http://localhost:8000/
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
X-Client-Data:CIe2yQEIprbJAQjznMoB

【问题讨论】:

  • 如果您包含预检 (OPTIONS) 请求的标头和响应标头,将会有所帮助。特别是请求中的 ACCESS-CONTROL-REQUEST-* 和 ORIGIN 标头以及响应中的 ACCESS-CONTROL-* 标头。
  • 您能否详细说明一下,因为我不知道如何包含这些数据。非常感谢。
  • 最简单的方法是使用浏览器的开发工具(通常是 F12,但并非总是如此。例如 OS X 上的 Chrome 使用 OPTION-COMMAND-i)。开发者工具应该有一个网络标签。确保它正在捕获流量,每个浏览器都有点不同。然后继续发出您的 AJAX 请求,浏览器应该捕获传出请求和响应。如果您选择适当的请求,您应该能够看到有关来自浏览器的请求和来自服务器的响应的大量信息。
  • 您收到的错误消息引用了对预检请求的响应,因此您希望获取浏览器发出的 OPTIONS HTTP 请求的 HEADER 信息。
  • 我已经在ajax对象中添加了headers: {'Access-Control-Allow-Origin': '*'},,但是还是不行。

标签: django google-cloud-storage boto


【解决方案1】:

标头问题不是来自您的应用程序,我认为它来自云存储桶。我在设置 api 时遇到了同样的问题,您发布到的资源缺少标题。

https://cloud.google.com/storage/docs/cross-origin

虽然有助于防止恶意行为,但此安全措施还可以防止已知来源之间有用且合法的交互。例如,在 example.appspot.com 上由 Google App Engine 托管的页面上的脚本可能希望使用存储在 example.storage.googleapis.com 上的 Cloud Storage 存储桶中的静态资源。但是,因为从浏览器的角度来看,这是两个不同的来源,所以浏览器不允许来自 example.appspot.com 的脚本使用 XMLHttpRequest 从 example.storage.googleapis.com 获取资源,因为获取的资源来自不同的来源。

所以看起来您需要配置存储桶以允许 cors 请求。 google 文档显示了要从 google cli 运行的以下代码。

https://cloud.google.com/storage/docs/cross-origin#Configuring-CORS-on-a-Bucket

gsutil cors set cors-json-file.json gs://example

[
    {
      "origin": ["http://mysite.heroku.com"],
      "responseHeader": ["Content-Type"],
      "method": ["GET", "HEAD", "DELETE", "PUT"],
      "maxAgeSeconds": 3600
    }
]

这将允许您获取、上传和删除内容。希望对您有所帮助。

【讨论】:

  • 还是不行。我在我的问题中添加了 EDIT 部分。
【解决方案2】:

根据 EDIT 2 中的信息,请求有问题。预检 (OPTIONS) 请求包含标头 ACCESS-CONTROL-REQUEST-HEADER。这不是有效的 CORS 标头。正确的标题是ACCESS-CONTROL-REQUEST-HEADERS,注意末尾的“S”。

即使标头是正确的,它也不应该为access-control-allow-origin 标头请求授权。 ACCESS-CONTROL-ALLOW-ORIGIN 不是从客户端发送的标头。它是一个标头,当服务器收到预检请求时,将在从服务器到客户端的响应中自动发送。客户端/浏览器将不允许跨域 PUT 请求,除非它在预检请求中从跨域服务器获得授权浏览器文档当前来源的 ACCESS-CONTROL-ALLOW-ORIGIN 标头。

错误标头的存在似乎与您收到的错误响应密切相关。但是,看起来该标头可能不在您的原始代码中,看起来您稍后添加了它(基于您的 cmets)。一定要把那个头配置去掉,肯定是不正确的。

所以我对那个标头的来源有点困惑,但我认为这是你问题的根源。

看起来您正在使用 jQuery 来发出 AJAX PUT 请求。我真正能建议的是确保您没有在 JS 代码中的某个地方调用 $.ajaxSetup() 可能配置了错误的标头。

【讨论】:

  • 非常感谢,乔迪。 ACCESS-CONTROL-REQUEST-HEADER(没有S)只是我错误的复制粘贴中的一个错字。对于那个很抱歉。是的,我确实在 ajax PUT 请求中添加了这样的标头;所以,我得到了错误的想法。忽略 EDIT 2,您能否提供更具体的建议。我所有的代码都在问题中(Python 和 JS),我没有执行任何$.ajaxSetup()
  • 尝试更改存储桶上的 CORS 配置以打开 responseHeader 为 *.
  • 示例:[{"maxAgeSeconds": 3600, "method": ["GET", "POST", "HEAD", "DELETE", "PUT"], "origin": ["*"], "responseHeader": ["*"]}]
  • 已更改,但现在:403 Forbidden, SignatureDoesNotMatch
  • 对于该错误,Google 建议:检查您的 Google 密码和签名方法。请参阅有关状态代码的 Goggle 文档:cloud.google.com/storage/docs/xml-api/reference-status。此外,403 是发生在 OPTIONS 请求还是 PUT 请求上。我认为 Google 不会尝试授权 OPTIONS 请求,我认为这只会发生在 PUT 上。
【解决方案3】:

经过这么多的尝试和错误,我想出了以下内容。程序正常工作,但是,有时/某些上传的图像不可见;其他时候他们还可以。我不知道为什么会这样。

我想请教更多关于为什么文件上传正常但某些图像已损坏的想法。

gsutil 命令:

gsutil cors set cors.json gs://<bucketName>
gsutil defacl ch -u allUsers:R gs://<bucketName>

cors.json 文件的内容:

[
    {
        "origin": ["*"],
        "responseHeader": ["Content-Type"],
        "method": ["GET", "POST", "HEAD", "DELETE", "PUT"],
        "maxAgeSeconds": 3600
    }
]

HTML:

<p id=status>Choose your avatar:</p>
<input id=fileInput type=file>

JavaScript:

$(document).on('change', '#fileInput', function() {
  var $this = $(this);
  var file = $this[0].files[0];

  $.ajax({
    url: 'upload/sign/?fileName=' + file.name + '&contentType=' + file.type,
    type: 'GET'
  })
  .done(function(data) {
    var response = JSON.parse(data);
    uploadFile(file, response.presignedUrl, response.url, response.contentType)
  })
  .fail(function() {
    alert('Unable to obtain a signed URL.');
  });
});

function uploadFile(file, presignedUrl, url, contentType) {
  var postData = new FormData();
  postData.append('file', file);

  $.ajax({
    url: presignedUrl,
    type: 'PUT',
    data: postData,
    headers: {
      'Content-Type': contentType,
    },
    processData: false,
    contentType: false
  })
  .done(function() {
    alert('File upload successful');
  })
  .fail(function() {
    alert('Unable to upload the file.');
  });
}

姜戈:

项目的urls.py

urlpatterns = [
    ...
    url(r'upload/', include('upload.urls', namespace='upload')),
]

应用的urls.py:

urlpatterns = [
    url(r'^$', views.upload, name='upload'),
    url(r'^sign/', views.sign, name='sign'),
]

views.py:

def upload(request):
    # ... render the template


def sign(request):
    fileName = request.GET.get('fileName')
    contentType = request.GET.get('contentType') 
    conn = boto.connect_gs(gs_access_key_id=GS_ACCESS_KEY,
                           gs_secret_access_key=GS_SECRET_KEY)
    presignedUrl = conn.generate_url(3600, 'PUT', GS_BUCKET_NAME, fileName, headers={'Content-Type':contentType})
    return HttpResponse(
        json.dumps({
            'presignedUrl': presignedUrl,
            'url': GS_URL + fileName,
            'contentType': contentType
        })
    )

【讨论】:

  • 经过多次试验和错误,我发现上面的代码适用于pdf和纯文本文件。只有图像文件有问题。为什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-02
  • 2015-02-28
相关资源
最近更新 更多