Changeset 1677
- Timestamp:
- 06/22/07 01:49:31
- Files:
-
- trunk/cherrypy/_cpwsgi.py (modified) (1 diff)
- trunk/cherrypy/test/helper.py (modified) (1 diff)
- trunk/cherrypy/test/test_core.py (modified) (1 diff)
- trunk/cherrypy/wsgiserver/__init__.py (modified) (21 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/_cpwsgi.py
r1671 r1677 416 416 # We could just pass cherrypy.tree, but by passing tree.apps, 417 417 # we get correct SCRIPT_NAMEs as early as possible. 418 s.__init__(self, bind_addr, _cherrypy.tree.apps .items(),418 s.__init__(self, bind_addr, _cherrypy.tree.apps, 419 419 server.thread_pool, 420 420 server.socket_host, trunk/cherrypy/test/helper.py
r1666 r1677 147 147 apps.reverse() 148 148 for s in cherrypy.server.httpservers: 149 s. mount_points = apps149 s.wsgi_app.apps = apps 150 150 151 151 def _run_test_suite_thread(moduleNames, conf): trunk/cherrypy/test/test_core.py
r1666 r1677 837 837 for key in ['Content-Length', 'Content-Type', 'Date', 838 838 'Expires', 'Location', 'Server']: 839 self.assertEqual(hnames.count(key), 1 )839 self.assertEqual(hnames.count(key), 1, self.headers) 840 840 841 841 if cherrypy.server.protocol_version == "HTTP/1.1": trunk/cherrypy/wsgiserver/__init__.py
r1676 r1677 12 12 return ['Hello world!\n'] 13 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> 14 server = wsgiserver.CherryPyWSGIServer( 15 ('localhost', 8070), my_crazy_app, 16 server_name='localhost') 17 18 The CherryPy WSGI server can serve as many WSGI applications 19 as you want in one instance: 20 21 wsgi_apps = [('/', my_crazy_app), ('/blog', my_blog_app)] 22 server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), wsgi_apps) 23 24 Want SSL support? Just set these attributes: 25 26 server.ssl_certificate = <filename> 27 server.ssl_private_key = <filename> 23 28 24 29 if __name__ == '__main__': … … 32 37 let the name "CherryPyWSGIServer" throw you; the name merely reflects 33 38 its origin, not it's coupling. 34 35 The CherryPy WSGI server can serve as many WSGI applications36 as you want in one instance:37 38 wsgi_apps = [('/', my_crazy_app), ('/blog', my_blog_app)]39 40 39 """ 41 40 … … 85 84 'WWW-AUTHENTICATE'] 86 85 86 87 class WSGIPathInfoDispatcher(object): 88 """A WSGI dispatcher for dispatch based on the PATH_INFO. 89 90 apps: a dict or list of (path_prefix, app) pairs. 91 """ 92 93 def __init__(self, apps): 94 try: 95 apps = apps.items() 96 except AttributeError: 97 pass 98 99 # Sort the apps by len(path), descending 100 apps.sort() 101 apps.reverse() 102 103 # The path_prefix strings must start, but not end, with a slash. 104 # Use "" instead of "/". 105 self.apps = [(p.rstrip("/"), a) for p, a in apps] 106 107 def __call__(self, environ, start_response): 108 path = environ["PATH_INFO"] or "/" 109 for p, app in self.apps: 110 # The apps list should be sorted by length, descending. 111 if path.startswith(p + "/") or path == p: 112 environ = environ.copy() 113 environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p 114 environ["PATH_INFO"] = path[len(p):] 115 return app(environ, start_response) 116 117 start_response('404 Not Found', [('Content-Type', 'text/plain'), 118 ('Content-Length', '0')]) 119 return [''] 120 121 87 122 class HTTPRequest(object): 88 123 """An HTTP Request (and response). … … 90 125 A single HTTP connection may consist of multiple request/response pairs. 91 126 92 connection: the HTTP Connection object which spawned this request. 93 rfile: the 'read' fileobject from the connection's socket 127 rfile: the 'read' fileobject from the connection's socket. 128 sendall: the 'sendall' method from the connection's fileobject. 129 wsgi_app: the WSGI application to call. 130 environ: a partial WSGI environ (server and connection entries). 131 The caller MUST set the following entries: 132 * SERVER_SOFTWARE: the value to write in the "Server" response header. 133 * ACTUAL_SERVER_PROTOCOL: the value to write in the Status-Line of 134 the response. From RFC 2145: "An HTTP server SHOULD send a 135 response version equal to the highest version for which the 136 server is at least conditionally compliant, and whose major 137 version is less than or equal to the one received in the 138 request. An HTTP server MUST NOT send a version for which 139 it is not at least conditionally compliant." 140 141 outheaders: a list of header tuples to write in the response. 94 142 ready: when True, the request has been parsed and is ready to begin 95 143 generating the response. When False, signals the calling Connection … … 104 152 """ 105 153 106 def __init__(self, connection):107 self. connection = connection108 self. rfile = self.connection.rfile109 self. sendall = self.connection.sendall110 self. environ = connection.environ.copy()154 def __init__(self, rfile, sendall, environ, wsgi_app): 155 self.rfile = rfile 156 self.sendall = sendall 157 self.environ = environ.copy() 158 self.wsgi_app = wsgi_app 111 159 112 160 self.ready = False … … 143 191 return 144 192 145 server = self.connection.server146 193 environ = self.environ 147 environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version148 194 149 195 method, path, req_protocol = request_line.strip().split(" ", 2) … … 162 208 if params: 163 209 path = path + ";" + params 210 211 environ["SCRIPT_NAME"] = "" 164 212 165 213 # Unquote the path+params (e.g. "/this%20path" -> "this path"). … … 171 219 atoms = [unquote(x) for x in quoted_slash.split(path)] 172 220 path = "%2F".join(atoms) 173 174 if path == "*": 175 # This means, of course, that the last wsgi_app (shortest path) 176 # will always handle a URI of "*". 177 environ["SCRIPT_NAME"] = "" 178 environ["PATH_INFO"] = "*" 179 self.wsgi_app = server.mount_points[-1][1] 180 else: 181 for mount_point, wsgi_app in server.mount_points: 182 # The mount_points list should be sorted by length, descending. 183 if path.startswith(mount_point + "/") or path == mount_point: 184 environ["SCRIPT_NAME"] = mount_point 185 environ["PATH_INFO"] = path[len(mount_point):] 186 self.wsgi_app = wsgi_app 187 break 188 else: 189 self.simple_response("404 Not Found") 190 return 221 environ["PATH_INFO"] = path 191 222 192 223 # Note that, like wsgiref and most other WSGI servers, … … 207 238 # only return 505 if the _major_ version is different. 208 239 rp = int(req_protocol[5]), int(req_protocol[7]) 209 sp = int(server.protocol[5]), int(server.protocol[7]) 240 server_protocol = environ["ACTUAL_SERVER_PROTOCOL"] 241 sp = int(server_protocol[5]), int(server_protocol[7]) 210 242 if sp[0] != rp[0]: 211 243 self.simple_response("505 HTTP Version Not Supported") … … 213 245 # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. 214 246 environ["SERVER_PROTOCOL"] = req_protocol 215 # set a non-standard environ entry so the WSGI app can know what216 # the *real* server protocol is (and what features to support).217 # See http://www.faqs.org/rfcs/rfc2145.html.218 environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol219 247 self.response_protocol = "HTTP/%s.%s" % min(rp, sp) 220 248 … … 369 397 if hasattr(response, "close"): 370 398 response.close() 371 if (self.ready and not self.sent_headers 372 and not self.connection.server.interrupt): 399 if (self.ready and not self.sent_headers): 373 400 self.sent_headers = True 374 401 self.send_headers() … … 379 406 """Write a simple response back to the client.""" 380 407 status = str(status) 381 buf = ["%s %s\r\n" % (self. connection.server.protocol, status),408 buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status), 382 409 "Content-Length: %s\r\n" % len(msg)] 383 410 … … 461 488 self.outheaders.append(("Date", rfc822.formatdate())) 462 489 463 server = self.connection.server464 465 490 if "server" not in hkeys: 466 self.outheaders.append(("Server", se rver.version))467 468 buf = [se rver.protocol, " ", self.status, "\r\n"]491 self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE'])) 492 493 buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"] 469 494 try: 470 495 buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] … … 550 575 551 576 socket: the raw socket object (usually TCP) for this connection. 552 addr: the "bind address" for the remote end of the socket. 553 For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT). 554 For UNIX domain sockets, this will be a string. 555 server: the HTTP Server for this Connection. Usually, the server 556 object possesses a passive (server) socket which spawns multiple, 557 active (client) sockets, one for each connection. The server object 558 must possess the following attributes: 559 560 * server_name: to be placed in the WSGI environ. 561 * bind_addr: either a UNIX socket (str) or a (host, port) tuple. 562 * version: a string, like "CherryPy/3.1alpha" 563 * protocol: an HTTP version string, e.g. "HTTP/1.1" 564 * mount_points: a list of [(script_name, wsgi_app)] pairs 565 * interrupt: usually None. 566 577 wsgi_app: the WSGI application for this server/connection. 567 578 environ: a WSGI environ template. This will be copied for each request. 579 568 580 rfile: a fileobject for reading from the socket. 569 581 sendall: a function for writing (+ flush) to the socket. … … 580 592 } 581 593 582 def __init__(self, sock, addr, server):594 def __init__(self, sock, wsgi_app, environ): 583 595 self.socket = sock 584 self.addr = addr 585 self.server = server 596 self.wsgi_app = wsgi_app 586 597 587 598 # Copy the class environ into self. 588 599 self.environ = self.environ.copy() 600 self.environ.update(environ) 589 601 590 602 if SSL and isinstance(sock, SSL.ConnectionType): … … 602 614 self.sendall = sock.sendall 603 615 604 self.environ.update({"wsgi.input": self.rfile, 605 "SERVER_NAME": self.server.server_name, 606 }) 607 608 if isinstance(self.server.bind_addr, basestring): 609 # AF_UNIX. This isn't really allowed by WSGI, which doesn't 610 # address unix domain sockets. But it's better than nothing. 611 self.environ["SERVER_PORT"] = "" 612 else: 613 self.environ["SERVER_PORT"] = str(self.server.bind_addr[1]) 614 # optional values 615 # Until we do DNS lookups, omit REMOTE_HOST 616 self.environ["REMOTE_ADDR"] = self.addr[0] 617 self.environ["REMOTE_PORT"] = str(self.addr[1]) 616 self.environ["wsgi.input"] = self.rfile 618 617 619 618 def communicate(self): … … 625 624 # get written to the previous request. 626 625 req = None 627 req = self.RequestHandlerClass(self) 626 req = self.RequestHandlerClass(self.rfile, self.sendall, 627 self.environ, self.wsgi_app) 628 628 # This order of operations should guarantee correct pipelining. 629 629 req.parse_request() … … 748 748 For UNIX sockets, supply the filename as a string. 749 749 wsgi_app: the WSGI 'application callable'; multiple WSGI applications 750 may be passed as (script_name, callable) pairs. Script_name values 751 should not end in a slash. If the script_name refers to the root 752 of the URI, it should be an empty string (not "/"). 750 may be passed as (path_prefix, app) pairs. 753 751 numthreads: the number of worker threads to create (default 10). 754 752 server_name: the string to set for WSGI's SERVER_NAME environ entry. … … 783 781 _interrupt = None 784 782 ConnectionClass = HTTPConnection 783 environ = {} 785 784 786 785 # Paths to certificate and private key files … … 795 794 # We've been handed a single wsgi_app, in CP-2.1 style. 796 795 # Assume it's mounted at "". 797 self. mount_points = [("", wsgi_app)]796 self.wsgi_app = wsgi_app 798 797 else: 799 # We've been handed a list of ( mount_point, wsgi_app) tuples,798 # We've been handed a list of (path_prefix, wsgi_app) tuples, 800 799 # so that the server can call different wsgi_apps, and also 801 800 # correctly set SCRIPT_NAME. 802 self.mount_points = [(mp.rstrip("/"), app) 803 for mp, app in wsgi_app] 804 self.mount_points.sort() 805 self.mount_points.reverse() 801 self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app) 806 802 807 803 self.bind_addr = bind_addr … … 942 938 if hasattr(s, 'settimeout'): 943 939 s.settimeout(self.timeout) 944 conn = self.ConnectionClass(s, addr, self) 940 941 environ = self.environ.copy() 942 # SERVER_SOFTWARE is common for IIS. It's also helpful for 943 # us to pass a default value for the "Server" response header. 944 environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version 945 # set a non-standard environ entry so the WSGI app can know what 946 # the *real* server protocol is (and what features to support). 947 # See http://www.faqs.org/rfcs/rfc2145.html. 948 environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol 949 environ["SERVER_NAME"] = self.server_name 950 951 if isinstance(self.bind_addr, basestring): 952 # AF_UNIX. This isn't really allowed by WSGI, which doesn't 953 # address unix domain sockets. But it's better than nothing. 954 environ["SERVER_PORT"] = "" 955 else: 956 environ["SERVER_PORT"] = str(self.bind_addr[1]) 957 # optional values 958 # Until we do DNS lookups, omit REMOTE_HOST 959 environ["REMOTE_ADDR"] = addr[0] 960 environ["REMOTE_PORT"] = str(addr[1]) 961 962 conn = self.ConnectionClass(s, self.wsgi_app, environ) 945 963 self.requests.put(conn) 946 964 except socket.timeout:

