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
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')
29 __all__ = ['server', 'format_date_time']
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"]
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
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))
52 # special flag return value for apps
53 class _AlreadyHandled(object):
64 ALREADY_HANDLED = _AlreadyHandled()
79 if content_length is not None:
80 content_length = int(content_length)
81 self.content_length = content_length
84 self.wfile_line = wfile_line
87 self.chunked_input = chunked_input
88 self.chunk_length = -1
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
95 def send_hundred_continue_response(self):
98 # 100 Continue status line
99 towrite.append(self.wfile_line)
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))
108 towrite.append(b'\r\n')
110 self.wfile.writelines(towrite)
112 # Reinitialize chunk_length (expect more data)
113 self.chunk_length = -1
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
128 read = reader(length)
129 except greenio.SSL.ZeroReturnError:
131 self.position += len(read)
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
144 if length and length < 0:
148 reader = self.rfile.readline
150 reader = self.rfile.read
153 while self.chunk_length != 0:
154 maxreadlen = self.chunk_length - self.position
155 if length is not None and length < maxreadlen:
159 data = reader(maxreadlen)
161 self.chunk_length = 0
162 raise IOError("unexpected end of file while parsing chunked data")
165 response.append(data)
167 self.position += datalen
168 if self.chunk_length == self.position:
171 if length is not None:
175 if use_readline and data[-1] == "\n":
178 self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16)
180 if self.chunk_length == 0:
182 except greenio.SSL.ZeroReturnError:
184 return b''.join(response)
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)
191 def readline(self, size=None):
192 if self.chunked_input:
193 return self._chunked_read(self.rfile, size, True)
195 return self._do_read(self.rfile.readline, size)
197 def readlines(self, hint=None):
198 return self._do_read(self.rfile.readlines, hint)
201 return iter(self.read, b'')
203 def get_socket(self):
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:
214 ('-'.join([x.capitalize() for x in key.split('-')]), value)
215 for key, value in headers]
216 self.hundred_continue_headers = headers
219 class HeaderLineTooLong(Exception):
223 class HeadersTooLarge(Exception):
227 class FileObjectForHeaders(object):
229 def __init__(self, fp):
231 self.total_header_size = 0
233 def readline(self, size=-1):
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()
246 class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
247 protocol_version = 'HTTP/1.1'
248 minimum_chunk_size = MINIMUM_CHUNK_SIZE
249 capitalize_response_headers = True
252 # overriding SocketServer.setup to correctly handle SSL.Connection objects
253 conn = self.connection = self.request
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)
263 # it's a SSLObject, or a martian
264 raise NotImplementedError("wsgi.py doesn't support sockets "
265 "of type %s" % type(conn))
267 def handle_one_request(self):
268 if self.server.max_http_version:
269 self.protocol_version = self.server.max_http_version
271 if self.rfile.closed:
272 self.close_connection = 1
276 self.raw_requestline = self.rfile.readline(self.server.url_length_limit)
277 if len(self.raw_requestline) == self.server.url_length_limit:
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
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:
288 self.raw_requestline = ''
290 if not self.raw_requestline:
291 self.close_connection = 1
294 orig_rfile = self.rfile
296 self.rfile = FileObjectForHeaders(self.rfile)
297 if not self.parse_request():
299 except HeaderLineTooLong:
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
305 except HeadersTooLarge:
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
312 self.rfile = orig_rfile
314 content_length = self.headers.get('content-length')
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
325 self.environ = self.get_environ()
326 self.application = self.server.app
328 self.server.outstanding_requests += 1
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:
336 self.server.outstanding_requests -= 1
338 def handle_one_response(self):
345 use_chunked = [False]
349 def write(data, _writelines=wfile.writelines):
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))
362 if 'date' not in header_list:
363 towrite.append(six.b('Date: %s\r\n' % (format_date_time(time.time()),)))
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
376 self.close_connection = 1
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
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
394 # Write the chunked encoding
395 towrite.append(six.b("%x" % (len(data),)) + b"\r\n" + data + b"\r\n")
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())
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()),
414 ("Internal Server Error: wsgi application passed "
415 "a unicode object to the server instead of a string.")])
417 def start_response(status, response_headers, exc_info=None):
418 status_code[0] = status.split()[0]
422 # Re-raise original exception if headers sent
423 six.reraise(exc_info[0], exc_info[1], exc_info[2])
425 # Avoid dangling circular ref
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:
434 ('-'.join([x.capitalize() for x in key.split('-')]), value)
435 for key, value in response_headers]
437 headers_set[:] = [status, response_headers]
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
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)))))
455 just_written_size = 0
456 minimum_write_chunk_size = int(self.environ.get(
457 'eventlet.minimum_write_chunk_size', self.minimum_chunk_size))
460 towrite_size += len(data)
461 if towrite_size >= minimum_write_chunk_size:
462 write(b''.join(towrite))
464 just_written_size = towrite_size
467 just_written_size = towrite_size
468 write(b''.join(towrite))
469 if not headers_sent or (use_chunked[0] and just_written_size):
472 self.close_connection = 1
473 tb = traceback.format_exc()
474 self.server.log_message(tb)
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))])
482 if hasattr(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):
496 for hook, args, kwargs in self.environ['eventlet.posthooks']:
497 hook(self.environ, *args, **kwargs)
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,
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(' ', '')
515 client_ip = "%s,%s" % (forward, client_ip)
518 def get_environ(self):
519 env = self.server.get_environ()
520 env['REQUEST_METHOD'] = self.command
521 env['SCRIPT_NAME'] = ''
523 pq = self.path.split('?', 1)
524 env['RAW_PATH_INFO'] = pq[0]
525 env['PATH_INFO'] = urllib.unquote(pq[0])
527 env['QUERY_STRING'] = pq[1]
529 ct = self.headers.get('content-type')
532 ct = self.headers.type
533 except AttributeError:
534 ct = self.headers.get_content_type()
535 env['CONTENT_TYPE'] = ct
537 length = self.headers.get('content-length')
539 env['CONTENT_LENGTH'] = length
540 env['SERVER_PROTOCOL'] = 'HTTP/1.0'
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'
550 headers = self.headers.headers
551 except AttributeError:
552 headers = self.headers._headers
554 headers = [h.split(':', 1) for h in headers]
557 k = k.replace('-', '_').upper()
567 if env.get('HTTP_EXPECT') == '100-continue':
569 wfile_line = b'HTTP/1.1 100 Continue\r\n'
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'] = []
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:
588 greenio.shutdown_safe(self.connection)
589 self.connection.close()
591 def handle_expect_100(self):
595 class Server(BaseHTTPServer.HTTPServer):
603 max_http_version=None,
604 protocol=HttpProtocol,
605 minimum_chunk_size=None,
606 log_x_forwarded_for=True,
609 log_format=DEFAULT_LOG_FORMAT,
610 url_length_limit=MAX_REQUEST_LINE,
613 capitalize_response_headers=True):
615 self.outstanding_requests = 0
617 self.address = address
621 self.log = sys.stderr
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
634 self.socket_timeout = socket_timeout
635 self.capitalize_response_headers = capitalize_response_headers
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)
644 def get_environ(self):
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',
653 # detect secure socket
654 if hasattr(self.socket, 'do_handshake'):
655 d['wsgi.url_scheme'] = 'https'
657 if self.environ is not None:
658 d.update(self.environ)
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
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
671 proto.__init__(sock, address, self)
672 except socket.timeout:
673 # Expected exceptions are not exceptional
676 # similar to logging "accepted" in server()
677 self.log_message('(%s) timed out %r' % (self.pid, address))
679 def log_message(self, message):
680 self.log.write(message + '\n')
684 new = types.InstanceType
685 except AttributeError:
686 new = lambda cls: cls.__new__(cls)
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))
695 ACCEPT_EXCEPTIONS = (socket.error,)
696 ACCEPT_ERRNO = set((errno.EPIPE, errno.EBADF, errno.ECONNRESET))
699 def server(sock, site,
703 max_http_version=DEFAULT_MAX_HTTP_VERSION,
704 protocol=HttpProtocol,
706 minimum_chunk_size=None,
707 log_x_forwarded_for=True,
711 log_format=DEFAULT_LOG_FORMAT,
712 url_length_limit=MAX_REQUEST_LINE,
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.
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`).
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.
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
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
760 :param url_length_limit: A maximum allowed length of the request url. If exceeded, 414 error
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
766 :param capitalize_response_headers: Normalize response headers' names to Foo-Bar.
769 serv = Server(sock, sock.getsockname(),
772 max_http_version=max_http_version,
774 minimum_chunk_size=minimum_chunk_size,
775 log_x_forwarded_for=log_x_forwarded_for,
777 log_output=log_output,
778 log_format=log_format,
779 url_length_limit=url_length_limit,
781 socket_timeout=socket_timeout,
782 capitalize_response_headers=capitalize_response_headers,
784 if server_event is not None:
785 server_event.send(serv)
787 max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS
788 if custom_pool is not None:
791 pool = greenpool.GreenPool(max_size)
793 host, port = sock.getsockname()[:2]
794 port = ':%s' % (port, )
795 if hasattr(sock, 'do_handshake'):
804 serv.log.write("(%s) wsgi starting up on %s://%s%s/\n" % (
805 serv.pid, scheme, host, port))
808 client_socket = sock.accept()
809 client_socket[0].settimeout(serv.socket_timeout)
811 serv.log.write("(%s) accepted %r\n" % (
812 serv.pid, client_socket[1]))
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:
824 except (KeyboardInterrupt, SystemExit):
825 serv.log.write("wsgi exiting\n")
829 serv.log.write("(%s) wsgi exited, is_accepting=%s\n" % (
830 serv.pid, is_accepting))
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
838 except socket.error as e:
839 if support.get_errno(e) not in BROKEN_SOCK:
840 traceback.print_exc()