From 014d711bd78cad1e752cd07bb59ad9bf8a7f9a40 Mon Sep 17 00:00:00 2001 From: "Paul J. Davis" Date: Tue, 23 Mar 2010 14:50:16 -0400 Subject: [PATCH] Fix PEP 333 compliance for the write callable. --- gunicorn/http/request.py | 33 ++++++++------------ gunicorn/http/response.py | 63 ++++++++++++++++++++------------------- gunicorn/util.py | 8 +++++ gunicorn/worker.py | 8 +++-- 4 files changed, 60 insertions(+), 52 deletions(-) diff --git a/gunicorn/http/request.py b/gunicorn/http/request.py index 3bdfbb10..921ae314 100644 --- a/gunicorn/http/request.py +++ b/gunicorn/http/request.py @@ -16,6 +16,7 @@ from urllib import unquote from gunicorn import __version__ from gunicorn.http.parser import Parser +from gunicorn.http.response import Response from gunicorn.http.tee import TeeInput from gunicorn.util import CHUNK_SIZE @@ -43,7 +44,7 @@ class Request(object): def __init__(self, socket, client_address, server_address, conf): self.debug = conf['debug'] self.conf = conf - self._sock = socket + self.socket = socket self.client_address = client_address self.server_address = server_address @@ -51,8 +52,8 @@ class Request(object): self.response_headers = [] self._version = 11 self.parser = Parser.parse_request() - self.start_response_called = False self.log = logging.getLogger(__name__) + self.response = None self.response_chunked = False self.headers_sent = False @@ -60,12 +61,12 @@ class Request(object): environ = {} headers = [] buf = StringIO() - data = self._sock.recv(CHUNK_SIZE) + data = self.socket.recv(CHUNK_SIZE) buf.write(data) buf2 = self.parser.filter_headers(headers, buf) if not buf2: while True: - data = self._sock.recv(CHUNK_SIZE) + data = self.socket.recv(CHUNK_SIZE) if not data: break buf.write(data) @@ -77,14 +78,14 @@ class Request(object): self.log.debug("Headers:\n%s" % headers) 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: - wsgi_input = TeeInput(self._sock, self.parser, StringIO(), + wsgi_input = TeeInput(self.socket, self.parser, StringIO(), self.conf) content_length = "0" 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) # This value should evaluate true if an equivalent application @@ -154,23 +155,15 @@ class Request(object): environ[key] = value return environ - def start_response(self, status, response_headers, exc_info=None): + def start_response(self, status, headers, exc_info=None): if exc_info: try: - if self.headers_sent: + if self.response and self.response.sent_headers: raise exc_info[0], exc_info[1], exc_info[2] finally: exc_info = None - elif self.start_response_called: + elif self.response is not None: raise AssertionError("Response headers already set!") - self.response_status = status - for name, value in response_headers: - 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 + self.response = Response(self, status, headers) + return self.response.write diff --git a/gunicorn/http/response.py b/gunicorn/http/response.py index 551b4bfa..ecb907c5 100644 --- a/gunicorn/http/response.py +++ b/gunicorn/http/response.py @@ -3,43 +3,46 @@ # This file is part of gunicorn released under the MIT license. # 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): - def __init__(self, sock, response, req): + def __init__(self, req, status, headers): self.req = req - self._sock = sock - self.data = response - self.headers = req.response_headers or [] - self.status = req.response_status - self.SERVER_VERSION = req.SERVER_VERSION - self.chunked = req.response_chunked + self.version = req.SERVER_VERSION + self.status = status + self.chunked = False + self.headers = [] + self.headers_sent = False - def default_headers(self): - return [ - "HTTP/1.1 %s\r\n" % self.status, - "Server: %s\r\n" % self.SERVER_VERSION, + for name, value in headers: + assert isinstance(name, basestring), "%r is not a string" % name + assert isinstance(value, basestring), "%r is not a string" % value + 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(), "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): - # send headers - resp_head = self.default_headers() - resp_head.extend(["%s: %s\r\n" % (n, v) for n, v in self.headers]) - write(self._sock, "%s\r\n" % "".join(resp_head)) - self.req.headers_sent = True - + def write(self, arg): + self.send_headers() + assert isinstance(arg, basestring), "%r is not a string." % arg + write(self.req.socket, arg, self.chunked) - last_chunk = None - for chunk in self.data: - last_chunk = chunk - 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() + def close(self): + if self.chunked: + write_chunk(self.socket, "") + close(self.req.socket) diff --git a/gunicorn/util.py b/gunicorn/util.py index 3ce59ed2..a066ffc3 100644 --- a/gunicorn/util.py +++ b/gunicorn/util.py @@ -29,6 +29,11 @@ weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + +hop_headers = set(""" + connection keep-alive proxy-authenticate proxy-authorization + te trailers transfer-encoding upgrade + """.split()) try: from setproctitle import setproctitle @@ -202,3 +207,6 @@ def to_bytestring(s): return s.encode('utf-8') else: return s + +def is_hoppish(header): + return header.lower().strip() in hop_headers \ No newline at end of file diff --git a/gunicorn/worker.py b/gunicorn/worker.py index 54e364a8..8b2202cc 100644 --- a/gunicorn/worker.py +++ b/gunicorn/worker.py @@ -158,7 +158,12 @@ class Worker(object): if not environ or not req.parser.status_line: 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: # Only send back traceback in HTTP in debug mode. if not self.debug: @@ -166,7 +171,6 @@ class Worker(object): util.write_error(client, traceback.format_exc()) return - http.Response(client, response, req).send() except socket.error, e: if e[0] != errno.EPIPE: self.log.exception("Error processing request.")