You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					412 lines
				
				13 KiB
			
		
		
			
		
	
	
					412 lines
				
				13 KiB
			| 
								 
											8 years ago
										 
									 | 
							
								# -*- coding: utf-8 -
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# This file is part of gunicorn released under the MIT license.
							 | 
						||
| 
								 | 
							
								# See the NOTICE for more information.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import io
							 | 
						||
| 
								 | 
							
								import logging
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from gunicorn._compat import unquote_to_wsgi_str
							 | 
						||
| 
								 | 
							
								from gunicorn.http.message import HEADER_RE
							 | 
						||
| 
								 | 
							
								from gunicorn.http.errors import InvalidHeader, InvalidHeaderName
							 | 
						||
| 
								 | 
							
								from gunicorn.six import string_types, binary_type, reraise
							 | 
						||
| 
								 | 
							
								from gunicorn import SERVER_SOFTWARE
							 | 
						||
| 
								 | 
							
								import gunicorn.util as util
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								try:
							 | 
						||
| 
								 | 
							
								    # Python 3.3 has os.sendfile().
							 | 
						||
| 
								 | 
							
								    from os import sendfile
							 | 
						||
| 
								 | 
							
								except ImportError:
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        from ._sendfile import sendfile
							 | 
						||
| 
								 | 
							
								    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
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								HEADER_VALUE_RE = re.compile(r'[\x00-\x1F\x7F]')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								log = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class FileWrapper(object):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, filelike, blksize=8192):
							 | 
						||
| 
								 | 
							
								        self.filelike = filelike
							 | 
						||
| 
								 | 
							
								        self.blksize = blksize
							 | 
						||
| 
								 | 
							
								        if hasattr(filelike, 'close'):
							 | 
						||
| 
								 | 
							
								            self.close = filelike.close
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __getitem__(self, key):
							 | 
						||
| 
								 | 
							
								        data = self.filelike.read(self.blksize)
							 | 
						||
| 
								 | 
							
								        if data:
							 | 
						||
| 
								 | 
							
								            return data
							 | 
						||
| 
								 | 
							
								        raise IndexError
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class WSGIErrorsWrapper(io.RawIOBase):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, cfg):
							 | 
						||
| 
								 | 
							
								        # There is no public __init__ method for RawIOBase so
							 | 
						||
| 
								 | 
							
								        # we don't need to call super() in the __init__ method.
							 | 
						||
| 
								 | 
							
								        # pylint: disable=super-init-not-called
							 | 
						||
| 
								 | 
							
								        errorlog = logging.getLogger("gunicorn.error")
							 | 
						||
| 
								 | 
							
								        handlers = errorlog.handlers
							 | 
						||
| 
								 | 
							
								        self.streams = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if cfg.errorlog == "-":
							 | 
						||
| 
								 | 
							
								            self.streams.append(sys.stderr)
							 | 
						||
| 
								 | 
							
								            handlers = handlers[1:]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for h in handlers:
							 | 
						||
| 
								 | 
							
								            if hasattr(h, "stream"):
							 | 
						||
| 
								 | 
							
								                self.streams.append(h.stream)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def write(self, data):
							 | 
						||
| 
								 | 
							
								        for stream in self.streams:
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                stream.write(data)
							 | 
						||
| 
								 | 
							
								            except UnicodeError:
							 | 
						||
| 
								 | 
							
								                stream.write(data.encode("UTF-8"))
							 | 
						||
| 
								 | 
							
								            stream.flush()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def base_environ(cfg):
							 | 
						||
| 
								 | 
							
								    return {
							 | 
						||
| 
								 | 
							
								        "wsgi.errors": WSGIErrorsWrapper(cfg),
							 | 
						||
| 
								 | 
							
								        "wsgi.version": (1, 0),
							 | 
						||
| 
								 | 
							
								        "wsgi.multithread": False,
							 | 
						||
| 
								 | 
							
								        "wsgi.multiprocess": (cfg.workers > 1),
							 | 
						||
| 
								 | 
							
								        "wsgi.run_once": False,
							 | 
						||
| 
								 | 
							
								        "wsgi.file_wrapper": FileWrapper,
							 | 
						||
| 
								 | 
							
								        "SERVER_SOFTWARE": SERVER_SOFTWARE,
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def default_environ(req, sock, cfg):
							 | 
						||
| 
								 | 
							
								    env = base_environ(cfg)
							 | 
						||
| 
								 | 
							
								    env.update({
							 | 
						||
| 
								 | 
							
								        "wsgi.input": req.body,
							 | 
						||
| 
								 | 
							
								        "gunicorn.socket": sock,
							 | 
						||
| 
								 | 
							
								        "REQUEST_METHOD": req.method,
							 | 
						||
| 
								 | 
							
								        "QUERY_STRING": req.query,
							 | 
						||
| 
								 | 
							
								        "RAW_URI": req.uri,
							 | 
						||
| 
								 | 
							
								        "SERVER_PROTOCOL": "HTTP/%s" % ".".join([str(v) for v in req.version])
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								    return env
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def proxy_environ(req):
							 | 
						||
| 
								 | 
							
								    info = req.proxy_protocol_info
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if not info:
							 | 
						||
| 
								 | 
							
								        return {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return {
							 | 
						||
| 
								 | 
							
								        "PROXY_PROTOCOL": info["proxy_protocol"],
							 | 
						||
| 
								 | 
							
								        "REMOTE_ADDR": info["client_addr"],
							 | 
						||
| 
								 | 
							
								        "REMOTE_PORT": str(info["client_port"]),
							 | 
						||
| 
								 | 
							
								        "PROXY_ADDR": info["proxy_addr"],
							 | 
						||
| 
								 | 
							
								        "PROXY_PORT": str(info["proxy_port"]),
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def create(req, sock, client, server, cfg):
							 | 
						||
| 
								 | 
							
								    resp = Response(req, sock, cfg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # set initial environ
							 | 
						||
| 
								 | 
							
								    environ = default_environ(req, sock, cfg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # default variables
							 | 
						||
| 
								 | 
							
								    host = None
							 | 
						||
| 
								 | 
							
								    script_name = os.environ.get("SCRIPT_NAME", "")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # add the headers to the environ
							 | 
						||
| 
								 | 
							
								    for hdr_name, hdr_value in req.headers:
							 | 
						||
| 
								 | 
							
								        if hdr_name == "EXPECT":
							 | 
						||
| 
								 | 
							
								            # handle expect
							 | 
						||
| 
								 | 
							
								            if hdr_value.lower() == "100-continue":
							 | 
						||
| 
								 | 
							
								                sock.send(b"HTTP/1.1 100 Continue\r\n\r\n")
							 | 
						||
| 
								 | 
							
								        elif hdr_name == 'HOST':
							 | 
						||
| 
								 | 
							
								            host = hdr_value
							 | 
						||
| 
								 | 
							
								        elif hdr_name == "SCRIPT_NAME":
							 | 
						||
| 
								 | 
							
								            script_name = hdr_value
							 | 
						||
| 
								 | 
							
								        elif hdr_name == "CONTENT-TYPE":
							 | 
						||
| 
								 | 
							
								            environ['CONTENT_TYPE'] = hdr_value
							 | 
						||
| 
								 | 
							
								            continue
							 | 
						||
| 
								 | 
							
								        elif hdr_name == "CONTENT-LENGTH":
							 | 
						||
| 
								 | 
							
								            environ['CONTENT_LENGTH'] = hdr_value
							 | 
						||
| 
								 | 
							
								            continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        key = 'HTTP_' + hdr_name.replace('-', '_')
							 | 
						||
| 
								 | 
							
								        if key in environ:
							 | 
						||
| 
								 | 
							
								            hdr_value = "%s,%s" % (environ[key], hdr_value)
							 | 
						||
| 
								 | 
							
								        environ[key] = hdr_value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # set the url scheme
							 | 
						||
| 
								 | 
							
								    environ['wsgi.url_scheme'] = req.scheme
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # set the REMOTE_* keys in environ
							 | 
						||
| 
								 | 
							
								    # authors should be aware that REMOTE_HOST and REMOTE_ADDR
							 | 
						||
| 
								 | 
							
								    # may not qualify the remote addr:
							 | 
						||
| 
								 | 
							
								    # http://www.ietf.org/rfc/rfc3875
							 | 
						||
| 
								 | 
							
								    if isinstance(client, string_types):
							 | 
						||
| 
								 | 
							
								        environ['REMOTE_ADDR'] = client
							 | 
						||
| 
								 | 
							
								    elif isinstance(client, binary_type):
							 | 
						||
| 
								 | 
							
								        environ['REMOTE_ADDR'] = str(client)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        environ['REMOTE_ADDR'] = client[0]
							 | 
						||
| 
								 | 
							
								        environ['REMOTE_PORT'] = str(client[1])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # handle the SERVER_*
							 | 
						||
| 
								 | 
							
								    # Normally only the application should use the Host header but since the
							 | 
						||
| 
								 | 
							
								    # WSGI spec doesn't support unix sockets, we are using it to create
							 | 
						||
| 
								 | 
							
								    # viable SERVER_* if possible.
							 | 
						||
| 
								 | 
							
								    if isinstance(server, string_types):
							 | 
						||
| 
								 | 
							
								        server = server.split(":")
							 | 
						||
| 
								 | 
							
								        if len(server) == 1:
							 | 
						||
| 
								 | 
							
								            # unix socket
							 | 
						||
| 
								 | 
							
								            if host:
							 | 
						||
| 
								 | 
							
								                server = host.split(':')
							 | 
						||
| 
								 | 
							
								                if len(server) == 1:
							 | 
						||
| 
								 | 
							
								                    if req.scheme == "http":
							 | 
						||
| 
								 | 
							
								                        server.append(80)
							 | 
						||
| 
								 | 
							
								                    elif req.scheme == "https":
							 | 
						||
| 
								 | 
							
								                        server.append(443)
							 | 
						||
| 
								 | 
							
								                    else:
							 | 
						||
| 
								 | 
							
								                        server.append('')
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                # no host header given which means that we are not behind a
							 | 
						||
| 
								 | 
							
								                # proxy, so append an empty port.
							 | 
						||
| 
								 | 
							
								                server.append('')
							 | 
						||
| 
								 | 
							
								    environ['SERVER_NAME'] = server[0]
							 | 
						||
| 
								 | 
							
								    environ['SERVER_PORT'] = str(server[1])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # set the path and script name
							 | 
						||
| 
								 | 
							
								    path_info = req.path
							 | 
						||
| 
								 | 
							
								    if script_name:
							 | 
						||
| 
								 | 
							
								        path_info = path_info.split(script_name, 1)[1]
							 | 
						||
| 
								 | 
							
								    environ['PATH_INFO'] = unquote_to_wsgi_str(path_info)
							 | 
						||
| 
								 | 
							
								    environ['SCRIPT_NAME'] = script_name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # override the environ with the correct remote and server address if
							 | 
						||
| 
								 | 
							
								    # we are behind a proxy using the proxy protocol.
							 | 
						||
| 
								 | 
							
								    environ.update(proxy_environ(req))
							 | 
						||
| 
								 | 
							
								    return resp, environ
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Response(object):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, req, sock, cfg):
							 | 
						||
| 
								 | 
							
								        self.req = req
							 | 
						||
| 
								 | 
							
								        self.sock = sock
							 | 
						||
| 
								 | 
							
								        self.version = SERVER_SOFTWARE
							 | 
						||
| 
								 | 
							
								        self.status = None
							 | 
						||
| 
								 | 
							
								        self.chunked = False
							 | 
						||
| 
								 | 
							
								        self.must_close = False
							 | 
						||
| 
								 | 
							
								        self.headers = []
							 | 
						||
| 
								 | 
							
								        self.headers_sent = False
							 | 
						||
| 
								 | 
							
								        self.response_length = None
							 | 
						||
| 
								 | 
							
								        self.sent = 0
							 | 
						||
| 
								 | 
							
								        self.upgrade = False
							 | 
						||
| 
								 | 
							
								        self.cfg = cfg
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def force_close(self):
							 | 
						||
| 
								 | 
							
								        self.must_close = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def should_close(self):
							 | 
						||
| 
								 | 
							
								        if self.must_close or self.req.should_close():
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								        if self.response_length is not None or self.chunked:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        if self.req.method == 'HEAD':
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        if self.status_code < 200 or self.status_code in (204, 304):
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def start_response(self, status, headers, exc_info=None):
							 | 
						||
| 
								 | 
							
								        if exc_info:
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                if self.status and self.headers_sent:
							 | 
						||
| 
								 | 
							
								                    reraise(exc_info[0], exc_info[1], exc_info[2])
							 | 
						||
| 
								 | 
							
								            finally:
							 | 
						||
| 
								 | 
							
								                exc_info = None
							 | 
						||
| 
								 | 
							
								        elif self.status is not None:
							 | 
						||
| 
								 | 
							
								            raise AssertionError("Response headers already set!")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.status = status
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # get the status code from the response here so we can use it to check
							 | 
						||
| 
								 | 
							
								        # the need for the connection header later without parsing the string
							 | 
						||
| 
								 | 
							
								        # each time.
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            self.status_code = int(self.status.split()[0])
							 | 
						||
| 
								 | 
							
								        except ValueError:
							 | 
						||
| 
								 | 
							
								            self.status_code = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.process_headers(headers)
							 | 
						||
| 
								 | 
							
								        self.chunked = self.is_chunked()
							 | 
						||
| 
								 | 
							
								        return self.write
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def process_headers(self, headers):
							 | 
						||
| 
								 | 
							
								        for name, value in headers:
							 | 
						||
| 
								 | 
							
								            if not isinstance(name, string_types):
							 | 
						||
| 
								 | 
							
								                raise TypeError('%r is not a string' % name)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if HEADER_RE.search(name):
							 | 
						||
| 
								 | 
							
								                raise InvalidHeaderName('%r' % name)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if HEADER_VALUE_RE.search(value):
							 | 
						||
| 
								 | 
							
								                raise InvalidHeader('%r' % value)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            value = str(value).strip()
							 | 
						||
| 
								 | 
							
								            lname = name.lower().strip()
							 | 
						||
| 
								 | 
							
								            if lname == "content-length":
							 | 
						||
| 
								 | 
							
								                self.response_length = int(value)
							 | 
						||
| 
								 | 
							
								            elif util.is_hoppish(name):
							 | 
						||
| 
								 | 
							
								                if lname == "connection":
							 | 
						||
| 
								 | 
							
								                    # handle websocket
							 | 
						||
| 
								 | 
							
								                    if value.lower().strip() == "upgrade":
							 | 
						||
| 
								 | 
							
								                        self.upgrade = True
							 | 
						||
| 
								 | 
							
								                elif lname == "upgrade":
							 | 
						||
| 
								 | 
							
								                    if value.lower().strip() == "websocket":
							 | 
						||
| 
								 | 
							
								                        self.headers.append((name.strip(), value))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                # ignore hopbyhop headers
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								            self.headers.append((name.strip(), value))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def is_chunked(self):
							 | 
						||
| 
								 | 
							
								        # Only use chunked responses when the client is
							 | 
						||
| 
								 | 
							
								        # speaking HTTP/1.1 or newer and there was
							 | 
						||
| 
								 | 
							
								        # no Content-Length header set.
							 | 
						||
| 
								 | 
							
								        if self.response_length is not None:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        elif self.req.version <= (1, 0):
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        elif self.req.method == 'HEAD':
							 | 
						||
| 
								 | 
							
								            # Responses to a HEAD request MUST NOT contain a response body.
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        elif self.status_code in (204, 304):
							 | 
						||
| 
								 | 
							
								            # Do not use chunked responses when the response is guaranteed to
							 | 
						||
| 
								 | 
							
								            # not have a response body.
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def default_headers(self):
							 | 
						||
| 
								 | 
							
								        # set the connection header
							 | 
						||
| 
								 | 
							
								        if self.upgrade:
							 | 
						||
| 
								 | 
							
								            connection = "upgrade"
							 | 
						||
| 
								 | 
							
								        elif self.should_close():
							 | 
						||
| 
								 | 
							
								            connection = "close"
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            connection = "keep-alive"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        headers = [
							 | 
						||
| 
								 | 
							
								            "HTTP/%s.%s %s\r\n" % (self.req.version[0],
							 | 
						||
| 
								 | 
							
								                self.req.version[1], self.status),
							 | 
						||
| 
								 | 
							
								            "Server: %s\r\n" % self.version,
							 | 
						||
| 
								 | 
							
								            "Date: %s\r\n" % util.http_date(),
							 | 
						||
| 
								 | 
							
								            "Connection: %s\r\n" % connection
							 | 
						||
| 
								 | 
							
								        ]
							 | 
						||
| 
								 | 
							
								        if self.chunked:
							 | 
						||
| 
								 | 
							
								            headers.append("Transfer-Encoding: chunked\r\n")
							 | 
						||
| 
								 | 
							
								        return headers
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def send_headers(self):
							 | 
						||
| 
								 | 
							
								        if self.headers_sent:
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								        tosend = self.default_headers()
							 | 
						||
| 
								 | 
							
								        tosend.extend(["%s: %s\r\n" % (k, v) for k, v in self.headers])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        header_str = "%s\r\n" % "".join(tosend)
							 | 
						||
| 
								 | 
							
								        util.write(self.sock, util.to_bytestring(header_str, "ascii"))
							 | 
						||
| 
								 | 
							
								        self.headers_sent = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def write(self, arg):
							 | 
						||
| 
								 | 
							
								        self.send_headers()
							 | 
						||
| 
								 | 
							
								        if not isinstance(arg, binary_type):
							 | 
						||
| 
								 | 
							
								            raise TypeError('%r is not a byte' % arg)
							 | 
						||
| 
								 | 
							
								        arglen = len(arg)
							 | 
						||
| 
								 | 
							
								        tosend = arglen
							 | 
						||
| 
								 | 
							
								        if self.response_length is not None:
							 | 
						||
| 
								 | 
							
								            if self.sent >= self.response_length:
							 | 
						||
| 
								 | 
							
								                # Never write more than self.response_length bytes
							 | 
						||
| 
								 | 
							
								                return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            tosend = min(self.response_length - self.sent, tosend)
							 | 
						||
| 
								 | 
							
								            if tosend < arglen:
							 | 
						||
| 
								 | 
							
								                arg = arg[:tosend]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Sending an empty chunk signals the end of the
							 | 
						||
| 
								 | 
							
								        # response and prematurely closes the response
							 | 
						||
| 
								 | 
							
								        if self.chunked and tosend == 0:
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.sent += tosend
							 | 
						||
| 
								 | 
							
								        util.write(self.sock, arg, self.chunked)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def can_sendfile(self):
							 | 
						||
| 
								 | 
							
								        return self.cfg.sendfile is not False and sendfile is not None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def sendfile(self, respiter):
							 | 
						||
| 
								 | 
							
								        if self.cfg.is_ssl or not self.can_sendfile():
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not util.has_fileno(respiter.filelike):
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        fileno = respiter.filelike.fileno()
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            offset = os.lseek(fileno, 0, os.SEEK_CUR)
							 | 
						||
| 
								 | 
							
								            if self.response_length is None:
							 | 
						||
| 
								 | 
							
								                filesize = os.fstat(fileno).st_size
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                # The file may be special and sendfile will fail.
							 | 
						||
| 
								 | 
							
								                # It may also be zero-length, but that is okay.
							 | 
						||
| 
								 | 
							
								                if filesize == 0:
							 | 
						||
| 
								 | 
							
								                    return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                nbytes = filesize - offset
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                nbytes = self.response_length
							 | 
						||
| 
								 | 
							
								        except (OSError, io.UnsupportedOperation):
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        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:
							 | 
						||
| 
								 | 
							
								            count = min(nbytes - sent, BLKSIZE)
							 | 
						||
| 
								 | 
							
								            sent += sendfile(sockno, fileno, offset + sent, count)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        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 not self.sendfile(respiter):
							 | 
						||
| 
								 | 
							
								            for item in respiter:
							 | 
						||
| 
								 | 
							
								                self.write(item)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def close(self):
							 | 
						||
| 
								 | 
							
								        if not self.headers_sent:
							 | 
						||
| 
								 | 
							
								            self.send_headers()
							 | 
						||
| 
								 | 
							
								        if self.chunked:
							 | 
						||
| 
								 | 
							
								            util.write_chunk(self.sock, b"")
							 |