Add python-eventlet 0.16.1
[packages/trusty/python-eventlet.git] / eventlet / eventlet / wsgi.py
1 import errno
2 import os
3 import sys
4 import time
5 import traceback
6 import types
7 import warnings
8
9 from eventlet.green import BaseHTTPServer
10 from eventlet.green import socket
11 from eventlet.green import urllib
12 from eventlet import greenio
13 from eventlet import greenpool
14 from eventlet import support
15 from eventlet.support import six
16
17
18 DEFAULT_MAX_SIMULTANEOUS_REQUESTS = 1024
19 DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1'
20 MAX_REQUEST_LINE = 8192
21 MAX_HEADER_LINE = 8192
22 MAX_TOTAL_HEADER_SIZE = 65536
23 MINIMUM_CHUNK_SIZE = 4096
24 # %(client_port)s is also available
25 DEFAULT_LOG_FORMAT = ('%(client_ip)s - - [%(date_time)s] "%(request_line)s"'
26                       ' %(status_code)s %(body_length)s %(wall_seconds).6f')
27 is_accepting = True
28
29 __all__ = ['server', 'format_date_time']
30
31 # Weekday and month names for HTTP date/time formatting; always English!
32 _weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
33 _monthname = [None,  # Dummy so we can use 1-based month numbers
34               "Jan", "Feb", "Mar", "Apr", "May", "Jun",
35               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
36
37
38 def format_date_time(timestamp):
39     """Formats a unix timestamp into an HTTP standard string."""
40     year, month, day, hh, mm, ss, wd, _y, _z = time.gmtime(timestamp)
41     return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
42         _weekdayname[wd], day, _monthname[month], year, hh, mm, ss
43     )
44
45
46 # Collections of error codes to compare against.  Not all attributes are set
47 # on errno module on all platforms, so some are literals :(
48 BAD_SOCK = set((errno.EBADF, 10053))
49 BROKEN_SOCK = set((errno.EPIPE, errno.ECONNRESET))
50
51
52 # special flag return value for apps
53 class _AlreadyHandled(object):
54
55     def __iter__(self):
56         return self
57
58     def next(self):
59         raise StopIteration
60
61     __next__ = next
62
63
64 ALREADY_HANDLED = _AlreadyHandled()
65
66
67 class Input(object):
68
69     def __init__(self,
70                  rfile,
71                  content_length,
72                  sock,
73                  wfile=None,
74                  wfile_line=None,
75                  chunked_input=False):
76
77         self.rfile = rfile
78         self._sock = sock
79         if content_length is not None:
80             content_length = int(content_length)
81         self.content_length = content_length
82
83         self.wfile = wfile
84         self.wfile_line = wfile_line
85
86         self.position = 0
87         self.chunked_input = chunked_input
88         self.chunk_length = -1
89
90         # (optional) headers to send with a "100 Continue" response. Set by
91         # calling set_hundred_continue_respose_headers() on env['wsgi.input']
92         self.hundred_continue_headers = None
93         self.is_hundred_continue_response_sent = False
94
95     def send_hundred_continue_response(self):
96         towrite = []
97
98         # 100 Continue status line
99         towrite.append(self.wfile_line)
100
101         # Optional headers
102         if self.hundred_continue_headers is not None:
103             # 100 Continue headers
104             for header in self.hundred_continue_headers:
105                 towrite.append(six.b('%s: %s\r\n' % header))
106
107         # Blank line
108         towrite.append(b'\r\n')
109
110         self.wfile.writelines(towrite)
111
112         # Reinitialize chunk_length (expect more data)
113         self.chunk_length = -1
114
115     def _do_read(self, reader, length=None):
116         if self.wfile is not None and \
117                 not self.is_hundred_continue_response_sent:
118             # 100 Continue response
119             self.send_hundred_continue_response()
120             self.is_hundred_continue_response_sent = True
121         if length is None and self.content_length is not None:
122             length = self.content_length - self.position
123         if length and length > self.content_length - self.position:
124             length = self.content_length - self.position
125         if not length:
126             return b''
127         try:
128             read = reader(length)
129         except greenio.SSL.ZeroReturnError:
130             read = b''
131         self.position += len(read)
132         return read
133
134     def _chunked_read(self, rfile, length=None, use_readline=False):
135         if self.wfile is not None and \
136                 not self.is_hundred_continue_response_sent:
137             # 100 Continue response
138             self.send_hundred_continue_response()
139             self.is_hundred_continue_response_sent = True
140         try:
141             if length == 0:
142                 return ""
143
144             if length and length < 0:
145                 length = None
146
147             if use_readline:
148                 reader = self.rfile.readline
149             else:
150                 reader = self.rfile.read
151
152             response = []
153             while self.chunk_length != 0:
154                 maxreadlen = self.chunk_length - self.position
155                 if length is not None and length < maxreadlen:
156                     maxreadlen = length
157
158                 if maxreadlen > 0:
159                     data = reader(maxreadlen)
160                     if not data:
161                         self.chunk_length = 0
162                         raise IOError("unexpected end of file while parsing chunked data")
163
164                     datalen = len(data)
165                     response.append(data)
166
167                     self.position += datalen
168                     if self.chunk_length == self.position:
169                         rfile.readline()
170
171                     if length is not None:
172                         length -= datalen
173                         if length == 0:
174                             break
175                     if use_readline and data[-1] == "\n":
176                         break
177                 else:
178                     self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16)
179                     self.position = 0
180                     if self.chunk_length == 0:
181                         rfile.readline()
182         except greenio.SSL.ZeroReturnError:
183             pass
184         return b''.join(response)
185
186     def read(self, length=None):
187         if self.chunked_input:
188             return self._chunked_read(self.rfile, length)
189         return self._do_read(self.rfile.read, length)
190
191     def readline(self, size=None):
192         if self.chunked_input:
193             return self._chunked_read(self.rfile, size, True)
194         else:
195             return self._do_read(self.rfile.readline, size)
196
197     def readlines(self, hint=None):
198         return self._do_read(self.rfile.readlines, hint)
199
200     def __iter__(self):
201         return iter(self.read, b'')
202
203     def get_socket(self):
204         return self._sock
205
206     def set_hundred_continue_response_headers(self, headers,
207                                               capitalize_response_headers=True):
208         # Response headers capitalization (default)
209         # CONTent-TYpe: TExt/PlaiN -> Content-Type: TExt/PlaiN
210         # Per HTTP RFC standard, header name is case-insensitive.
211         # Please, fix your client to ignore header case if possible.
212         if capitalize_response_headers:
213             headers = [
214                 ('-'.join([x.capitalize() for x in key.split('-')]), value)
215                 for key, value in headers]
216         self.hundred_continue_headers = headers
217
218
219 class HeaderLineTooLong(Exception):
220     pass
221
222
223 class HeadersTooLarge(Exception):
224     pass
225
226
227 class FileObjectForHeaders(object):
228
229     def __init__(self, fp):
230         self.fp = fp
231         self.total_header_size = 0
232
233     def readline(self, size=-1):
234         sz = size
235         if size < 0:
236             sz = MAX_HEADER_LINE
237         rv = self.fp.readline(sz)
238         if len(rv) >= MAX_HEADER_LINE:
239             raise HeaderLineTooLong()
240         self.total_header_size += len(rv)
241         if self.total_header_size > MAX_TOTAL_HEADER_SIZE:
242             raise HeadersTooLarge()
243         return rv
244
245
246 class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
247     protocol_version = 'HTTP/1.1'
248     minimum_chunk_size = MINIMUM_CHUNK_SIZE
249     capitalize_response_headers = True
250
251     def setup(self):
252         # overriding SocketServer.setup to correctly handle SSL.Connection objects
253         conn = self.connection = self.request
254         try:
255             self.rfile = conn.makefile('rb', self.rbufsize)
256             self.wfile = conn.makefile('wb', self.wbufsize)
257         except (AttributeError, NotImplementedError):
258             if hasattr(conn, 'send') and hasattr(conn, 'recv'):
259                 # it's an SSL.Connection
260                 self.rfile = socket._fileobject(conn, "rb", self.rbufsize)
261                 self.wfile = socket._fileobject(conn, "wb", self.wbufsize)
262             else:
263                 # it's a SSLObject, or a martian
264                 raise NotImplementedError("wsgi.py doesn't support sockets "
265                                           "of type %s" % type(conn))
266
267     def handle_one_request(self):
268         if self.server.max_http_version:
269             self.protocol_version = self.server.max_http_version
270
271         if self.rfile.closed:
272             self.close_connection = 1
273             return
274
275         try:
276             self.raw_requestline = self.rfile.readline(self.server.url_length_limit)
277             if len(self.raw_requestline) == self.server.url_length_limit:
278                 self.wfile.write(
279                     b"HTTP/1.0 414 Request URI Too Long\r\n"
280                     b"Connection: close\r\nContent-length: 0\r\n\r\n")
281                 self.close_connection = 1
282                 return
283         except greenio.SSL.ZeroReturnError:
284             self.raw_requestline = ''
285         except socket.error as e:
286             if support.get_errno(e) not in BAD_SOCK:
287                 raise
288             self.raw_requestline = ''
289
290         if not self.raw_requestline:
291             self.close_connection = 1
292             return
293
294         orig_rfile = self.rfile
295         try:
296             self.rfile = FileObjectForHeaders(self.rfile)
297             if not self.parse_request():
298                 return
299         except HeaderLineTooLong:
300             self.wfile.write(
301                 b"HTTP/1.0 400 Header Line Too Long\r\n"
302                 b"Connection: close\r\nContent-length: 0\r\n\r\n")
303             self.close_connection = 1
304             return
305         except HeadersTooLarge:
306             self.wfile.write(
307                 b"HTTP/1.0 400 Headers Too Large\r\n"
308                 b"Connection: close\r\nContent-length: 0\r\n\r\n")
309             self.close_connection = 1
310             return
311         finally:
312             self.rfile = orig_rfile
313
314         content_length = self.headers.get('content-length')
315         if content_length:
316             try:
317                 int(content_length)
318             except ValueError:
319                 self.wfile.write(
320                     b"HTTP/1.0 400 Bad Request\r\n"
321                     b"Connection: close\r\nContent-length: 0\r\n\r\n")
322                 self.close_connection = 1
323                 return
324
325         self.environ = self.get_environ()
326         self.application = self.server.app
327         try:
328             self.server.outstanding_requests += 1
329             try:
330                 self.handle_one_response()
331             except socket.error as e:
332                 # Broken pipe, connection reset by peer
333                 if support.get_errno(e) not in BROKEN_SOCK:
334                     raise
335         finally:
336             self.server.outstanding_requests -= 1
337
338     def handle_one_response(self):
339         start = time.time()
340         headers_set = []
341         headers_sent = []
342
343         wfile = self.wfile
344         result = None
345         use_chunked = [False]
346         length = [0]
347         status_code = [200]
348
349         def write(data, _writelines=wfile.writelines):
350             towrite = []
351             if not headers_set:
352                 raise AssertionError("write() before start_response()")
353             elif not headers_sent:
354                 status, response_headers = headers_set
355                 headers_sent.append(1)
356                 header_list = [header[0].lower() for header in response_headers]
357                 towrite.append(six.b('%s %s\r\n' % (self.protocol_version, status)))
358                 for header in response_headers:
359                     towrite.append(six.b('%s: %s\r\n' % header))
360
361                 # send Date header?
362                 if 'date' not in header_list:
363                     towrite.append(six.b('Date: %s\r\n' % (format_date_time(time.time()),)))
364
365                 client_conn = self.headers.get('Connection', '').lower()
366                 send_keep_alive = False
367                 if self.close_connection == 0 and \
368                    self.server.keepalive and (client_conn == 'keep-alive' or
369                                               (self.request_version == 'HTTP/1.1' and
370                                                not client_conn == 'close')):
371                         # only send keep-alives back to clients that sent them,
372                         # it's redundant for 1.1 connections
373                     send_keep_alive = (client_conn == 'keep-alive')
374                     self.close_connection = 0
375                 else:
376                     self.close_connection = 1
377
378                 if 'content-length' not in header_list:
379                     if self.request_version == 'HTTP/1.1':
380                         use_chunked[0] = True
381                         towrite.append(b'Transfer-Encoding: chunked\r\n')
382                     elif 'content-length' not in header_list:
383                         # client is 1.0 and therefore must read to EOF
384                         self.close_connection = 1
385
386                 if self.close_connection:
387                     towrite.append(b'Connection: close\r\n')
388                 elif send_keep_alive:
389                     towrite.append(b'Connection: keep-alive\r\n')
390                 towrite.append(b'\r\n')
391                 # end of header writing
392
393             if use_chunked[0]:
394                 # Write the chunked encoding
395                 towrite.append(six.b("%x" % (len(data),)) + b"\r\n" + data + b"\r\n")
396             else:
397                 towrite.append(data)
398             try:
399                 _writelines(towrite)
400                 length[0] = length[0] + sum(map(len, towrite))
401             except UnicodeEncodeError:
402                 self.server.log_message(
403                     "Encountered non-ascii unicode while attempting to write"
404                     "wsgi response: %r" %
405                     [x for x in towrite if isinstance(x, six.text_type)])
406                 self.server.log_message(traceback.format_exc())
407                 _writelines(
408                     ["HTTP/1.1 500 Internal Server Error\r\n",
409                      "Connection: close\r\n",
410                      "Content-type: text/plain\r\n",
411                      "Content-length: 98\r\n",
412                      "Date: %s\r\n" % format_date_time(time.time()),
413                      "\r\n",
414                      ("Internal Server Error: wsgi application passed "
415                       "a unicode object to the server instead of a string.")])
416
417         def start_response(status, response_headers, exc_info=None):
418             status_code[0] = status.split()[0]
419             if exc_info:
420                 try:
421                     if headers_sent:
422                         # Re-raise original exception if headers sent
423                         six.reraise(exc_info[0], exc_info[1], exc_info[2])
424                 finally:
425                     # Avoid dangling circular ref
426                     exc_info = None
427
428             # Response headers capitalization
429             # CONTent-TYpe: TExt/PlaiN -> Content-Type: TExt/PlaiN
430             # Per HTTP RFC standard, header name is case-insensitive.
431             # Please, fix your client to ignore header case if possible.
432             if self.capitalize_response_headers:
433                 response_headers = [
434                     ('-'.join([x.capitalize() for x in key.split('-')]), value)
435                     for key, value in response_headers]
436
437             headers_set[:] = [status, response_headers]
438             return write
439
440         try:
441             try:
442                 result = self.application(self.environ, start_response)
443                 if (isinstance(result, _AlreadyHandled)
444                         or isinstance(getattr(result, '_obj', None), _AlreadyHandled)):
445                     self.close_connection = 1
446                     return
447
448                 # Set content-length if possible
449                 if not headers_sent and hasattr(result, '__len__') and \
450                         'Content-Length' not in [h for h, _v in headers_set[1]]:
451                     headers_set[1].append(('Content-Length', str(sum(map(len, result)))))
452
453                 towrite = []
454                 towrite_size = 0
455                 just_written_size = 0
456                 minimum_write_chunk_size = int(self.environ.get(
457                     'eventlet.minimum_write_chunk_size', self.minimum_chunk_size))
458                 for data in result:
459                     towrite.append(data)
460                     towrite_size += len(data)
461                     if towrite_size >= minimum_write_chunk_size:
462                         write(b''.join(towrite))
463                         towrite = []
464                         just_written_size = towrite_size
465                         towrite_size = 0
466                 if towrite:
467                     just_written_size = towrite_size
468                     write(b''.join(towrite))
469                 if not headers_sent or (use_chunked[0] and just_written_size):
470                     write(b'')
471             except Exception:
472                 self.close_connection = 1
473                 tb = traceback.format_exc()
474                 self.server.log_message(tb)
475                 if not headers_set:
476                     err_body = six.b(tb) if self.server.debug else b''
477                     start_response("500 Internal Server Error",
478                                    [('Content-type', 'text/plain'),
479                                     ('Content-length', len(err_body))])
480                     write(err_body)
481         finally:
482             if hasattr(result, 'close'):
483                 result.close()
484             if (self.environ['eventlet.input'].chunked_input or
485                     self.environ['eventlet.input'].position
486                     < (self.environ['eventlet.input'].content_length or 0)):
487                 # Read and discard body if there was no pending 100-continue
488                 if not self.environ['eventlet.input'].wfile:
489                     # NOTE: MINIMUM_CHUNK_SIZE is used here for purpose different than chunking.
490                     # We use it only cause it's at hand and has reasonable value in terms of
491                     # emptying the buffer.
492                     while self.environ['eventlet.input'].read(MINIMUM_CHUNK_SIZE):
493                         pass
494             finish = time.time()
495
496             for hook, args, kwargs in self.environ['eventlet.posthooks']:
497                 hook(self.environ, *args, **kwargs)
498
499             if self.server.log_output:
500                 self.server.log_message(self.server.log_format % {
501                     'client_ip': self.get_client_ip(),
502                     'client_port': self.client_address[1],
503                     'date_time': self.log_date_time_string(),
504                     'request_line': self.requestline,
505                     'status_code': status_code[0],
506                     'body_length': length[0],
507                     'wall_seconds': finish - start,
508                 })
509
510     def get_client_ip(self):
511         client_ip = self.client_address[0]
512         if self.server.log_x_forwarded_for:
513             forward = self.headers.get('X-Forwarded-For', '').replace(' ', '')
514             if forward:
515                 client_ip = "%s,%s" % (forward, client_ip)
516         return client_ip
517
518     def get_environ(self):
519         env = self.server.get_environ()
520         env['REQUEST_METHOD'] = self.command
521         env['SCRIPT_NAME'] = ''
522
523         pq = self.path.split('?', 1)
524         env['RAW_PATH_INFO'] = pq[0]
525         env['PATH_INFO'] = urllib.unquote(pq[0])
526         if len(pq) > 1:
527             env['QUERY_STRING'] = pq[1]
528
529         ct = self.headers.get('content-type')
530         if ct is None:
531             try:
532                 ct = self.headers.type
533             except AttributeError:
534                 ct = self.headers.get_content_type()
535         env['CONTENT_TYPE'] = ct
536
537         length = self.headers.get('content-length')
538         if length:
539             env['CONTENT_LENGTH'] = length
540         env['SERVER_PROTOCOL'] = 'HTTP/1.0'
541
542         host, port = self.request.getsockname()[:2]
543         env['SERVER_NAME'] = host
544         env['SERVER_PORT'] = str(port)
545         env['REMOTE_ADDR'] = self.client_address[0]
546         env['REMOTE_PORT'] = str(self.client_address[1])
547         env['GATEWAY_INTERFACE'] = 'CGI/1.1'
548
549         try:
550             headers = self.headers.headers
551         except AttributeError:
552             headers = self.headers._headers
553         else:
554             headers = [h.split(':', 1) for h in headers]
555
556         for k, v in headers:
557             k = k.replace('-', '_').upper()
558             v = v.strip()
559             if k in env:
560                 continue
561             envk = 'HTTP_' + k
562             if envk in env:
563                 env[envk] += ',' + v
564             else:
565                 env[envk] = v
566
567         if env.get('HTTP_EXPECT') == '100-continue':
568             wfile = self.wfile
569             wfile_line = b'HTTP/1.1 100 Continue\r\n'
570         else:
571             wfile = None
572             wfile_line = None
573         chunked = env.get('HTTP_TRANSFER_ENCODING', '').lower() == 'chunked'
574         env['wsgi.input'] = env['eventlet.input'] = Input(
575             self.rfile, length, self.connection, wfile=wfile, wfile_line=wfile_line,
576             chunked_input=chunked)
577         env['eventlet.posthooks'] = []
578
579         return env
580
581     def finish(self):
582         try:
583             BaseHTTPServer.BaseHTTPRequestHandler.finish(self)
584         except socket.error as e:
585             # Broken pipe, connection reset by peer
586             if support.get_errno(e) not in BROKEN_SOCK:
587                 raise
588         greenio.shutdown_safe(self.connection)
589         self.connection.close()
590
591     def handle_expect_100(self):
592         return True
593
594
595 class Server(BaseHTTPServer.HTTPServer):
596
597     def __init__(self,
598                  socket,
599                  address,
600                  app,
601                  log=None,
602                  environ=None,
603                  max_http_version=None,
604                  protocol=HttpProtocol,
605                  minimum_chunk_size=None,
606                  log_x_forwarded_for=True,
607                  keepalive=True,
608                  log_output=True,
609                  log_format=DEFAULT_LOG_FORMAT,
610                  url_length_limit=MAX_REQUEST_LINE,
611                  debug=True,
612                  socket_timeout=None,
613                  capitalize_response_headers=True):
614
615         self.outstanding_requests = 0
616         self.socket = socket
617         self.address = address
618         if log:
619             self.log = log
620         else:
621             self.log = sys.stderr
622         self.app = app
623         self.keepalive = keepalive
624         self.environ = environ
625         self.max_http_version = max_http_version
626         self.protocol = protocol
627         self.pid = os.getpid()
628         self.minimum_chunk_size = minimum_chunk_size
629         self.log_x_forwarded_for = log_x_forwarded_for
630         self.log_output = log_output
631         self.log_format = log_format
632         self.url_length_limit = url_length_limit
633         self.debug = debug
634         self.socket_timeout = socket_timeout
635         self.capitalize_response_headers = capitalize_response_headers
636
637         if not self.capitalize_response_headers:
638             warnings.warn("""capitalize_response_headers is disabled.
639  Please, make sure you know what you are doing.
640  HTTP headers names are case-insensitive per RFC standard.
641  Most likely, you need to fix HTTP parsing in your client software.""",
642                           DeprecationWarning, stacklevel=3)
643
644     def get_environ(self):
645         d = {
646             'wsgi.errors': sys.stderr,
647             'wsgi.version': (1, 0),
648             'wsgi.multithread': True,
649             'wsgi.multiprocess': False,
650             'wsgi.run_once': False,
651             'wsgi.url_scheme': 'http',
652         }
653         # detect secure socket
654         if hasattr(self.socket, 'do_handshake'):
655             d['wsgi.url_scheme'] = 'https'
656             d['HTTPS'] = 'on'
657         if self.environ is not None:
658             d.update(self.environ)
659         return d
660
661     def process_request(self, sock_params):
662         # The actual request handling takes place in __init__, so we need to
663         # set minimum_chunk_size before __init__ executes and we don't want to modify
664         # class variable
665         sock, address = sock_params
666         proto = new(self.protocol)
667         if self.minimum_chunk_size is not None:
668             proto.minimum_chunk_size = self.minimum_chunk_size
669         proto.capitalize_response_headers = self.capitalize_response_headers
670         try:
671             proto.__init__(sock, address, self)
672         except socket.timeout:
673             # Expected exceptions are not exceptional
674             sock.close()
675             if self.debug:
676                 # similar to logging "accepted" in server()
677                 self.log_message('(%s) timed out %r' % (self.pid, address))
678
679     def log_message(self, message):
680         self.log.write(message + '\n')
681
682
683 try:
684     new = types.InstanceType
685 except AttributeError:
686     new = lambda cls: cls.__new__(cls)
687
688
689 try:
690     import ssl
691     ACCEPT_EXCEPTIONS = (socket.error, ssl.SSLError)
692     ACCEPT_ERRNO = set((errno.EPIPE, errno.EBADF, errno.ECONNRESET,
693                         ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_SSL))
694 except ImportError:
695     ACCEPT_EXCEPTIONS = (socket.error,)
696     ACCEPT_ERRNO = set((errno.EPIPE, errno.EBADF, errno.ECONNRESET))
697
698
699 def server(sock, site,
700            log=None,
701            environ=None,
702            max_size=None,
703            max_http_version=DEFAULT_MAX_HTTP_VERSION,
704            protocol=HttpProtocol,
705            server_event=None,
706            minimum_chunk_size=None,
707            log_x_forwarded_for=True,
708            custom_pool=None,
709            keepalive=True,
710            log_output=True,
711            log_format=DEFAULT_LOG_FORMAT,
712            url_length_limit=MAX_REQUEST_LINE,
713            debug=True,
714            socket_timeout=None,
715            capitalize_response_headers=True):
716     """Start up a WSGI server handling requests from the supplied server
717     socket.  This function loops forever.  The *sock* object will be
718     closed after server exits, but the underlying file descriptor will
719     remain open, so if you have a dup() of *sock*, it will remain usable.
720
721     .. warning::
722
723         At the moment :func:`server` will always wait for active connections to finish before
724         exiting, even if there's an exception raised inside it
725         (*all* exceptions are handled the same way, including :class:`greenlet.GreenletExit`
726         and those inheriting from `BaseException`).
727
728         While this may not be an issue normally, when it comes to long running HTTP connections
729         (like :mod:`eventlet.websocket`) it will become problematic and calling
730         :meth:`~eventlet.greenthread.GreenThread.wait` on a thread that runs the server may hang,
731         even after using :meth:`~eventlet.greenthread.GreenThread.kill`, as long
732         as there are active connections.
733
734     :param sock: Server socket, must be already bound to a port and listening.
735     :param site: WSGI application function.
736     :param log: File-like object that logs should be written to.
737                 If not specified, sys.stderr is used.
738     :param environ: Additional parameters that go into the environ dictionary of every request.
739     :param max_size: Maximum number of client connections opened at any time by this server.
740     :param max_http_version: Set to "HTTP/1.0" to make the server pretend it only supports HTTP 1.0.
741                 This can help with applications or clients that don't behave properly using HTTP 1.1.
742     :param protocol: Protocol class.  Deprecated.
743     :param server_event: Used to collect the Server object.  Deprecated.
744     :param minimum_chunk_size: Minimum size in bytes for http chunks.  This can be used to improve
745                 performance of applications which yield many small strings, though
746                 using it technically violates the WSGI spec. This can be overridden
747                 on a per request basis by setting environ['eventlet.minimum_write_chunk_size'].
748     :param log_x_forwarded_for: If True (the default), logs the contents of the x-forwarded-for
749                 header in addition to the actual client ip address in the 'client_ip' field of the
750                 log line.
751     :param custom_pool: A custom GreenPool instance which is used to spawn client green threads.
752                 If this is supplied, max_size is ignored.
753     :param keepalive: If set to False, disables keepalives on the server; all connections will be
754                 closed after serving one request.
755     :param log_output: A Boolean indicating if the server will log data or not.
756     :param log_format: A python format string that is used as the template to generate log lines.
757                 The following values can be formatted into it: client_ip, date_time, request_line,
758                 status_code, body_length, wall_seconds.  The default is a good example of how to
759                 use it.
760     :param url_length_limit: A maximum allowed length of the request url. If exceeded, 414 error
761                 is returned.
762     :param debug: True if the server should send exception tracebacks to the clients on 500 errors.
763                 If False, the server will respond with empty bodies.
764     :param socket_timeout: Timeout for client connections' socket operations. Default None means
765                 wait forever.
766     :param capitalize_response_headers: Normalize response headers' names to Foo-Bar.
767                 Default is True.
768     """
769     serv = Server(sock, sock.getsockname(),
770                   site, log,
771                   environ=environ,
772                   max_http_version=max_http_version,
773                   protocol=protocol,
774                   minimum_chunk_size=minimum_chunk_size,
775                   log_x_forwarded_for=log_x_forwarded_for,
776                   keepalive=keepalive,
777                   log_output=log_output,
778                   log_format=log_format,
779                   url_length_limit=url_length_limit,
780                   debug=debug,
781                   socket_timeout=socket_timeout,
782                   capitalize_response_headers=capitalize_response_headers,
783                   )
784     if server_event is not None:
785         server_event.send(serv)
786     if max_size is None:
787         max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS
788     if custom_pool is not None:
789         pool = custom_pool
790     else:
791         pool = greenpool.GreenPool(max_size)
792     try:
793         host, port = sock.getsockname()[:2]
794         port = ':%s' % (port, )
795         if hasattr(sock, 'do_handshake'):
796             scheme = 'https'
797             if port == ':443':
798                 port = ''
799         else:
800             scheme = 'http'
801             if port == ':80':
802                 port = ''
803
804         serv.log.write("(%s) wsgi starting up on %s://%s%s/\n" % (
805             serv.pid, scheme, host, port))
806         while is_accepting:
807             try:
808                 client_socket = sock.accept()
809                 client_socket[0].settimeout(serv.socket_timeout)
810                 if debug:
811                     serv.log.write("(%s) accepted %r\n" % (
812                         serv.pid, client_socket[1]))
813                 try:
814                     pool.spawn_n(serv.process_request, client_socket)
815                 except AttributeError:
816                     warnings.warn("wsgi's pool should be an instance of "
817                                   "eventlet.greenpool.GreenPool, is %s. Please convert your"
818                                   " call site to use GreenPool instead" % type(pool),
819                                   DeprecationWarning, stacklevel=2)
820                     pool.execute_async(serv.process_request, client_socket)
821             except ACCEPT_EXCEPTIONS as e:
822                 if support.get_errno(e) not in ACCEPT_ERRNO:
823                     raise
824             except (KeyboardInterrupt, SystemExit):
825                 serv.log.write("wsgi exiting\n")
826                 break
827     finally:
828         pool.waitall()
829         serv.log.write("(%s) wsgi exited, is_accepting=%s\n" % (
830             serv.pid, is_accepting))
831         try:
832             # NOTE: It's not clear whether we want this to leave the
833             # socket open or close it.  Use cases like Spawning want
834             # the underlying fd to remain open, but if we're going
835             # that far we might as well not bother closing sock at
836             # all.
837             sock.close()
838         except socket.error as e:
839             if support.get_errno(e) not in BROKEN_SOCK:
840                 traceback.print_exc()