2016-08-14 1 views
7

In Django-Projekten, die auf Heroku bereitgestellt wurden, habe ich Dateien über Boto in den Google Cloud-Speicher hochgeladen. In letzter Zeit muss ich jedoch große Dateien hochladen, die Heroku Timeout verursachen.Django, Heroku, boto: direkter Datei-Upload in Google Cloud-Speicher

Ich verfolge Heroku Dokumentation über direct file upload to S3 und Customizing wie folgt:

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, 
}); 

Ich habe die folgende Fehlermeldung:

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. 

EDIT:

Der Ausgang der gsutil cors get gs://<bucketName>:

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

Es scheint, der CORS in Ordnung ist. Also, wie löse ich das Problem? Vielen Dank.

EDIT 2:

Der Header der OPTION Anfrage von 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 

Der Header der OPTION Anfrage von Chrome:

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

Es würde helfen, wenn Sie den Headern für die Preflight enthalten (OPTIONS) Anfrage und die Antwort er aders. Insbesondere die Header ACCESS-CONTROL-REQUEST- * und ORIGIN in der Anfrage und die Header ACCESS-CONTROL- * in der Antwort. –

+0

Würden Sie mehr ausarbeiten, weil ich keine Ahnung habe, wie Sie diese Daten einbeziehen? Danke vielmals. –

+0

Der einfachste Weg wäre, die Entwicklertools Ihres Browsers zu verwenden (normalerweise F12, aber nicht immer. Zum Beispiel verwendet Chrome unter OS X OPTION-COMMAND-i). Die Entwicklerwerkzeuge sollten eine Netzwerkregisterkarte haben. Stellen Sie sicher, dass es Verkehr erfasst, jeder Browser ist ein wenig anders. Dann mach weiter und mach deine AJAX-Anfrage, der Browser sollte die ausgehende Anfrage und die Antwort erfassen.Wenn Sie die entsprechende Anfrage auswählen, sollten Sie eine Reihe von Informationen sowohl über die Anfrage vom Browser als auch die Antwort vom Server sehen können. –

Antwort

3

Die Header-Ausgabe kommt nicht aus Ihre App, ich denke, sie kommt aus dem Cloud Storage Bucket. Ich hatte das gleiche Problem beim Einrichten einer API, die Ressource, die Sie veröffentlichen, fehlt die Überschrift.

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

Während nützlich für schädliches Verhalten zu verhindern, diese Sicherheitsvorkehrung verhindert auch nützliche und legitime Wechselwirkungen zwischen bekannter Herkunft. Ein Skript auf einer Seite, die von Google App Engine unter beispiel.appspot.com gehostet wird, könnte beispielsweise statische Ressourcen verwenden, die in einem Cloud Storage-Bucket unter example.storage.googleapis.com gespeichert sind. Da es sich jedoch um zwei verschiedene Ursprünge aus der Perspektive des Browsers handelt, lässt der Browser einem Skript von example.appspot.com nicht zu, dass Ressourcen von example.storage.googleapis.com mithilfe von XMLHttpRequest abgerufen werden, da die abgerufene Ressource aus a andere Herkunft.

Es sieht also so aus, als müssten Sie den Bucket konfigurieren, um Cors-Anfragen zuzulassen. Die Google-Dokumentation zeigt den folgenden Code, der von Google CLI ausgeführt wird.

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 
    } 
] 

die Sie und Inhalt löschen bekommen, hochladen, erlauben würde. Ich hoffe, das hilft.

+0

Funktioniert immer noch nicht. Ich habe den EDIT-Teil in meine Frage eingefügt. –

1

Basierend auf den Informationen in EDIT 2 stimmt etwas mit der Anfrage nicht. Die Preflight (OPTIONS) -Anforderung enthält den Header ACCESS-CONTROL-REQUEST-HEADER. Dies ist kein gültiger CORS-Header. Die richtige Kopfzeile ist ACCESS-CONTROL-REQUEST-HEADERS, beachten Sie das 'S' am Ende.

Auch wenn der Header korrekt war, sollte er keine Autorisierung für einen access-control-allow-origin Header anfordern. ACCESS-CONTROL-ALLOW-ORIGIN ist keine Kopfzeile, die vom Client gesendet wird. Es ist eine Kopfzeile, die automatisch in der Antwort vom Server an den Client gesendet wird, wenn der Server eine Preflight-Anforderung erhält. Der Client/Browser lässt eine übergreifende PUT-Anfrage nicht zu, es sei denn, er erhält eine ACCESS-CONTROL-ALLOW-ORIGIN Kopfzeile, die den aktuellen Ursprung des Browserdokuments vom Cross-Origin-Server in der Preflight-Anfrage autorisiert.

Das Vorhandensein der fehlerhaften Kopfzeile scheint gut mit der Fehlerantwort übereinzustimmen, die Sie empfangen. Es sieht jedoch so aus, als wäre dieser Header wahrscheinlich nicht in Ihrem ursprünglichen Code enthalten. Es sieht so aus, als hätten Sie ihn später hinzugefügt (basierend auf Ihren Kommentaren). Stellen Sie sicher, dass Sie die Header-Konfiguration herausnehmen, es ist definitiv nicht korrekt.

Ich bin ein wenig verwirrt darüber, woher dieser Header kommt, aber ich denke, dass es die Quelle Ihres Problems ist.

Es sieht so aus, als ob Sie jQuery verwenden, um die AJAX PUT-Anfrage zu stellen. Alles, was ich wirklich vorschlagen kann, ist sicherzustellen, dass Sie nicht $ .ajaxSetup() irgendwo in Ihrem JS-Code aufgerufen haben, der den schlechten Header konfigurieren könnte.

+0

Vielen Dank, Jody. Der 'ACCESS-CONTROL-REQUEST-HEADER' (ohne' S') ist nur ein Tippfehler von meinem schlechten Kopieren-und-Einfügen. Das tut mir leid. Ja, ich habe einen solchen Header in der AJAX-PUT-Anfrage hinzugefügt; Also, ich habe eine falsche Vorstellung. Wenn Sie EDIT 2 ignorieren, würden Sie genauere Vorschläge machen. All mein Code ist in der Frage (Python und JS), und ich habe keine '$ .ajaxSetup()' durchgeführt. –

+0

versuchen Sie, in der Bucket auf CORS config zu wechseln, um responseHeader zu * zu öffnen. –

+0

Beispiel: '[{" maxAgeSeconds ": 3600," Methode ": [" GET "," POST "," KOPF "," LÖSCHEN "," PUT "]," Ursprung ": [" * "]," responseHeader ": [" * "]}]' –

0

Nach so vielen Versuchen und Fehlern kam ich auf Folgendes. Die Programme funktionierten jedoch manchmal/einige der hochgeladenen Bilder sind nicht sichtbar; andere Male sind sie in Ordnung. Ich habe keine Ahnung, warum das passiert ist.

Ich möchte weitere Ideen erbitten, warum Datei-Uploads in Ordnung sind, aber einige der Bilder sind beschädigt.

gsutil Befehle:

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

Inhalt der cors.json Datei:

[ 
    { 
     "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

:

Project urls.py:

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

App 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

Nach vielen Versuchen und Fehlern, fand ich, dass der obige Code für PDF- und reine Textdateien funktioniert. Nur Bilddateien haben Probleme. Warum? –