2013-02-23 23 views
10

Tôi có một lối vào REST được viết bằng Python/Chai xử lý các tệp tải lên, thường là tệp tải lên lớn. API là wirtten theo cách mà:Tải lên tệp trực tuyến bằng cách sử dụng chai (hoặc bình hoặc tương tự)

Máy khách gửi PUT với tệp dưới dạng tải trọng. Trong số những thứ khác, nó sẽ gửi các tiêu đề Date và Authorization. Đây là biện pháp bảo mật chống lại các cuộc tấn công phát lại - yêu cầu được ghi bằng một khóa tạm thời, sử dụng url mục tiêu, ngày và một số thứ khác

Bây giờ là vấn đề. Máy chủ chấp nhận yêu cầu nếu ngày được cung cấp nằm trong cửa sổ ngày giờ đã cho trong 15 phút. Nếu quá trình tải lên mất nhiều thời gian, thời gian tải lên sẽ dài hơn đồng bằng thời gian cho phép. Bây giờ, việc xử lý ủy quyền yêu cầu được thực hiện bằng cách sử dụng trang trí trên phương thức xem chai. Tuy nhiên, chai sẽ không bắt đầu quá trình gửi trừ khi quá trình tải lên hoàn tất, do đó việc xác thực không thành công khi tải lên lâu hơn.

Câu hỏi của tôi là: có cách nào giải thích cho chai hoặc WSGI xử lý yêu cầu ngay lập tức và truyền trực tuyến tải lên khi nó đi không? Điều này cũng hữu ích cho tôi vì những lý do khác. Hoặc bất kỳ giải pháp nào khác? Khi tôi viết bài này, phần mềm trung gian của WSGI xuất hiện trong đầu, nhưng tôi vẫn muốn hiểu rõ hơn về bên ngoài.

Tôi sẵn sàng chuyển sang Flask hoặc thậm chí các khung công tác Python khác, vì lối vào REST khá nhẹ.

Cảm ơn bạn

Trả lời

16

Tôi khuyên bạn nên chia nhỏ tệp vào thành các đoạn nhỏ hơn trên giao diện người dùng. Tôi đang thực hiện việc này để thực hiện chức năng tạm dừng/tiếp tục cho các tệp tải lên lớn trong ứng dụng Flask.

Sử dụng Sebastian Tschan's jquery plugin, bạn có thể thực hiện chunking bằng cách xác định một maxChunkSize khi khởi tạo các plugin, như trong:

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

Bây giờ khách hàng sẽ gửi nhiều yêu cầu khi tải lên các tập tin lớn. Và mã phía máy chủ của bạn có thể sử dụng tiêu đề Content-Range để vá lại tệp lớn gốc lại với nhau. Đối với ứng dụng Flask, chế độ xem có thể trông giống như sau:

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

Đối với ứng dụng cụ thể của bạn, bạn sẽ phải đảm bảo rằng tiêu đề chính xác vẫn được gửi với mỗi yêu cầu.

Hy vọng điều này sẽ hữu ích! Tôi đã phải vật lộn với vấn đề này trong một thời gian;)

+0

Tôi sẽ thêm điều đó vào một số hệ điều hành (trong trường hợp của tôi là Ubuntu 14.10), nếu bạn mở (tên tệp 'a'), thì tìm kiếm() sẽ không di chuyển con trỏ của bạn. Việc bổ sung sẽ được thực thi và bạn sẽ luôn đính kèm đoạn thư đến vào cuối tệp. – Drachenfels

+0

@ petrus-jvrensburg Câu trả lời của bạn là rất tốt cho nhu cầu của tôi, nhưng tôi tự hỏi, làm thế nào Flask có thể không trộn yêu cầu trong trường hợp hai người dùng tải lên cùng một tên tập tin cùng một lúc?Bạn có phải thực hiện một cơ chế phiên để xác định hai người dùng hoặc có một số thuộc tính http/nginx/uwsgi/flask cơ bản có ánh xạ chính xác yêu cầu đến cùng một phương thức gọi không? Cảm ơn sự giúp đỡ của bạn! –

+0

@CyrilN. Đã không nghĩ về điều đó. Nhưng nếu bạn đã thiết lập một số xác thực cho ứng dụng của mình thì hãy sử dụng. Nếu không, bạn có thể thẩm vấn 'request.remote_addr' và 'request.user_agent' để phân biệt giữa những người dùng đồng thời. –

1

Khi sử dụng giải pháp plupload có thể là như thế này:

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

Và mã bình-python của bạn trong trường hợp như vậy sẽ tương tự như sau:

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 luôn gửi các khối theo thứ tự chính xác, từ đầu đến cuối, vì vậy bạn không phải bận tâm tìm kiếm hoặc bất cứ điều gì tương tự.

Các vấn đề liên quan