diff options
Diffstat (limited to 'python/gevent/pywsgi.py')
-rw-r--r-- | python/gevent/pywsgi.py | 102 |
1 files changed, 69 insertions, 33 deletions
diff --git a/python/gevent/pywsgi.py b/python/gevent/pywsgi.py index 2726f6d..8d0b5b8 100644 --- a/python/gevent/pywsgi.py +++ b/python/gevent/pywsgi.py @@ -1,5 +1,5 @@ # Copyright (c) 2005-2009, eventlet contributors -# Copyright (c) 2009-2015, gevent contributors +# Copyright (c) 2009-2018, gevent contributors """ A pure-Python, gevent-friendly WSGI server. @@ -9,6 +9,8 @@ created for each request. The server can be customized to use different subclasses of :class:`WSGIHandler`. """ +from __future__ import absolute_import + # FIXME: Can we refactor to make smallor? # pylint:disable=too-many-lines @@ -517,20 +519,20 @@ class WSGIHandler(object): if len(words) == 3: self.command, self.path, self.request_version = words if not self._check_http_version(): - raise _InvalidClientRequest('Invalid http version: %r', raw_requestline) + raise _InvalidClientRequest('Invalid http version: %r' % (raw_requestline,)) elif len(words) == 2: self.command, self.path = words if self.command != "GET": - raise _InvalidClientRequest('Expected GET method: %r', raw_requestline) + raise _InvalidClientRequest('Expected GET method: %r' % (raw_requestline,)) self.request_version = "HTTP/0.9" # QQQ I'm pretty sure we can drop support for HTTP/0.9 else: - raise _InvalidClientRequest('Invalid HTTP method: %r', raw_requestline) + raise _InvalidClientRequest('Invalid HTTP method: %r' % (raw_requestline,)) self.headers = self.MessageClass(self.rfile, 0) if self.headers.status: - raise _InvalidClientRequest('Invalid headers status: %r', self.headers.status) + raise _InvalidClientRequest('Invalid headers status: %r' % (self.headers.status,)) if self.headers.get("transfer-encoding", "").lower() == "chunked": try: @@ -542,7 +544,7 @@ class WSGIHandler(object): if content_length is not None: content_length = int(content_length) if content_length < 0: - raise _InvalidClientRequest('Invalid Content-Length: %r', content_length) + raise _InvalidClientRequest('Invalid Content-Length: %r' % (content_length,)) if content_length and self.command in ('HEAD', ): raise _InvalidClientRequest('Unexpected Content-Length') @@ -706,7 +708,9 @@ class WSGIHandler(object): raise self.response_length += len(data) - def _write(self, data): + def _write(self, data, + _PY34_EXACTLY=(sys.version_info[:2] == (3, 4)), + _bytearray=bytearray): if not data: # The application/middleware are allowed to yield # empty bytestrings. @@ -714,14 +718,25 @@ class WSGIHandler(object): if self.response_use_chunked: ## Write the chunked encoding - header = ("%x\r\n" % len(data)).encode('ascii') - # socket.sendall will slice these small strings, as [0:], - # but that's special cased to return the original string. - # They're small enough we probably expect them to go down to the network - # buffers in one go anyway. - self._sendall(header) - self._sendall(data) - self._sendall(b'\r\n') # trailer + # header + if _PY34_EXACTLY: + # This is the only version we support that doesn't + # allow % to be used with bytes. Passing a bytestring + # directly in to bytearray() is faster than passing a + # (unicode) str with encoding, which naturally is faster still + # than encoding first. Interestingly, byte formatting on Python 3 + # is faster than str formatting. + header_str = '%x\r\n' % len(data) + towrite = _bytearray(header_str, 'ascii') + else: + header_str = b'%x\r\n' % len(data) + towrite = _bytearray(header_str) + + # data + towrite += data + # trailer + towrite += b'\r\n' + self._sendall(towrite) else: self._sendall(data) @@ -741,21 +756,20 @@ class WSGIHandler(object): self._write_with_headers(data) def _write_with_headers(self, data): - towrite = bytearray() self.headers_sent = True self.finalize_headers() # self.response_headers and self.status are already in latin-1, as encoded by self.start_response - towrite.extend(b'HTTP/1.1 ') - towrite.extend(self.status) - towrite.extend(b'\r\n') + towrite = bytearray(b'HTTP/1.1 ') + towrite += self.status + towrite += b'\r\n' for header, value in self.response_headers: - towrite.extend(header) - towrite.extend(b': ') - towrite.extend(value) - towrite.extend(b"\r\n") + towrite += header + towrite += b': ' + towrite += value + towrite += b"\r\n" - towrite.extend(b'\r\n') + towrite += b'\r\n' self._sendall(towrite) # No need to copy the data into towrite; we may make an extra syscall # but the copy time could be substantial too, and it reduces the chances @@ -788,7 +802,7 @@ class WSGIHandler(object): # "Servers should check for errors in the headers at the time # start_response is called, so that an error can be raised # while the application is still running." Here, we check the encoding. - # This aids debugging: headers especially are generated programatically + # This aids debugging: headers especially are generated programmatically # and an encoding error in a loop or list comprehension yields an opaque # UnicodeError without any clue which header was wrong. # Note that this results in copying the header list at this point, not modifying it, @@ -899,8 +913,8 @@ class WSGIHandler(object): # Trigger the flush of the headers. self.write(b'') if self.response_use_chunked: - self.socket.sendall(b'0\r\n\r\n') - self.response_length += 5 + self._sendall(b'0\r\n\r\n') + def run_application(self): assert self.result is None @@ -921,7 +935,33 @@ class WSGIHandler(object): close = None self.result = None + #: These errors are silently ignored by :meth:`handle_one_response` to avoid producing + #: excess log entries on normal operating conditions. They indicate + #: a remote client has disconnected and there is little or nothing + #: this process can be expected to do about it. You may change this + #: value in a subclass. + #: + #: The default value includes :data:`errno.EPIPE` and :data:`errno.ECONNRESET`. + #: On Windows this also includes :data:`errno.WSAECONNABORTED`. + #: + #: This is a provisional API, subject to change. See :pr:`377`, :pr:`999` + #: and :issue:`136`. + #: + #: .. versionadded:: 1.3 + ignored_socket_errors = (errno.EPIPE, errno.ECONNRESET) + try: + ignored_socket_errors += (errno.WSAECONNABORTED,) + except AttributeError: + pass # Not windows + def handle_one_response(self): + """ + Invoke the application to produce one response. + + This is called by :meth:`handle_one_request` after all the + state for the request has been established. It is responsible + for error handling. + """ self.time_start = time.time() self.status = None self.headers_sent = False @@ -946,12 +986,8 @@ class WSGIHandler(object): except _InvalidClientInput: self._send_error_response_if_possible(400) except socket.error as ex: - if ex.args[0] in (errno.EPIPE, errno.ECONNRESET): - # Broken pipe, connection reset by peer. - # Swallow these silently to avoid spewing - # useless info on normal operating conditions, - # bloating logfiles. See https://github.com/gevent/gevent/pull/377 - # and https://github.com/gevent/gevent/issues/136. + if ex.args[0] in self.ignored_socket_errors: + # See description of self.ignored_socket_errors. if not PY3: sys.exc_clear() self.close_connection = True |