import errno
+import functools
import os
import sys
import time
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
BROKEN_SOCK = set((errno.EPIPE, errno.ECONNRESET))
+class ChunkReadError(ValueError):
+ pass
+
+
# special flag return value for apps
class _AlreadyHandled(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''
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
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()
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
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)
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)
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()")
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):
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')
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']:
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,
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.
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()