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