Django,Heroku,boto:直接将文件上传到Google云存储

问题描述:

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

我下面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://<bucketName>输出:

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

看来CORS是OK。那么,我该如何解决这个问题呢?谢谢。

编辑2:

的OPTION请求的从Firefox头:

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 

从铬的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 
+0

这将有助于如果您包括了预检头(OPTIONS)请求和响应aders。特别是请求上的ACCESS-CONTROL-REQUEST- *和ORIGIN头以及响应上的ACCESS-CONTROL- *头。 –

+0

你会详细解释一下,因为我不知道如何包含这些数据。非常感谢。 –

+0

最简单的方法是使用浏览器的开发工具(通常是F12,但并非总是如此,例如OS X上的Chrome使用OPTION-COMMAND-i)。开发人员工具应该有一个网络选项卡。确保它正在捕获流量,每个浏览器都有点不同。然后继续并发出您的AJAX请求,浏览器应捕获传出请求和响应。如果您选择适当的请求,您应该能够看到关于浏览器请求和服务器响应的大量信息。 –

头问题不是从未来您的应用,我认为它来自云存储分区。设置api时我遇到同样的问题,您发布的资源缺少标题。

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

虽然为了防止恶意的行为非常有用,这个安全措施也阻止已知的起点之间有用的和合法的相互作用。例如,example.appspot.com上Google App Engine托管的页面上的脚本可能需要使用存储在云存储存储桶中的静态资源(example.storage.googleapis.com)。但是,因为从浏览器的角度来看,这两个来源是不同的,所以浏览器将不允许example.appspot.com的脚本使用XMLHttpRequest从example.storage.googleapis.com获取资源,因为所提取的资源来自不同的起源。

所以它看起来像你需要配置存储桶以允许cors请求。谷歌文档显示下面的代码从谷歌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 
    } 
] 

这样可以让你得到,上传和删除的内容。希望有所帮助。

+0

仍然无法正常工作。我在我的问题中添加了编辑部分。 –

根据编辑2中的信息,请求出了问题。预检(OPTIONS)请求包括标题ACCESS-CONTROL-REQUEST-HEADER。这不是有效的CORS标题。正确的标题是ACCESS-CONTROL-REQUEST-HEADERS,最后注意'S'。

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

错误标题的出现似乎与您收到的错误响应很好地相关。但是,它看起来像头可能不是在您的原始代码中,它看起来像稍后添加它(根据您的意见)。一定要把这个头配置出来,这肯定是不正确的。

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

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

+0

谢谢,乔迪。 'ACCESS-CONTROL-REQUEST-HEADER'(没有'S')只是我错误的复制和粘贴错误。对于那个很抱歉。是的,我在ajax PUT请求中添加了这样一个头文件;所以,我错了。忽略编辑2,你会提供更具体的建议吗?我的所有代码都在问题中(Python和JS),而且我没有执行任何'$ .ajaxSetup()'。 –

+0

尝试更改桶上的CORS配置以打开responseHeader为*。 –

+0

示例:'[{“maxAgeSeconds”:3600,“method”:[“GET”,“POST”,“HEAD”,“DELETE”,“PUT”],“origin”:[“*”],“responseHeader “:[”*“]}]' –

经过这么多的试验和错误,我想出了以下内容。程序运行,但是,有时/一些上传的图像不可见;其他时间他们都可以。我不知道为什么会发生这种情况。

我想征求更多的想法,为什么文件上传是好的,但一些图像已损坏。

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.'); 
    }); 
} 

Django的:

项目的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 
     }) 
    ) 
+0

经过多次试验和错误之后,我发现上述代码适用于pdf和纯文本文件。只有图像文件有问题。为什么? –