Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 1253

Show
Ignore:
Timestamp:
08/20/06 01:32:43
Author:
fumanchu
Message:

Fix for #551 (_cpwsgiserver should handle persistent connections). All of CP should now default to HTTP/1.1. Some tests still need to be written, and 1.0 support reviewed throughout, but the basics work.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/_cpwsgi.py

    r1250 r1253  
    119119class CPHTTPRequest(_cpwsgiserver.HTTPRequest): 
    120120     
    121     def __init__(self, socket, addr, server): 
    122         _cpwsgiserver.HTTPRequest.__init__(self, socket, addr, server) 
     121    def parse_request(self): 
    123122        mhs = int(cherrypy.config.get('server.max_request_header_size', 
    124123                                      500 * 1024)) 
    125124        if mhs > 0: 
    126125            self.rfile = http.SizeCheckWrapper(self.rfile, mhs) 
    127      
    128     def parse_request(self): 
     126         
    129127        try: 
    130128            _cpwsgiserver.HTTPRequest.parse_request(self) 
    131129        except http.MaxSizeExceeded: 
    132             msg = "Request Entity Too Large" 
    133             self.wfile.write("%s 413 %s\r\n" % (self.server.protocol, msg)) 
    134             self.wfile.write("Content-Length: %s\r\n\r\n" % len(msg)) 
    135             self.wfile.write(msg) 
    136             self.wfile.flush() 
    137             self.ready = False 
    138              
     130            self.abort(413, "Request Entity Too Large") 
    139131            cherrypy.log(traceback=True) 
    140         else: 
    141             if self.ready: 
    142                 # Request header is parsed 
    143                 script_name = self.environ.get('SCRIPT_NAME', '') 
    144                 path_info = self.environ.get('PATH_INFO', '') 
    145                 path = (script_name + path_info) 
    146                 if path == "*": 
    147                     path = "global" 
    148                  
    149                 if isinstance(self.rfile, http.SizeCheckWrapper): 
    150                     # Unwrap the rfile 
    151                     self.rfile = self.rfile.rfile 
    152                 self.environ["wsgi.input"] = self.rfile 
     132 
     133 
     134class CPHTTPConnection(_cpwsgiserver.HTTPConnection): 
     135     
     136    RequestHandlerClass = CPHTTPRequest 
    153137 
    154138 
     
    163147    """ 
    164148     
    165     RequestHandlerClass = CPHTTPRequest 
     149    ConnectionClass = CPHTTPConnection 
    166150     
    167151    def __init__(self): 
     
    182166                   timeout = conf('server.socket_timeout'), 
    183167                   ) 
    184         s.protocol = conf('server.protocol_version', 'HTTP/1.0') 
     168        s.protocol = conf('server.protocol_version', 'HTTP/1.1') 
    185169 
  • trunk/cherrypy/_cpwsgiserver.py

    r1237 r1253  
    3535    ] 
    3636 
     37 
    3738class HTTPRequest(object): 
    3839     
    39     stderr = sys.stderr 
    40     bufsize = -1 
    41      
    42     def __init__(self, socket, addr, server): 
    43         self.socket = socket 
    44         self.addr = addr 
    45         self.server = server 
    46         self.environ = {} 
     40    def __init__(self, connection): 
     41        self.connection = connection 
     42        self.rfile = self.connection.rfile 
     43        self.environ = connection.environ.copy() 
     44         
    4745        self.ready = False 
    4846        self.started_response = False 
    4947        self.status = "" 
    5048        self.outheaders = [] 
    51         self.outheaderkeys = [] 
    52         self.rfile = self.socket.makefile("r", self.bufsize) 
    53         self.wfile = self.socket.makefile("w", self.bufsize) 
    5449        self.sent_headers = False 
     50        self.close_connection = False 
    5551     
    5652    def parse_request(self): 
    57         self.sent_headers = False 
    58         self.environ = {} 
    59         self.environ["wsgi.version"] = (1,0) 
    60         self.environ["wsgi.url_scheme"] = "http" 
    61         self.environ["wsgi.input"] = self.rfile 
    62         self.environ["wsgi.errors"] = self.stderr 
    63         self.environ["wsgi.multithread"] = True 
    64         self.environ["wsgi.multiprocess"] = False 
    65         self.environ["wsgi.run_once"] = False 
    66         request_line = self.rfile.readline() 
     53        try: 
     54            request_line = self.rfile.readline() 
     55        except socket.timeout: 
     56            self.abort("408 Request Timeout") 
     57         
    6758        if not request_line: 
    68             self.ready = False 
    6959            return 
     60         
     61        server = self.connection.server 
    7062         
    7163        method, path, req_protocol = request_line.strip().split(" ", 2) 
     
    8880        path = "%2F".join(atoms) 
    8981         
    90         for mount_point, wsgi_app in self.server.mount_points: 
     82        for mount_point, wsgi_app in server.mount_points: 
    9183            if path == "*": 
    9284                # This means, of course, that the first wsgi_app will 
     
    123115        # only return 505 if the _major_ version is different. 
    124116        rp = int(req_protocol[5]), int(req_protocol[7]) 
    125         sp = int(self.server.protocol[5]), int(self.server.protocol[7]) 
     117        sp = int(server.protocol[5]), int(server.protocol[7]) 
    126118        if sp[0] != rp[0]: 
    127119            self.abort("505 HTTP Version Not Supported") 
     
    130122         
    131123        # If the Request-URI was an absoluteURI, use its location atom. 
    132         self.environ["SERVER_NAME"] = location or self.server.server_name 
    133          
    134         if isinstance(self.server.bind_addr, basestring): 
    135             # AF_UNIX. This isn't really allowed by WSGI, which doesn't 
    136             # address unix domain sockets. But it's better than nothing. 
    137             self.environ["SERVER_PORT"] = "" 
    138         else: 
    139             self.environ["SERVER_PORT"] = str(self.server.bind_addr[1]) 
    140             # optional values 
    141             self.environ["REMOTE_HOST"] = self.addr[0] 
    142             self.environ["REMOTE_ADDR"] = self.addr[0] 
    143             self.environ["REMOTE_PORT"] = str(self.addr[1]) 
     124        if location: 
     125            self.environ["SERVER_NAME"] = location 
    144126         
    145127        # then all the http headers 
    146128        headers = mimetools.Message(self.rfile) 
    147129        self.environ["CONTENT_TYPE"] = headers.getheader("Content-type", "") 
     130         
    148131        cl = headers.getheader("Content-length") 
    149132        if method in ("POST", "PUT") and cl is None: 
     
    163146            else: 
    164147                self.environ[envname] = headers[k] 
     148         
     149        if self.environ["SERVER_PROTOCOL"] == "HTTP/1.1": 
     150            if headers.getheader("Connection", "") == "close": 
     151                self.close_connection = True 
     152                self.outheaders.append(("Connection", "close")) 
     153        else: 
     154            if headers.getheader("Connection", "") == "Keep-Alive": 
     155                if self.close_connection == False: 
     156                    self.outheaders.append(("Connection", "Keep-Alive")) 
     157            else: 
     158                self.close_connection = True 
     159         
    165160        self.ready = True 
     161     
     162    def respond(self): 
     163        response = self.wsgi_app(self.environ, self.start_response) 
     164        for line in response: 
     165            self.write(line) 
     166        if hasattr(response, "close"): 
     167            response.close() 
     168        self.terminate() 
    166169     
    167170    def abort(self, status, msg=""): 
    168171        """Write a simple error message back to the client.""" 
    169         self.wfile.write("%s %s\r\n" % (self.server.protocol, status)) 
    170         self.wfile.write("Content-Length: %s\r\n\r\n" % len(msg)) 
     172        status = str(status) 
     173        wfile = self.connection.wfile 
     174        wfile.write("%s %s\r\n" % (self.connection.server.protocol, status)) 
     175        wfile.write("Content-Length: %s\r\n" % len(msg)) 
     176         
     177        if status[:3] == "413" and self.environ["SERVER_PROTOCOL"] == 'HTTP/1.1': 
     178            # Request Entity Too Large 
     179            self.close_connection = True 
     180            wfile.write("Connection: close\r\n") 
     181         
     182        wfile.write("\r\n") 
    171183        if msg: 
    172             self.wfile.write(msg) 
    173         self.wfile.flush() 
     184            wfile.write(msg) 
     185        wfile.flush() 
    174186        self.ready = False 
    175187     
     
    185197        self.started_response = True 
    186198        self.status = status 
    187         self.outheaders = headers 
    188         self.outheaderkeys = [key.lower() for (key,value) in self.outheaders] 
     199        self.outheaders.extend(headers) 
    189200        return self.write 
    190201     
     
    193204            self.sent_headers = True 
    194205            self.send_headers() 
    195         self.wfile.write(d) 
    196         self.wfile.flush() 
     206        self.connection.wfile.write(d) 
     207        self.connection.wfile.flush() 
    197208     
    198209    def send_headers(self): 
    199         if "content-length" not in self.outheaderkeys: 
    200             self.close_at_end = True 
    201         if "date" not in self.outheaderkeys: 
     210        hkeys = [key.lower() for (key,value) in self.outheaders] 
     211         
     212        if (self.environ["SERVER_PROTOCOL"] == 'HTTP/1.1' 
     213            and (# Request Entity Too Large. Close conn to avoid garbage. 
     214                self.status[:3] == "413" 
     215                # No Content-Length. Close conn to determine transfer-length. 
     216                or "content-length" not in hkeys)): 
     217            if "connection" not in hkeys: 
     218                self.outheaders.append(("Connection", "close")) 
     219            self.close_connection = True 
     220         
     221        if "date" not in hkeys: 
    202222            self.outheaders.append(("Date", rfc822.formatdate())) 
    203         if "server" not in self.outheaderkeys: 
    204             self.outheaders.append(("Server", self.server.version)) 
    205         if (self.server.protocol == "HTTP/1.1" 
    206             and "connection" not in self.outheaderkeys): 
    207             self.outheaders.append(("Connection", "close")) 
    208         self.wfile.write(self.server.protocol + " " + self.status + "\r\n") 
    209         for (k,v) in self.outheaders: 
    210             self.wfile.write(k + ": " + v + "\r\n") 
    211         self.wfile.write("\r\n") 
    212         self.wfile.flush() 
     223         
     224        server = self.connection.server 
     225        wfile = self.connection.wfile 
     226         
     227        if "server" not in hkeys: 
     228            self.outheaders.append(("Server", server.version)) 
     229         
     230        wfile.write(server.protocol + " " + self.status + "\r\n") 
     231        for k, v in self.outheaders: 
     232            wfile.write(k + ": " + v + "\r\n") 
     233        wfile.write("\r\n") 
     234        wfile.flush() 
    213235     
    214236    def terminate(self): 
    215         if self.ready and not self.sent_headers and not self.server.interrupt: 
     237        if (self.ready and not self.sent_headers 
     238                and not self.connection.server.interrupt): 
    216239            self.sent_headers = True 
    217240            self.send_headers() 
     241 
     242 
     243class HTTPConnection(object): 
     244     
     245    bufsize = -1 
     246    RequestHandlerClass = HTTPRequest 
     247    environ = {"wsgi.version": (1, 0), 
     248               "wsgi.url_scheme": "http", 
     249               "wsgi.multithread": True, 
     250               "wsgi.multiprocess": False, 
     251               "wsgi.run_once": False, 
     252               "wsgi.errors": sys.stderr, 
     253               } 
     254     
     255    def __init__(self, socket, addr, server): 
     256        self.socket = socket 
     257        self.addr = addr 
     258        self.server = server 
     259         
     260        self.rfile = self.socket.makefile("r", self.bufsize) 
     261        self.wfile = self.socket.makefile("w", self.bufsize) 
     262         
     263        # Copy the class environ into self. 
     264        self.environ = self.environ.copy() 
     265        self.environ.update({"wsgi.input": self.rfile, 
     266                             "SERVER_NAME": self.server.server_name, 
     267                             }) 
     268         
     269        if isinstance(self.server.bind_addr, basestring): 
     270            # AF_UNIX. This isn't really allowed by WSGI, which doesn't 
     271            # address unix domain sockets. But it's better than nothing. 
     272            self.environ["SERVER_PORT"] = "" 
     273        else: 
     274            self.environ["SERVER_PORT"] = str(self.server.bind_addr[1]) 
     275            # optional values 
     276            self.environ["REMOTE_HOST"] = self.addr[0] 
     277            self.environ["REMOTE_ADDR"] = self.addr[0] 
     278            self.environ["REMOTE_PORT"] = str(self.addr[1]) 
     279     
     280    def communicate(self): 
     281        """Read each request and respond appropriately.""" 
     282        while True: 
     283            req = self.RequestHandlerClass(self) 
     284            # This order of operations should guarantee correct pipelining. 
     285            req.parse_request() 
     286            if not req.ready: 
     287                break 
     288            req.respond() 
     289            if req.close_connection: 
     290                break 
     291     
     292    def close(self): 
    218293        self.rfile.close() 
    219294        self.wfile.close() 
     
    234309            self.ready = True 
    235310            while True: 
    236                 request = self.server.requests.get() 
    237                 if request is _SHUTDOWNREQUEST: 
     311                conn = self.server.requests.get() 
     312                if conn is _SHUTDOWNREQUEST: 
    238313                    return 
    239314                 
    240315                try: 
    241316                    try: 
    242                         request.parse_request() 
    243                         if request.ready: 
    244                             response = request.wsgi_app(request.environ, 
    245                                                         request.start_response) 
    246                             for line in response: 
    247                                 request.write(line) 
    248                             if hasattr(response, "close"): 
    249                                 response.close() 
     317                        conn.communicate() 
    250318                    except socket.error, e: 
    251319                        errno = e.args[0] 
     
    257325                        traceback.print_exc() 
    258326                finally: 
    259                     request.terminate() 
     327                    conn.close() 
    260328        except (KeyboardInterrupt, SystemExit), exc: 
    261329            self.server.interrupt = exc 
     
    278346    """ 
    279347     
    280     protocol = "HTTP/1.0
     348    protocol = "HTTP/1.1
    281349    version = "CherryPy/3.0.0alpha" 
    282350    ready = False 
    283351    _interrupt = None 
    284     RequestHandlerClass = HTTPRequest 
     352    ConnectionClass = HTTPConnection 
    285353     
    286354    def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, 
     
    393461            if hasattr(s, 'settimeout'): 
    394462                s.settimeout(self.timeout) 
    395             request = self.RequestHandlerClass(s, addr, self) 
    396             self.requests.put(request
     463            conn = self.ConnectionClass(s, addr, self) 
     464            self.requests.put(conn
    397465        except socket.timeout: 
    398466            # The only reason for the timeout in start() is so we can 
  • trunk/cherrypy/config.py

    r1243 r1253  
    6262    'server.socket_queue_size': 5, 
    6363    'server.socket_timeout': 10, 
    64     'server.protocol_version': 'HTTP/1.0', 
     64    'server.protocol_version': 'HTTP/1.1', 
    6565    'server.reverse_dns': False, 
    6666    'server.thread_pool': 10, 
  • trunk/cherrypy/test/helper.py

    r1249 r1253  
    4141        pass 
    4242     
    43     def getPage(self, url, headers=None, method="GET", body=None, protocol="HTTP/1.1"): 
     43    def getPage(self, url, headers=None, method="GET", body=None, protocol=None): 
    4444        """Open the url. Return status, headers, body.""" 
    4545        if self.script_name: 
  • trunk/cherrypy/test/webtest.py

    r1186 r1253  
    140140    HOST = "127.0.0.1" 
    141141    PORT = 8000 
    142     HTTP_CONN=httplib.HTTPConnection 
    143      
    144     def getPage(self, url, headers=None, method="GET", body=None, protocol="HTTP/1.1"): 
     142    HTTP_CONN = httplib.HTTPConnection 
     143    PROTOCOL = "HTTP/1.1" 
     144     
     145    def getPage(self, url, headers=None, method="GET", body=None, protocol=None): 
    145146        """Open the url with debugging support. Return status, headers, body.""" 
    146147        ServerError.on = False 
     
    148149        self.url = url 
    149150        result = openURL(url, headers, method, body, self.HOST, self.PORT, 
    150                          self.HTTP_CONN, protocol
     151                         self.HTTP_CONN, protocol or self.PROTOCOL
    151152        self.status, self.headers, self.body = result 
    152153         
     
    367368    while trial < 10: 
    368369        try: 
    369             conn = http_conn(host, port) 
     370            # Allow http_conn to be a class or an instance 
     371            if hasattr(http_conn, "host"): 
     372                conn = http_conn 
     373            else: 
     374                conn = http_conn(host, port) 
     375             
    370376            conn._http_vsn_str = protocol 
    371377            conn._http_vsn = int("".join([x for x in protocol if x.isdigit()])) 
     
    407413            outbody = response.read() 
    408414             
    409             conn.close() 
     415            if not hasattr(http_conn, "host"): 
     416                # We made our own conn instance. Close it. 
     417                conn.close() 
     418             
    410419            return status, outheaders, outbody 
    411420        except socket.error: 

Hosted by WebFaction

Log in as guest/cpguest to create tickets