X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=python-eventlet%2Feventlet%2Fwsgi.py;h=6af2b99c7ba47f609718a241ffd03c14b2766dd7;hb=refs%2Fheads%2Fmaster;hp=72582770ae9edc015ea7580a5035eb7e5d1b591e;hpb=358bd9258c2b6d2ee74de4dfd07a5123107abad4;p=packages%2Ftrusty%2Fpython-eventlet.git diff --git a/python-eventlet/eventlet/wsgi.py b/python-eventlet/eventlet/wsgi.py index 7258277..6af2b99 100644 --- a/python-eventlet/eventlet/wsgi.py +++ b/python-eventlet/eventlet/wsgi.py @@ -1,4 +1,5 @@ import errno +import functools import os import sys import time @@ -6,13 +7,12 @@ import traceback import types import warnings -from eventlet.green import BaseHTTPServer -from eventlet.green import socket from eventlet import greenio from eventlet import greenpool from eventlet import support +from eventlet.green import BaseHTTPServer +from eventlet.green import socket from eventlet.support import six - from eventlet.support.six.moves import urllib @@ -50,6 +50,10 @@ BAD_SOCK = set((errno.EBADF, 10053)) BROKEN_SOCK = set((errno.EPIPE, errno.ECONNRESET)) +class ChunkReadError(ValueError): + pass + + # special flag return value for apps class _AlreadyHandled(object): @@ -109,19 +113,18 @@ class Input(object): towrite.append(b'\r\n') self.wfile.writelines(towrite) + self.wfile.flush() # Reinitialize chunk_length (expect more data) self.chunk_length = -1 def _do_read(self, reader, length=None): - if self.wfile is not None and \ - not self.is_hundred_continue_response_sent: + if self.wfile is not None and not self.is_hundred_continue_response_sent: # 100 Continue response self.send_hundred_continue_response() self.is_hundred_continue_response_sent = True - if length is None and self.content_length is not None: - length = self.content_length - self.position - if length and length > self.content_length - self.position: + if (self.content_length is not None) and ( + length is None or length > self.content_length - self.position): length = self.content_length - self.position if not length: return b'' @@ -133,8 +136,7 @@ class Input(object): return read def _chunked_read(self, rfile, length=None, use_readline=False): - if self.wfile is not None and \ - not self.is_hundred_continue_response_sent: + if self.wfile is not None and not self.is_hundred_continue_response_sent: # 100 Continue response self.send_hundred_continue_response() self.is_hundred_continue_response_sent = True @@ -176,7 +178,10 @@ class Input(object): if use_readline and data[-1] == "\n": break else: - self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16) + try: + self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16) + except ValueError as err: + raise ChunkReadError(err) self.position = 0 if self.chunk_length == 0: rfile.readline() @@ -216,6 +221,10 @@ class Input(object): for key, value in headers] self.hundred_continue_headers = headers + def discard(self, buffer_size=16 << 10): + while self.read(buffer_size): + pass + class HeaderLineTooLong(Exception): pass @@ -238,6 +247,9 @@ class LoggerFileWrapper(object): self.log = log self._debug = debug + def error(self, msg, *args, **kwargs): + self.write(msg, *args) + def info(self, msg, *args, **kwargs): self.write(msg, *args) @@ -276,9 +288,23 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): minimum_chunk_size = MINIMUM_CHUNK_SIZE capitalize_response_headers = True + # https://github.com/eventlet/eventlet/issues/295 + # Stdlib default is 0 (unbuffered), but then `wfile.writelines()` looses data + # so before going back to unbuffered, remove any usage of `writelines`. + wbufsize = 16 << 10 + def setup(self): # overriding SocketServer.setup to correctly handle SSL.Connection objects conn = self.connection = self.request + + # TCP_QUICKACK is a better alternative to disabling Nagle's algorithm + # https://news.ycombinator.com/item?id=10607422 + if getattr(socket, 'TCP_QUICKACK', None): + try: + conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, True) + except socket.error: + pass + try: self.rfile = conn.makefile('rb', self.rbufsize) self.wfile = conn.makefile('wb', self.wbufsize) @@ -374,7 +400,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): length = [0] status_code = [200] - def write(data, _writelines=wfile.writelines): + def write(data): towrite = [] if not headers_set: raise AssertionError("write() before start_response()") @@ -423,7 +449,8 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): towrite.append(six.b("%x" % (len(data),)) + b"\r\n" + data + b"\r\n") else: towrite.append(data) - _writelines(towrite) + wfile.writelines(towrite) + wfile.flush() length[0] = length[0] + sum(map(len, towrite)) def start_response(status, response_headers, exc_info=None): @@ -468,6 +495,8 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): minimum_write_chunk_size = int(self.environ.get( 'eventlet.minimum_write_chunk_size', self.minimum_chunk_size)) for data in result: + if len(data) == 0: + continue if isinstance(data, six.text_type): data = data.encode('ascii') @@ -496,16 +525,20 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): finally: if hasattr(result, 'close'): result.close() - if (self.environ['eventlet.input'].chunked_input or - self.environ['eventlet.input'].position - < (self.environ['eventlet.input'].content_length or 0)): + request_input = self.environ['eventlet.input'] + if (request_input.chunked_input or + request_input.position < (request_input.content_length or 0)): # Read and discard body if there was no pending 100-continue - if not self.environ['eventlet.input'].wfile: - # NOTE: MINIMUM_CHUNK_SIZE is used here for purpose different than chunking. - # We use it only cause it's at hand and has reasonable value in terms of - # emptying the buffer. - while self.environ['eventlet.input'].read(MINIMUM_CHUNK_SIZE): - pass + if not request_input.wfile and self.close_connection == 0: + try: + request_input.discard() + except ChunkReadError as e: + self.close_connection = 1 + self.server.log.error(( + 'chunked encoding error while discarding request body.' + + ' ip={0} request="{1}" error="{2}"').format( + self.get_client_ip(), self.requestline, e, + )) finish = time.time() for hook, args, kwargs in self.environ['eventlet.posthooks']: @@ -711,6 +744,24 @@ except ImportError: ACCEPT_ERRNO = set((errno.EPIPE, errno.EBADF, errno.ECONNRESET)) +def socket_repr(sock): + scheme = 'http' + if hasattr(sock, 'do_handshake'): + scheme = 'https' + + name = sock.getsockname() + if sock.family == socket.AF_INET: + hier_part = '//{0}:{1}'.format(*name) + elif sock.family == socket.AF_INET6: + hier_part = '//[{0}]:{1}'.format(*name[:2]) + elif sock.family == socket.AF_UNIX: + hier_part = name + else: + hier_part = repr(name) + + return scheme + ':' + hier_part + + def server(sock, site, log=None, environ=None, @@ -752,6 +803,7 @@ def server(sock, site, If not specified, sys.stderr is used. :param environ: Additional parameters that go into the environ dictionary of every request. :param max_size: Maximum number of client connections opened at any time by this server. + Default is 1024. :param max_http_version: Set to "HTTP/1.0" to make the server pretend it only supports HTTP 1.0. This can help with applications or clients that don't behave properly using HTTP 1.1. :param protocol: Protocol class. Deprecated. @@ -805,19 +857,8 @@ def server(sock, site, else: pool = greenpool.GreenPool(max_size) try: - host, port = sock.getsockname()[:2] - port = ':%s' % (port, ) - if hasattr(sock, 'do_handshake'): - scheme = 'https' - if port == ':443': - port = '' - else: - scheme = 'http' - if port == ':80': - port = '' - - serv.log.info("(%s) wsgi starting up on %s://%s%s/" % ( - serv.pid, scheme, host, port)) + serv.log.info("(%s) wsgi starting up on %s" % ( + serv.pid, socket_repr(sock))) while is_accepting: try: client_socket = sock.accept()