Add python-eventlet package to MOS 9.0 repository
[packages/trusty/python-eventlet.git] / python-eventlet / eventlet / wsgi.py
index 72582770ae9edc015ea7580a5035eb7e5d1b591e..6af2b99c7ba47f609718a241ffd03c14b2766dd7 100644 (file)
@@ -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()