Merge pull request #1094 from benoitc/1038-sendfile-seek

Simplify sendfile logic
This commit is contained in:
Benoit Chesneau 2015-11-23 09:24:46 +01:00
commit 6b92575e00
2 changed files with 38 additions and 75 deletions

View File

@ -12,7 +12,6 @@ import sys
from gunicorn._compat import unquote_to_wsgi_str
from gunicorn.six import string_types, binary_type, reraise
from gunicorn import SERVER_SOFTWARE
import gunicorn.six as six
import gunicorn.util as util
try:
@ -24,6 +23,10 @@ except ImportError:
except ImportError:
sendfile = None
# Send files in at most 1GB blocks as some operating systems can have problems
# with sending files in blocks over 2GB.
BLKSIZE = 0x3FFFFFFF
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
log = logging.getLogger(__name__)
@ -344,77 +347,51 @@ class Response(object):
util.write(self.sock, arg, self.chunked)
def can_sendfile(self):
return (self.cfg.sendfile and (sendfile is not None))
return self.cfg.sendfile and sendfile is not None
def sendfile_all(self, fileno, sockno, offset, nbytes):
# Send file in at most 1GB blocks as some operating
# systems can have problems with sending files in blocks
# over 2GB.
def sendfile(self, respiter):
if self.cfg.is_ssl or not self.can_sendfile():
return False
BLKSIZE = 0x3FFFFFFF
try:
fileno = respiter.filelike.fileno()
offset = os.lseek(fileno, 0, os.SEEK_CUR)
if self.response_length is None:
filesize = os.fstat(fileno).st_size
if nbytes > BLKSIZE:
for m in range(0, nbytes, BLKSIZE):
self.sendfile_all(fileno, sockno, offset, min(nbytes, BLKSIZE))
offset += BLKSIZE
nbytes -= BLKSIZE
else:
sent = 0
sent += sendfile(sockno, fileno, offset + sent, nbytes - sent)
while sent != nbytes:
sent += sendfile(sockno, fileno, offset + sent, nbytes - sent)
# The file may be special and sendfile will fail.
# It may also be zero-length, but that is okay.
if filesize == 0:
return False
def sendfile_use_send(self, fileno, fo_offset, nbytes):
nbytes = filesize - offset
else:
nbytes = self.response_length
except (OSError, io.UnsupportedOperation):
return False
# send file in blocks of 8182 bytes
BLKSIZE = 8192
self.send_headers()
if self.is_chunked():
chunk_size = "%X\r\n" % nbytes
self.sock.sendall(chunk_size.encode('utf-8'))
sockno = self.sock.fileno()
sent = 0
while sent != nbytes:
data = os.read(fileno, BLKSIZE)
if not data:
break
sent += len(data)
if sent > nbytes:
data = data[:nbytes - sent]
for m in range(0, nbytes, BLKSIZE):
count = min(nbytes - sent, BLKSIZE)
sent += sendfile(sockno, fileno, offset + sent, count)
util.write(self.sock, data, self.chunked)
if self.is_chunked():
self.sock.sendall(b"\r\n")
os.lseek(fileno, offset, os.SEEK_SET)
return True
def write_file(self, respiter):
if self.can_sendfile() and util.is_fileobject(respiter.filelike):
# sometimes the fileno isn't a callable
if six.callable(respiter.filelike.fileno):
fileno = respiter.filelike.fileno()
else:
fileno = respiter.filelike.fileno
fd_offset = os.lseek(fileno, 0, os.SEEK_CUR)
fo_offset = respiter.filelike.tell()
nbytes = max(os.fstat(fileno).st_size - fo_offset, 0)
if self.response_length:
nbytes = min(nbytes, self.response_length)
if nbytes == 0:
return
self.send_headers()
if self.cfg.is_ssl:
self.sendfile_use_send(fileno, fo_offset, nbytes)
else:
if self.is_chunked():
chunk_size = "%X\r\n" % nbytes
self.sock.sendall(chunk_size.encode('utf-8'))
self.sendfile_all(fileno, self.sock.fileno(), fo_offset, nbytes)
if self.is_chunked():
self.sock.sendall(b"\r\n")
os.lseek(fileno, fd_offset, os.SEEK_SET)
else:
if not self.sendfile(respiter):
for item in respiter:
self.write(item)

View File

@ -7,7 +7,6 @@ from __future__ import print_function
import email.utils
import fcntl
import io
import os
import pkg_resources
import random
@ -517,19 +516,6 @@ def to_latin1(value):
return value.encode("latin-1")
def is_fileobject(obj):
if not hasattr(obj, "tell") or not hasattr(obj, "fileno"):
return False
# check BytesIO case and maybe others
try:
obj.fileno()
except (IOError, io.UnsupportedOperation):
return False
return True
def warn(msg):
print("!!!", file=sys.stderr)