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