2013-02-23 9 views
10

Ich habe ein REST-Frontend geschrieben mit Python/Bottle, die Datei-Uploads behandelt, in der Regel große. Die API ist in einer solchen Weise, dass wirtten:Streaming-Datei-Upload mit Flasche (oder Flasche oder ähnliches)

der Client mit der Datei als Payload PUT sendet. Unter anderem sendet es Datums- und Autorisierungsheader. Dies ist eine Sicherheitsmaßnahme gegen Replay-Attacken - die Anforderung mit einem temporären Schlüssel versengt ist, Ziel-URL verwenden, das Datum und einige andere Dinge

nun das Problem. Der Server akzeptiert die Anfrage, wenn das angegebene Datum in einem bestimmten Fenster von 15 Minuten liegt. Wenn der Upload lange genug dauert, ist er länger als das zulässige Zeitdelta. Jetzt wird die Bearbeitung der Anforderungsautorisierung mit dem Decorator auf der Flashenansichtsmethode durchgeführt. Die Flasche startet den Versandvorgang jedoch erst, wenn der Upload abgeschlossen ist. Daher schlägt die Validierung bei längeren Uploads fehl.

Meine Frage ist: Gibt es eine Möglichkeit zur Flasche oder WSGI zu erklären, die Anfrage sofort und Strom mit dem Hochladen zu behandeln, wie es geht? Dies wäre für mich auch aus anderen Gründen nützlich. Oder irgendwelche anderen Lösungen? Während ich dies schreibe, kommt mir WSGI Middleware in den Sinn, aber trotzdem möchte ich externe Einblicke haben.

ich wäre bereit zu Flasche zu wechseln, oder auch anderes Python-Frameworks, wie die REST-Frontend ganz leicht ist.

Danke

Antwort

16

I Splitting die eingehende Datei in kleinere Stücke auf dem Frontend empfehlen. Ich mache das, um eine Pause/Resume-Funktion für große Datei-Uploads in einer Flask-Anwendung zu implementieren.

Mit Sebastian Tschan's jquery plugin, können Sie durch die Angabe eines maxChunkSize Chunking implementieren, wenn das Plug-Initialisierung, wie in:

$('#file-select').fileupload({ 
    url: '/uploads/', 
    sequentialUploads: true, 
    done: function (e, data) { 
     console.log("uploaded: " + data.files[0].name) 
    }, 
    maxChunkSize: 1000000 // 1 MB 
}); 

Jetzt wird der Client mehrere Anfragen senden, wenn große Dateien hochladen. Und Ihr serverseitiger Code kann den Header Content-Range verwenden, um die ursprüngliche große Datei wieder zusammenzufügen. Für eine Flasche Anwendung könnte die Ansicht etwas wie folgt aussehen:

# Upload files 
@app.route('/uploads/', methods=['POST']) 
def results(): 

    files = request.files 

    # assuming only one file is passed in the request 
    key = files.keys()[0] 
    value = files[key] # this is a Werkzeug FileStorage object 
    filename = value.filename 

    if 'Content-Range' in request.headers: 
     # extract starting byte from Content-Range header string 
     range_str = request.headers['Content-Range'] 
     start_bytes = int(range_str.split(' ')[1].split('-')[0]) 

     # append chunk to the file on disk, or create new 
     with open(filename, 'a') as f: 
      f.seek(start_bytes) 
      f.write(value.stream.read()) 

    else: 
     # this is not a chunked request, so just save the whole file 
     value.save(filename) 

    # send response with appropriate mime type header 
    return jsonify({"name": value.filename, 
        "size": os.path.getsize(filename), 
        "url": 'uploads/' + value.filename, 
        "thumbnail_url": None, 
        "delete_url": None, 
        "delete_type": None,}) 

für Ihre Anwendung, Sie müssen nur sicherstellen, dass die korrekten Auth-Header noch mit jeder Anforderung gesendet werden.

Hoffe, das hilft! Ich war für eine Weile mit diesem Problem zu kämpfen;)

+0

Ich werde das auf einigen Betriebssystemen (in meinem Fall Ubuntu 14.10) hinzufügen, wenn Sie geöffnet (Dateinamen, ‚a‘) zu tun, dann suchen() nicht den Mauszeiger bewegen. Das Anhängen wird erzwungen, und Sie hängen eingehende Blöcke immer an das Ende der Datei an. – Drachenfels

+0

@ petrus-jvrensburg Ihre Antwort ist großartig für meine Bedürfnisse, aber ich frage mich, wie kann Flask nicht die Anfrage in dem Fall, dass zwei Benutzer den gleichen Dateinamen zur gleichen Zeit hochladen?Müssen Sie einen Sitzungsmechanismus implementieren, um die beiden Benutzer zu identifizieren, oder gibt es eine zugrundeliegende http/nginx/uwsgi/flask-Eigenschaft, die die Anforderung der gleichen Aufrufmethode korrekt zuordnet? Danke für Ihre Hilfe! –

+0

@CyrilN. Hab nicht darüber nachgedacht. Wenn Sie jedoch bereits eine Authentifizierung für Ihre App eingerichtet haben, verwenden Sie diese. Andernfalls könnten Sie 'request.remote_addr' und 'request.user_agent' abfragen, um zwischen gleichzeitigen Benutzern zu unterscheiden. –

1

Wenn Plupload-Lösung wie dieser sein könnte:

$("#uploader").plupload({ 
    // General settings 
    runtimes : 'html5,flash,silverlight,html4', 
    url : "/uploads/", 

    // Maximum file size 
    max_file_size : '20mb', 

    chunk_size: '128kb', 

    // Specify what files to browse for 
    filters : [ 
     {title : "Image files", extensions : "jpg,gif,png"}, 
    ], 

    // Enable ability to drag'n'drop files onto the widget (currently only HTML5 supports that) 
    dragdrop: true, 

    // Views to activate 
    views: { 
     list: true, 
     thumbs: true, // Show thumbs 
     active: 'thumbs' 
    }, 

    // Flash settings 
    flash_swf_url : '/static/js/plupload-2.1.2/js/plupload/js/Moxie.swf', 

    // Silverlight settings 
    silverlight_xap_url : '/static/js/plupload-2.1.2/js/plupload/js/Moxie.xap' 
}); 

Und Ihr Kolben-Python-Code in einem solchen Fall ähnlich wie dies wäre:

from werkzeug import secure_filename 

# Upload files 
@app.route('/uploads/', methods=['POST']) 
def results(): 
    content = request.files['file'].read() 
    filename = secure_filename(request.values['name']) 

    with open(filename, 'ab+') as fp: 
     fp.write(content) 

    # send response with appropriate mime type header 
    return jsonify({ 
     "name": filename, 
     "size": os.path.getsize(filename), 
     "url": 'uploads/' + filename,}) 

Plupload sendet immer Brocken in genau derselben Reihenfolge, von Anfang bis Ende, so dass Sie mit suchen oder so etwas müssen nicht stören.