2009-10-08 42 views
14

Tôi đang làm việc tại một ứng dụng web bằng Python/Twisted.HTTP Tải xuống tệp rất lớn

Tôi muốn người dùng có thể tải xuống tệp rất lớn (> 100 Mb). Tôi không muốn tải tất cả các tập tin trong bộ nhớ (của máy chủ), tất nhiên.

phía máy chủ Tôi có ý tưởng này:

... 
request.setHeader('Content-Type', 'text/plain') 
fp = open(fileName, 'rb') 
try: 
    r = None 
    while r != '': 
     r = fp.read(1024) 
     request.write(r) 
finally: 
    fp.close() 
    request.finish() 

tôi mong đợi này để làm việc, nhưng tôi có vấn đề: Tôi đang thử nghiệm với FF ... Dường như trình duyệt làm cho tôi chờ đợi cho đến khi tập tin là hoàn thành tải xuống, và sau đó tôi có hộp thoại mở/lưu.

tôi mong đợi hộp thoại ngay lập tức, và sau đó thanh tiến trình trong hành động ...

Có lẽ tôi cần phải thêm một cái gì đó trong tiêu đề Http ... Một cái gì đó giống như kích thước của tập tin?

+1

Bạn sẽ nhận được thông tốt hơn và ít tải trên máy chủ của bạn bằng cách đọc và gửi khối lớn hơn ... thử nghiệm với các giá trị khoảng 4-16k để tìm những gì hoạt động tốt nhất cho hoàn cảnh của bạn. – dcrosta

+0

Bạn có muốn chấp nhận một trong các câu trả lời không? –

Trả lời

3

Có, tiêu đề Nội dung độ dài sẽ cung cấp cho bạn thanh tiến trình bạn muốn!

+0

Vì tôi chỉ gửi cho trình duyệt nội dung của tệp, Nội dung-Lenth chính xác là kích thước của tệp trong Bytes? –

+0

Có, theo http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13 –

3

Nếu đây thực sự là nội dung text/plain, bạn nên cân nhắc gửi nó với Content-Encoding: gzip bất cứ khi nào khách hàng cho biết họ có thể xử lý nó. Bạn nên xem tiết kiệm băng thông rất lớn. Ngoài ra, nếu đây là một tệp tĩnh, những gì bạn thực sự muốn làm là sử dụng sendfile(2). Đối với các trình duyệt không làm những gì bạn mong đợi về tải xuống mọi thứ, bạn có thể muốn xem tiêu đề Content-Disposition. Vì vậy, dù sao đi nữa, logic đi như thế này:

Nếu khách hàng chỉ ra họ có thể xử lý gzip mã hóa thông qua Accept-Encoding tiêu đề (ví dụ như Accept-Encoding: compress;q=0.5, gzip;q=1.0 hoặc Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0 hoặc tương tự) sau đó nén các tập tin, bộ nhớ cache kết quả nén ở đâu đó, hãy viết các tiêu đề chính xác cho phản hồi (Content-Encoding: gzip, Content-Length: n, Content-Type: text/plain, v.v.) và sau đó sử dụng sendfile(2) (tuy nhiên có thể có hoặc không có sẵn trong môi trường của bạn) để sao chép nội dung từ bộ mô tả tệp mở vào luồng phản hồi của bạn.

Nếu họ không chấp nhận gzip, hãy làm tương tự, nhưng không cần gzipping trước.

Ngoài ra, nếu bạn có Apache, Lighttpd, hay diễn xuất tương tự như một proxy trong suốt ở phía trước của máy chủ của bạn, bạn có thể sử dụng tiêu đề, mà là cực kỳ nhanh chóng:

response.setHeader('Content-Type', 'text/plain') 
response.setHeader(
    'Content-Disposition', 
    'attachment; filename="' + os.path.basename(fileName) + '"' 
) 
response.setHeader('X-Sendfile', fileName) 
response.setHeader('Content-Length', os.stat(fileName).st_size) 
35

Hai vấn đề lớn với mã mẫu bạn đăng là không hợp tác và tải toàn bộ tệp vào bộ nhớ trước khi gửi.

while r != '': 
    r = fp.read(1024) 
    request.write(r) 

Hãy nhớ rằng Twisted sử dụng đa nhiệm hợp tác để đạt được bất kỳ loại đồng thời nào. Vì vậy, vấn đề đầu tiên với đoạn mã này là nó là một vòng lặp while trong nội dung của toàn bộ tệp (mà bạn nói là lớn). Điều này có nghĩa toàn bộ tệp sẽ được đọc vào bộ nhớ và được ghi vào phản hồi trước mọi thứ khác có thể xảy ra trong quá trình. Trong trường hợp này, điều đó xảy ra là "mọi thứ" cũng bao gồm việc đẩy các byte từ bộ đệm trong bộ nhớ lên mạng, vì vậy mã của bạn cũng sẽ giữ toàn bộ tệp trong bộ nhớ cùng một lúc và chỉ bắt đầu loại bỏ nó khi vòng lặp hoàn tất.Vì vậy, theo nguyên tắc chung, bạn không nên viết mã để sử dụng trong ứng dụng dựa trên Xoắn sử dụng vòng lặp như thế này để thực hiện một công việc lớn. Thay vào đó, bạn cần phải thực hiện từng phần nhỏ của công việc lớn theo cách sẽ hợp tác với vòng lặp sự kiện. Để gửi tệp qua mạng, cách tốt nhất để tiếp cận điều này là với nhà sản xuấtngười tiêu dùng. Đây là hai API có liên quan để di chuyển một lượng lớn dữ liệu xung quanh bằng cách sử dụng các sự kiện trống đệm để làm việc đó hiệu quả và không lãng phí lượng bộ nhớ không hợp lý.

Bạn có thể tìm thấy một số tài liệu của các API này ở đây:

http://twistedmatrix.com/projects/core/documentation/howto/producers.html

May mắn thay, đối với trường hợp rất phổ biến này, đó cũng là một nhà sản xuất viết đã mà bạn có thể sử dụng, chứ không phải là thực hiện của riêng bạn:

http://twistedmatrix.com/documents/current/api/twisted.protocols.basic.FileSender.html

Bạn có thể muốn sử dụng nó loại như thế này:

from twisted.protocols.basic import FileSender 
from twisted.python.log import err 
from twisted.web.server import NOT_DONE_YET 

class Something(Resource): 
    ... 

    def render_GET(self, request): 
     request.setHeader('Content-Type', 'text/plain') 
     fp = open(fileName, 'rb') 
     d = FileSender().beginFileTransfer(fp, request) 
     def cbFinished(ignored): 
      fp.close() 
      request.finish() 
     d.addErrback(err).addCallback(cbFinished) 
     return NOT_DONE_YET 

Bạn có thể đọc thêm về NOT_DONE_YET và các ý tưởng khác có liên quan đến chuỗi "Twisted Web in 60 Seconds" trên blog của tôi, http://jcalderone.livejournal.com/50562.html (xem mục "phản hồi không đồng bộ").

+0

+1 Wow - một câu trả lời tuyệt vời và thông qua! –

+0

Cảm ơn vì gợi ý của nhà sản xuất/người tiêu dùng. –

1

Dưới đây là một ví dụ về tải file trong khối sử dụng urllib2, mà bạn có thể sử dụng từ bên trong một hàm gọi xoắn

import os 
import urllib2 
import math 

def downloadChunks(url): 
    """Helper to download large files 
     the only arg is a url 
     this file will go to a temp directory 
     the file will also be downloaded 
     in chunks and print out how much remains 
    """ 

    baseFile = os.path.basename(url) 

    #move the file to a more uniq path 
    os.umask(0002) 
    temp_path = "/tmp/" 
    try: 
     file = os.path.join(temp_path,baseFile) 

     req = urllib2.urlopen(url) 
     total_size = int(req.info().getheader('Content-Length').strip()) 
     downloaded = 0 
     CHUNK = 256 * 10240 
     with open(file, 'wb') as fp: 
      while True: 
       chunk = req.read(CHUNK) 
       downloaded += len(chunk) 
       print math.floor((downloaded/total_size) * 100) 
       if not chunk: break 
       fp.write(chunk) 
    except urllib2.HTTPError, e: 
     print "HTTP Error:",e.code , url 
     return False 
    except urllib2.URLError, e: 
     print "URL Error:",e.reason , url 
     return False 

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