Changeset 1573
- Timestamp:
- 12/28/06 14:10:07
- Files:
-
- branches/cherrypy-2.x/cherrypy/_cpwsgiserver3.py (modified) (30 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/cherrypy-2.x/cherrypy/_cpwsgiserver3.py
r1520 r1573 1 """A high-speed, production ready, thread pooled, generic WSGI server.""" 1 """A high-speed, production ready, thread pooled, generic WSGI server. 2 3 Simplest example on how to use this module directly 4 (without using CherryPy's application machinery): 5 6 from cherrypy import wsgiserver 7 8 def my_crazy_app(environ, start_response): 9 status = '200 OK' 10 response_headers = [('Content-type','text/plain')] 11 start_response(status, response_headers) 12 return ['Hello world!\n'] 13 14 # Here we set our application to the script_name '/' 15 wsgi_apps = [('/', my_crazy_app)] 16 17 server = wsgiserver.CherryPyWSGIServer(('localhost', 8070), wsgi_apps, 18 server_name='localhost') 19 20 # Want SSL support? Just set these attributes 21 # server.ssl_certificate = <filename> 22 # server.ssl_private_key = <filename> 23 24 if __name__ == '__main__': 25 server.start() 26 27 This won't call the CherryPy engine (application side) at all, only the 28 WSGI server, which is independant from the rest of CherryPy. Don't 29 let the name "CherryPyWSGIServer" throw you; the name merely reflects 30 its origin, not it's coupling. 31 32 The CherryPy WSGI server can serve as many WSGI application 33 as you want in one instance: 34 35 wsgi_apps = [('/', my_crazy_app), (/blog', my_blog_app)] 36 37 """ 38 2 39 3 40 import base64 4 import mimetools # todo: use email5 41 import Queue 6 42 import os … … 39 75 socket_errors_to_ignore.append("timed out") 40 76 41 # These are lowercase because mimetools.Message uses lowercase keys. 42 comma_separated_headers = [ 43 'accept', 'accept-charset', 'accept-encoding', 'accept-language', 44 'accept-ranges', 'allow', 'cache-control', 'connection', 'content-encoding', 45 'content-language', 'expect', 'if-match', 'if-none-match', 'pragma', 46 'proxy-authenticate', 'te', 'trailer', 'transfer-encoding', 'upgrade', 47 'vary', 'via', 'warning', 'www-authenticate', 48 ] 49 77 comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING', 78 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL', 79 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT', 80 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE', 81 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING', 82 'WWW-AUTHENTICATE'] 50 83 51 84 class HTTPRequest(object): 85 """An HTTP Request (and response). 86 87 A single HTTP connection may consist of multiple request/response pairs. 88 89 connection: the HTTP Connection object which spawned this request. 90 rfile: the 'read' fileobject from the connection's socket 91 ready: when True, the request has been parsed and is ready to begin 92 generating the response. When False, signals the calling Connection 93 that the response should not be generated and the connection should 94 close. 95 close_connection: signals the calling Connection that the request 96 should close. This does not imply an error! The client and/or 97 server may each request that the connection be closed. 98 chunked_write: if True, output will be encoded with the "chunked" 99 transfer-coding. This value is set automatically inside 100 send_headers. 101 """ 52 102 53 103 def __init__(self, connection): 54 104 self.connection = connection 55 105 self.rfile = self.connection.rfile 106 self.sendall = self.connection.sendall 56 107 self.environ = connection.environ.copy() 57 108 … … 65 116 66 117 def parse_request(self): 118 """Parse the next HTTP request start-line and message-headers.""" 67 119 # HTTP/1.1 connections are persistent by default. If a client 68 120 # requests a page, then idles (leaves the connection open), … … 78 130 return 79 131 132 if request_line == "\r\n": 133 # RFC 2616 sec 4.1: "...if the server is reading the protocol 134 # stream at the beginning of a message and receives a CRLF 135 # first, it should ignore the CRLF." 136 # But only ignore one leading line! else we enable a DoS. 137 request_line = self.rfile.readline() 138 if not request_line: 139 self.ready = False 140 return 141 80 142 server = self.connection.server 81 self.environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version 143 environ = self.environ 144 environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version 82 145 83 146 method, path, req_protocol = request_line.strip().split(" ", 2) 84 self.environ["REQUEST_METHOD"] = method147 environ["REQUEST_METHOD"] = method 85 148 86 149 # path may be an abs_path (including "http://host.domain.tld"); … … 93 156 94 157 if scheme: 95 self.environ["wsgi.url_scheme"] = scheme158 environ["wsgi.url_scheme"] = scheme 96 159 if params: 97 160 path = path + ";" + params … … 109 172 # This means, of course, that the last wsgi_app (shortest path) 110 173 # will always handle a URI of "*". 111 self.environ["SCRIPT_NAME"] = ""112 self.environ["PATH_INFO"] = "*"174 environ["SCRIPT_NAME"] = "" 175 environ["PATH_INFO"] = "*" 113 176 self.wsgi_app = server.mount_points[-1][1] 114 177 else: … … 116 179 # The mount_points list should be sorted by length, descending. 117 180 if path.startswith(mount_point + "/") or path == mount_point: 118 self.environ["SCRIPT_NAME"] = mount_point119 self.environ["PATH_INFO"] = path[len(mount_point):]181 environ["SCRIPT_NAME"] = mount_point 182 environ["PATH_INFO"] = path[len(mount_point):] 120 183 self.wsgi_app = wsgi_app 121 184 break … … 126 189 # Note that, like wsgiref and most other WSGI servers, 127 190 # we unquote the path but not the query string. 128 self.environ["QUERY_STRING"] = qs191 environ["QUERY_STRING"] = qs 129 192 130 193 # Compare request and server HTTP protocol versions, in case our … … 146 209 return 147 210 # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. 148 self.environ["SERVER_PROTOCOL"] = req_protocol211 environ["SERVER_PROTOCOL"] = req_protocol 149 212 # set a non-standard environ entry so the WSGI app can know what 150 213 # the *real* server protocol is (and what features to support). 151 214 # See http://www.faqs.org/rfcs/rfc2145.html. 152 self.environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol215 environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol 153 216 self.response_protocol = "HTTP/%s.%s" % min(rp, sp) 154 217 155 218 # If the Request-URI was an absoluteURI, use its location atom. 156 219 if location: 157 self.environ["SERVER_NAME"] = location220 environ["SERVER_NAME"] = location 158 221 159 222 # then all the http headers 160 headers = mimetools.Message(self.rfile) 161 self.environ.update(self.parse_headers(headers)) 162 163 creds = headers.getheader("Authorization", "").split(" ", 1) 164 self.environ["AUTH_TYPE"] = creds[0] 223 try: 224 self.read_headers() 225 except ValueError, ex: 226 self.simple_response("400 Bad Request", repr(ex.args)) 227 return 228 229 creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1) 230 environ["AUTH_TYPE"] = creds[0] 165 231 if creds[0].lower() == 'basic': 166 232 user, pw = base64.decodestring(creds[1]).split(":", 1) 167 self.environ["REMOTE_USER"] = user233 environ["REMOTE_USER"] = user 168 234 169 235 # Persistent connection support 170 236 if self.response_protocol == "HTTP/1.1": 171 if headers.getheader("Connection", "") == "close":237 if environ.get("HTTP_CONNECTION", "") == "close": 172 238 self.close_connection = True 173 self.outheaders.append(("Connection", "close"))174 239 else: 175 240 # HTTP/1.0 176 if headers.getheader("Connection", "") == "Keep-Alive": 177 if self.close_connection == False: 178 self.outheaders.append(("Connection", "Keep-Alive")) 179 else: 241 if environ.get("HTTP_CONNECTION", "") != "Keep-Alive": 180 242 self.close_connection = True 181 243 182 244 # Transfer-Encoding support 183 te = headers.getheader("Transfer-Encoding", "") 184 te = [x.strip() for x in te.split(",") if x.strip()] 245 te = None 246 if self.response_protocol == "HTTP/1.1": 247 te = environ.get("HTTP_TRANSFER_ENCODING") 248 if te: 249 te = [x.strip().lower() for x in te.split(",") if x.strip()] 250 251 read_chunked = False 252 185 253 if te: 186 while te: 187 enc = te.pop() 188 if enc.lower() == "chunked": 189 if not self.decode_chunked(): 190 return 254 for enc in te: 255 if enc == "chunked": 256 read_chunked = True 191 257 else: 258 # Note that, even if we see "chunked", we must reject 259 # if there is an extension we don't recognize. 192 260 self.simple_response("501 Unimplemented") 193 261 self.close_connection = True 194 262 return 263 264 if read_chunked: 265 if not self.decode_chunked(): 266 return 195 267 else: 196 cl = headers.getheader("Content-length")268 cl = environ.get("CONTENT_LENGTH") 197 269 if method in ("POST", "PUT") and cl is None: 198 270 # No Content-Length header supplied. This will hang … … 220 292 # We used to do 3, but are now doing 1. Maybe we'll do 2 someday, 221 293 # but it seems like it would be a big slowdown for such a rare case. 222 if headers.getheader("Expect", "") == "100-continue":294 if environ.get("HTTP_EXPECT", "") == "100-continue": 223 295 self.simple_response(100) 224 296 225 297 self.ready = True 226 298 227 def parse_headers(self, headers): 228 environ = {} 229 ct = headers.getheader("Content-type", "") 230 if ct: 231 environ["CONTENT_TYPE"] = ct 232 cl = headers.getheader("Content-length") or "" 233 if cl: 234 environ["CONTENT_LENGTH"] = cl 235 236 # Must use keys() here for Python 2.3 (rfc822.Message had no __iter__). 237 for k in headers.keys(): 238 if k in ('transfer-encoding', 'content-type', 'content-length'): 239 continue 299 def read_headers(self): 300 """Read header lines from the incoming stream.""" 301 environ = self.environ 302 303 while True: 304 line = self.rfile.readline() 305 if not line: 306 # No more data--illegal end of headers 307 raise ValueError("Illegal end of headers.") 240 308 241 envname = "HTTP_" + k.upper().replace("-", "_") 309 if line == '\r\n': 310 # Normal end of headers 311 break 312 313 if line[0] in ' \t': 314 # It's a continuation line. 315 v = line.strip() 316 else: 317 k, v = line.split(":", 1) 318 k, v = k.strip().upper(), v.strip() 319 envname = "HTTP_" + k.replace("-", "_") 320 242 321 if k in comma_separated_headers: 243 322 existing = environ.get(envname) 244 323 if existing: 245 environ[envname] = ", ".join([existing] + headers.getheaders(k)) 246 else: 247 environ[envname] = ", ".join(headers.getheaders(k)) 248 else: 249 environ[envname] = headers[k] 250 return environ 324 v = ", ".join((existing, v)) 325 environ[envname] = v 326 327 ct = environ.pop("HTTP_CONTENT_TYPE", None) 328 if ct: 329 environ["CONTENT_TYPE"] = ct 330 cl = environ.pop("HTTP_CONTENT_LENGTH", None) 331 if cl: 332 environ["CONTENT_LENGTH"] = cl 251 333 252 334 def decode_chunked(self): … … 269 351 return 270 352 271 headers = mimetools.Message(self.rfile) 272 self.environ.update(self.parse_headers(headers)) 353 # Grab any trailer headers 354 self.read_headers() 355 273 356 data.seek(0) 274 357 self.environ["wsgi.input"] = data … … 277 360 278 361 def respond(self): 279 wfile = self.connection.wfile362 """Call the appropriate WSGI app and write its iterable output.""" 280 363 response = self.wsgi_app(self.environ, self.start_response) 281 364 try: 282 for line in response: 283 if not self.sent_headers: 284 self.sent_headers = True 285 self.send_headers() 286 if self.chunked_write: 287 wfile.write(hex(len(line))[2:]) 288 wfile.write("\r\n") 289 wfile.write(line) 290 wfile.write("\r\n") 291 else: 292 wfile.write(line) 293 wfile.flush() 365 for chunk in response: 366 self.write(chunk) 294 367 finally: 295 368 if hasattr(response, "close"): … … 300 373 self.send_headers() 301 374 if self.chunked_write: 302 wfile.write("0\r\n\r\n") 303 wfile.flush() 375 self.sendall("0\r\n\r\n") 304 376 305 377 def simple_response(self, status, msg=""): 306 378 """Write a simple response back to the client.""" 307 379 status = str(status) 308 wfile = self.connection.wfile 309 wfile.write("%s %s\r\n" % (self.connection.server.protocol, status)) 310 wfile.write("Content-Length: %s\r\n" % len(msg)) 380 buf = ["%s %s\r\n" % (self.connection.server.protocol, status), 381 "Content-Length: %s\r\n" % len(msg)] 311 382 312 383 if status[:3] == "413" and self.response_protocol == 'HTTP/1.1': 313 384 # Request Entity Too Large 314 385 self.close_connection = True 315 wfile.write("Connection: close\r\n")316 317 wfile.write("\r\n")386 buf.append("Connection: close\r\n") 387 388 buf.append("\r\n") 318 389 if msg: 319 wfile.write(msg)320 wfile.flush()390 buf.append(msg) 391 self.sendall("".join(buf)) 321 392 322 393 def start_response(self, status, headers, exc_info = None): 394 """WSGI callable to begin the HTTP response.""" 323 395 if self.started_response: 324 396 if not exc_info: … … 334 406 return self.write 335 407 336 def write(self, d): 408 def write(self, chunk): 409 """WSGI callable to write unbuffered data to the client. 410 411 This method is also used internally by start_response (to write 412 data from the iterable returned by the WSGI application). 413 """ 337 414 if not self.sent_headers: 338 415 self.sent_headers = True 339 416 self.send_headers() 340 self.connection.wfile.write(d) 341 self.connection.wfile.flush() 417 if self.chunked_write: 418 buf = [hex(len(chunk))[2:], 419 "\r\n", chunk, "\r\n"] 420 self.sendall("".join(buf)) 421 else: 422 self.sendall(chunk) 342 423 343 424 def send_headers(self): 344 hkeys = [key.lower() for (key, value) in self.outheaders] 425 """Assert, process, and send the HTTP response message-headers.""" 426 hkeys = [key.lower() for key, value in self.outheaders] 345 427 status = int(self.status[:3]) 346 428 347 if self.response_protocol == 'HTTP/1.1': 348 if status == 413: 349 # Request Entity Too Large. Close conn to avoid garbage. 350 self.close_connection = True 351 elif "content-length" not in hkeys: 352 if status in (200, 203, 206): 429 if status == 413: 430 # Request Entity Too Large. Close conn to avoid garbage. 431 self.close_connection = True 432 elif "content-length" not in hkeys: 433 # "All 1xx (informational), 204 (no content), 434 # and 304 (not modified) responses MUST NOT 435 # include a message-body." So no point chunking. 436 if status < 200 or status in (204, 205, 304): 437 pass 438 else: 439 if self.response_protocol == 'HTTP/1.1': 353 440 # Use the chunked transfer-coding 354 441 self.chunked_write = True 355 442 self.outheaders.append(("Transfer-Encoding", "chunked")) 356 # "All 1xx (informational), 204 (no content), 357 # and 304 (not modified) responses MUST NOT 358 # include a message-body." 359 elif status >= 200 and status not in (204, 205, 304): 360 # Close conn to determine transfer-length. 443 else: 444 # Closing the conn is the only way to determine len. 361 445 self.close_connection = True 362 446 363 if self.close_connection and "connection" not in hkeys: 364 self.outheaders.append(("Connection", "close")) 447 if "connection" not in hkeys: 448 if self.response_protocol == 'HTTP/1.1': 449 if self.close_connection: 450 self.outheaders.append(("Connection", "close")) 451 else: 452 if not self.close_connection: 453 self.outheaders.append(("Connection", "Keep-Alive")) 365 454 366 455 if "date" not in hkeys: … … 368 457 369 458 server = self.connection.server 370 wfile = self.connection.wfile371 459 372 460 if "server" not in hkeys: 373 461 self.outheaders.append(("Server", server.version)) 374 462 375 wfile.write(server.protocol + " " + self.status + "\r\n")463 buf = [server.protocol, " ", self.status, "\r\n"] 376 464 try: 377 for k, v in self.outheaders: 378 wfile.write(k + ": " + v + "\r\n") 465 buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] 379 466 except TypeError: 380 467 if not isinstance(k, str): … … 384 471 else: 385 472 raise 386 wfile.write("\r\n") 387 wfile.flush() 388 389 390 def _ssl_wrap_method(method): 473 buf.append("\r\n") 474 self.sendall("".join(buf)) 475 476 477 def _ssl_wrap_method(method, is_reader=False): 478 """Wrap the given method with SSL error-trapping. 479 480 is_reader: if False (the default), EOF errors will be raised. 481 If True, EOF errors will return "" (to emulate normal sockets). 482 """ 391 483 def ssl_method_wrapper(self, *args, **kwargs): 392 484 ## print (id(self), method, args, kwargs) … … 396 488 return method(self, *args, **kwargs) 397 489 except (SSL.WantReadError, SSL.WantWriteError): 398 # Sleep and try again 490 # Sleep and try again. This is dangerous, because it means 491 # the rest of the stack has no way of differentiating 492 # between a "new handshake" error and "client dropped". 493 # Note this isn't an endless loop: there's a timeout below. 399 494 time.sleep(self.ssl_retry) 400 495 except SSL.SysCallError, e: 401 if e.args == (-1, 'Unexpected EOF'):496 if is_reader and e.args == (-1, 'Unexpected EOF'): 402 497 return "" 403 498 404 499 errno = e.args[0] 405 if errno not in socket_errors_to_ignore: 406 raise socket.error(errno) 407 408 return "" 500 if is_reader and errno in socket_errors_to_ignore: 501 return "" 502 raise socket.error(errno) 409 503 except SSL.Error, e: 410 if e.args == (-1, 'Unexpected EOF'):504 if is_reader and e.args == (-1, 'Unexpected EOF'): 411 505 return "" 412 elife.args[0][0][2] == 'ssl handshake failure':506 if is_reader and e.args[0][0][2] == 'ssl handshake failure': 413 507 return "" 414 else: 415 raise 508 raise 416 509 if time.time() - start > self.ssl_timeout: 417 510 raise socket.timeout("timed out") … … 428 521 write = _ssl_wrap_method(socket._fileobject.write) 429 522 writelines = _ssl_wrap_method(socket._fileobject.writelines) 430 read = _ssl_wrap_method(socket._fileobject.read )431 readline = _ssl_wrap_method(socket._fileobject.readline )432 readlines = _ssl_wrap_method(socket._fileobject.readlines )523 read = _ssl_wrap_method(socket._fileobject.read, is_reader=True) 524 readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True) 525 readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True) 433 526 434 527 435 528 class HTTPConnection(object): 529 """An HTTP connection (active socket). 530 531 socket: the raw socket object (usually TCP) for this connection. 532 addr: the "bind address" for the remote end of the socket. 533 For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT). 534 For UNIX domain sockets, this will be a string. 535 server: the HTTP Server for this Connection. Usually, the server 536 object possesses a passive (server) socket which spawns multiple, 537 active (client) sockets, one for each connection. 538 539 environ: a WSGI environ template. This will be copied for each request. 540 rfile: a fileobject for reading from the socket. 541 sendall: a function for writing (+ flush) to the socket. 542 """ 436 543 437 544 rbufsize = -1 438 wbufsize = -1439 545 RequestHandlerClass = HTTPRequest 440 546 environ = {"wsgi.version": (1, 0), … … 455 561 456 562 if SSL and isinstance(sock, SSL.ConnectionType): 563 timeout = sock.gettimeout() 457 564 self.rfile = SSL_fileobject(sock, "r", self.rbufsize) 458 self.wfile = SSL_fileobject(sock, "w", self.wbufsize) 565 self.rfile.ssl_timeout = timeout 566 self.sendall = _ssl_wrap_method(sock.sendall) 459 567 self.environ["wsgi.url_scheme"] = "https" 460 568 self.environ["HTTPS"] = "on" … … 463 571 self.environ.update(sslenv) 464 572 else: 465 self.rfile = s elf.socket.makefile("r", self.rbufsize)466 self. wfile = self.socket.makefile("w", self.wbufsize)573 self.rfile = sock.makefile("r", self.rbufsize) 574 self.sendall = sock.sendall 467 575 468 576 self.environ.update({"wsgi.input": self.rfile, … … 511 619 512 620 def close(self): 621 """Close the socket underlying this connection.""" 513 622 self.rfile.close() 514 self.wfile.close()515 623 self.socket.close() 516 624 … … 528 636 529 637 class WorkerThread(threading.Thread): 638 """Thread which continuously polls a Queue for Connection objects. 639 640 server: the HTTP Server which spawned this thread, and which owns the 641 Queue and is placing active connections into it. 642 ready: a simple flag for the calling server to know when this thread 643 has begun polling the Queue. 644 645 Due to the timing issues of polling a Queue, a WorkerThread does not 646 check its own 'ready' flag after it has started. To stop the thread, 647 it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue 648 (one for each running WorkerThread). 649 """ 530 650 531 651 def __init__(self, server): … … 551 671 552 672 class SSLConnection: 673 """A thread-safe wrapper for an SSL.Connection. 674 675 *args: the arguments to create the wrapped SSL.Connection(*args). 676 """ 677 553 678 def __init__(self, *args): 554 679 self._ssl_conn = SSL.Connection(*args) … … 586 711 specifies the maximum number of queued connections (default 5). 587 712 timeout: the timeout in seconds for accepted connections (default 10). 713 714 protocol: the version string to write in the Status-Line of all 715 HTTP responses. For example, "HTTP/1.1" (the default). This 716 also limits the supported features used in the response. 717 718 719 SSL/HTTPS 720 --------- 721 The OpenSSL module must be importable for SSL functionality. 722 You can obtain it from http://pyopenssl.sourceforge.net/ 723 724 ssl_certificate: the filename of the server SSL certificate. 725 ssl_privatekey: the filename of the server's private key file. 726 727 If either of these is None (both are None by default), this server 728 will not use SSL. If both are given and are valid, they will be read 729 on server start and used in the SSL context for the listening socket. 588 730 """ 589 731 590 732 protocol = "HTTP/1.1" 591 version = "CherryPy/3.0.0 RC1"733 version = "CherryPy/3.0.0" 592 734 ready = False 593 735 _interrupt = None … … 631 773 # trap those exceptions in whatever code block calls start(). 632 774 self._interrupt = None 633 634 def bind(family, type, proto=0):635 """Create (or recreate) the actual socket object."""636 self.socket = socket.socket(family, type, proto)637 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)638 if self.ssl_certificate and self.ssl_private_key:639 if SSL is None:640 raise ImportError("You must install pyOpenSSL to use HTTPS.")641 642 # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473643 ctx = SSL.Context(SSL.SSLv23_METHOD)644 ctx.use_privatekey_file(self.ssl_private_key)645 ctx.use_certificate_file(self.ssl_certificate)646 self.socket = SSLConnection(ctx, self.socket)647 self.populate_ssl_environ()648 self.socket.bind(self.bind_addr)649 775 650 776 # Select the appropriate socket … … 665 791 # Get the correct address family for our host (allows IPv6 addresses) 666 792 host, port = self.bind_addr 793 flags = 0 794 if host == '': 795 # Despite the socket module docs, using '' does not 796 # allow AI_PASSIVE to work. Passing None instead 797 # returns '0.0.0.0' like we want. In other words: 798 # host AI_PASSIVE result 799 # '' Y 192.168.x.y 800 # '' N 192.168.x.y 801 # None Y 0.0.0.0 802 # None N 127.0.0.1 803 host = None 804 flags = socket.AI_PASSIVE 667 805 try: 668 806 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, 669 socket.SOCK_STREAM )807 socket.SOCK_STREAM, 0, flags) 670 808 except socket.gaierror: 671 809 # Probably a DNS issue. Assume IPv4. … … 677 815 af, socktype, proto, canonname, sa = res 678 816 try: 679 bind(af, socktype, proto)817 self.bind(af, socktype, proto) 680 818 except socket.error, msg: 681 819 if self.socket: … … 710 848 raise self.interrupt 711 849 850 def bind(self, family, type, proto=0): 851 """Create (or recreate) the actual socket object.""" 852 self.socket = socket.socket(family, type, proto) 853 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 854 ## self.socket.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1) 855 if self.ssl_certificate and self.ssl_private_key: 856 if SSL is None: 857 raise ImportError("You must install pyOpenSSL to use HTTPS.") 858 859 # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 860 ctx = SSL.Context(SSL.SSLv23_METHOD) 861 ctx.use_privatekey_file(self.ssl_private_key) 862 ctx.use_certificate_file(self.ssl_certificate) 863 self.socket = SSLConnection(ctx, self.socket) 864 self.populate_ssl_environ() 865 self.socket.bind(self.bind_addr) 866 712 867 def tick(self): 868 """Accept a new connection and put it on the Queue.""" 713 869 try: 714 870 s, addr = self.socket.accept() … … 740 896 self.stop() 741 897 self._interrupt = interrupt 742 interrupt = property(_get_interrupt, _set_interrupt) 898 interrupt = property(_get_interrupt, _set_interrupt, 899 doc="Set this to an Exception instance to " 900 "interrupt the server.") 743 901 744 902 def stop(self): … … 756 914 raise 757 915 else: 916 # Note that we're explicitly NOT using AI_PASSIVE, 917 # here, because we want an actual IP to touch. 918 # localhost won't work if we've bound to a public IP, 919 # but it would if we bound to INADDR_ANY via host = ''. 758 920 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, 759 921 socket.SOCK_STREAM):

