2013-02-23 63 views
10

我有一個使用Python/Bottle編寫的REST前端,它處理文件上傳,通常是大文件上傳。該API的編碼方式如下:使用瓶子(或燒瓶或類似物品)上傳文件上傳

客戶端發送PUT並將該文件作爲有效負載。除此之外,它還發送日期和授權標題。這是針對重放攻擊安全措施 - 請求被燒焦了臨時密鑰,使用目標URL,日期和其他一些東西

現在的問題。如果提供的日期在15分鐘的給定日期時間窗口中,服務器接受請求。如果上傳時間足夠長,則它會比允許的時間增量長。現在,請求授權處理是使用瓶子視圖方法上的裝飾器完成的。但是,除非上傳完成,否則bottle不會啓動分發過程,因此在更長的上傳時驗證失敗。

我的問題是:有沒有辦法來解釋瓶或WSGI立即處理該請求,併爲它去流上傳?由於其他原因,這對我也很有用。還是其他解決方案?在寫這篇文章時,我想到了WSGI中間件,但仍然需要外部洞察力。

我願意轉瓶,甚至是其他Python框架,因爲REST前端是相當輕巧。

謝謝

回答

16

我建議在前端將傳入文件拆分爲更小的塊。我這樣做是爲了在Flask應用程序中爲大文件上傳實現暫停/恢復功能。

使用Sebastian Tschan's jquery plugin,可以實現通過初始化插件時,在指定maxChunkSize分塊:

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

現在客戶端在上傳大文件時,發送多個請求。而您的服務器端代碼可以使用Content-Range標題將原始大文件修補到一起。對於瓶的應用程序,視圖可能看起來像:

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

對於特定的應用程序,你只需要確保正確的AUTH頭仍與每個請求一起發送。

希望這會有所幫助!我被這個問題困擾了一邊掙扎;)

+0

我會補充說,在一些操作系統(在我的情況的Ubuntu 14.10),如果你打開(文件名,「A」),再求()將不會移動鼠標指針。追加將被強制執行,並且您將始終將傳入塊附加到文件末尾。 – Drachenfels 2015-03-24 17:37:44

+0

@ petrus-jvrensburg您的回答非常適合我的需求,但我想知道,在兩個用戶同時上傳相同文件名的情況下,Flask如何不混合請求?你是否必須實現一個會話機制來識別這兩個用戶,或者是否有一些底層的http/nginx/uwsgi/flask屬性能夠將請求正確映射到相同的調用方法?感謝您的幫助! – 2015-06-19 19:15:22

+0

@CyrilN。沒想過。但是,如果您已經爲您的應用程序設置了一些身份驗證,請使用該身份驗證。否則,您可以詢問'request.remote_addr'和'request.user_agent'以區分同時使用的用戶。 – 2015-06-20 20:33:11

1

當使用plupload解決方案可能是像這樣的:在這種情況下

$("#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' 
}); 

而且你的燒瓶,Python代碼將類似於此:

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總是發送塊中完全相同的順序,從第一個到最後,所以你不要有求之類的東西來打擾。