Fix PEP 333 compliance for the write callable.

This commit is contained in:
Paul J. Davis 2010-03-23 14:50:16 -04:00
parent e37bfa3c4e
commit 014d711bd7
4 changed files with 60 additions and 52 deletions

View File

@ -16,6 +16,7 @@ from urllib import unquote
from gunicorn import __version__ from gunicorn import __version__
from gunicorn.http.parser import Parser from gunicorn.http.parser import Parser
from gunicorn.http.response import Response
from gunicorn.http.tee import TeeInput from gunicorn.http.tee import TeeInput
from gunicorn.util import CHUNK_SIZE from gunicorn.util import CHUNK_SIZE
@ -43,7 +44,7 @@ class Request(object):
def __init__(self, socket, client_address, server_address, conf): def __init__(self, socket, client_address, server_address, conf):
self.debug = conf['debug'] self.debug = conf['debug']
self.conf = conf self.conf = conf
self._sock = socket self.socket = socket
self.client_address = client_address self.client_address = client_address
self.server_address = server_address self.server_address = server_address
@ -51,8 +52,8 @@ class Request(object):
self.response_headers = [] self.response_headers = []
self._version = 11 self._version = 11
self.parser = Parser.parse_request() self.parser = Parser.parse_request()
self.start_response_called = False
self.log = logging.getLogger(__name__) self.log = logging.getLogger(__name__)
self.response = None
self.response_chunked = False self.response_chunked = False
self.headers_sent = False self.headers_sent = False
@ -60,12 +61,12 @@ class Request(object):
environ = {} environ = {}
headers = [] headers = []
buf = StringIO() buf = StringIO()
data = self._sock.recv(CHUNK_SIZE) data = self.socket.recv(CHUNK_SIZE)
buf.write(data) buf.write(data)
buf2 = self.parser.filter_headers(headers, buf) buf2 = self.parser.filter_headers(headers, buf)
if not buf2: if not buf2:
while True: while True:
data = self._sock.recv(CHUNK_SIZE) data = self.socket.recv(CHUNK_SIZE)
if not data: if not data:
break break
buf.write(data) buf.write(data)
@ -77,14 +78,14 @@ class Request(object):
self.log.debug("Headers:\n%s" % headers) self.log.debug("Headers:\n%s" % headers)
if self.parser.headers_dict.get('Expect','').lower() == "100-continue": if self.parser.headers_dict.get('Expect','').lower() == "100-continue":
self._sock.send("HTTP/1.1 100 Continue\r\n\r\n") self.socket.send("HTTP/1.1 100 Continue\r\n\r\n")
if not self.parser.content_len and not self.parser.is_chunked: if not self.parser.content_len and not self.parser.is_chunked:
wsgi_input = TeeInput(self._sock, self.parser, StringIO(), wsgi_input = TeeInput(self.socket, self.parser, StringIO(),
self.conf) self.conf)
content_length = "0" content_length = "0"
else: else:
wsgi_input = TeeInput(self._sock, self.parser, buf2, self.conf) wsgi_input = TeeInput(self.socket, self.parser, buf2, self.conf)
content_length = str(wsgi_input.len) content_length = str(wsgi_input.len)
# This value should evaluate true if an equivalent application # This value should evaluate true if an equivalent application
@ -154,23 +155,15 @@ class Request(object):
environ[key] = value environ[key] = value
return environ return environ
def start_response(self, status, response_headers, exc_info=None): def start_response(self, status, headers, exc_info=None):
if exc_info: if exc_info:
try: try:
if self.headers_sent: if self.response and self.response.sent_headers:
raise exc_info[0], exc_info[1], exc_info[2] raise exc_info[0], exc_info[1], exc_info[2]
finally: finally:
exc_info = None exc_info = None
elif self.start_response_called: elif self.response is not None:
raise AssertionError("Response headers already set!") raise AssertionError("Response headers already set!")
self.response_status = status self.response = Response(self, status, headers)
for name, value in response_headers: return self.response.write
if name.lower() == "transfer-encoding":
if value.lower() == "chunked":
self.response_chunked = True
if not isinstance(value, basestring):
value = str(value)
self.response_headers.append((name.title(), value.strip()))
self.start_response_called = True

View File

@ -3,43 +3,46 @@
# This file is part of gunicorn released under the MIT license. # This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information. # See the NOTICE for more information.
from gunicorn.util import http_date, write, write_chunk from gunicorn.util import close, http_date, write, write_chunk, is_hoppish
class Response(object): class Response(object):
def __init__(self, sock, response, req): def __init__(self, req, status, headers):
self.req = req self.req = req
self._sock = sock self.version = req.SERVER_VERSION
self.data = response self.status = status
self.headers = req.response_headers or [] self.chunked = False
self.status = req.response_status self.headers = []
self.SERVER_VERSION = req.SERVER_VERSION self.headers_sent = False
self.chunked = req.response_chunked
def default_headers(self): for name, value in headers:
return [ assert isinstance(name, basestring), "%r is not a string" % name
"HTTP/1.1 %s\r\n" % self.status, assert isinstance(value, basestring), "%r is not a string" % value
"Server: %s\r\n" % self.SERVER_VERSION, assert not is_hoppish(name), "%s is a hop-by-hop header." % name
if name.lower().strip() == "transfer-encoding":
if value.lower().strip() == "chunked":
self.chunked = True
self.headers.append((name.strip(), value.strip()))
def send_headers(self):
if self.headers_sent:
return
tosend = [
"HTP/1.1 %s\r\n" % self.status,
"Server: %s\r\n" % self.version,
"Date: %s\r\n" % http_date(), "Date: %s\r\n" % http_date(),
"Connection: close\r\n" "Connection: close\r\n"
] ]
tosend.extend(["%s: %s\r\n" % (n, v) for n, v in self.headers])
write(self.req.socket, "%s\r\n" % "".join(tosend))
self.headers_sent = True
def send(self): def write(self, arg):
# send headers self.send_headers()
resp_head = self.default_headers() assert isinstance(arg, basestring), "%r is not a string." % arg
resp_head.extend(["%s: %s\r\n" % (n, v) for n, v in self.headers]) write(self.req.socket, arg, self.chunked)
write(self._sock, "%s\r\n" % "".join(resp_head))
self.req.headers_sent = True
def close(self):
last_chunk = None if self.chunked:
for chunk in self.data: write_chunk(self.socket, "")
last_chunk = chunk close(self.req.socket)
write(self._sock, chunk, self.chunked)
if self.chunked and last_chunk != "":
# send last chunk
write_chunk(self._sock, "")
if hasattr(self.data, "close"):
self.data.close()

View File

@ -30,6 +30,11 @@ monthname = [None,
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
hop_headers = set("""
connection keep-alive proxy-authenticate proxy-authorization
te trailers transfer-encoding upgrade
""".split())
try: try:
from setproctitle import setproctitle from setproctitle import setproctitle
def _setproctitle(title): def _setproctitle(title):
@ -202,3 +207,6 @@ def to_bytestring(s):
return s.encode('utf-8') return s.encode('utf-8')
else: else:
return s return s
def is_hoppish(header):
return header.lower().strip() in hop_headers

View File

@ -158,7 +158,12 @@ class Worker(object):
if not environ or not req.parser.status_line: if not environ or not req.parser.status_line:
return return
response = self.app(environ, req.start_response) respiter = self.app(environ, req.start_response)
for item in respiter:
req.response.write(item)
req.response.close()
if hasattr(respiter, "close"):
respiter.close()
except Exception, e: except Exception, e:
# Only send back traceback in HTTP in debug mode. # Only send back traceback in HTTP in debug mode.
if not self.debug: if not self.debug:
@ -166,7 +171,6 @@ class Worker(object):
util.write_error(client, traceback.format_exc()) util.write_error(client, traceback.format_exc())
return return
http.Response(client, response, req).send()
except socket.error, e: except socket.error, e:
if e[0] != errno.EPIPE: if e[0] != errno.EPIPE:
self.log.exception("Error processing request.") self.log.exception("Error processing request.")