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

Changeset 1224

Show
Ignore:
Timestamp:
08/06/06 20:09:48
Author:
fumanchu
Message:

WSGI-related changes:

  1. Changed Request.run from request_line arg to deconstructed args: method, path, query_string, and protocol.
  2. Moved HTTP protocol checking from _cprequest to _cpwsgiserver. What was cherrypy.response.version is now cherrypy.request.protocol (tuple form of SERVER_PROTOCOL). request.version and response.version attributes removed. _cpwsgiserver now writes out server.protocol, not SERVER_PROTOCOL (which is a misnomer, it really should have been REQUEST_PROTOCOL).
  3. path unquoting was also moved from _cprequest to _cpwsgiserver (like most other WSGI servers).
  4. New test for absoluteURI in the Request-Line.
Files:

Legend:

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

    r1220 r1224  
    229229        pass 
    230230     
    231     def run(self, request_line, headers, rfile): 
     231    def run(self, method, path, query_string, protocol, headers, rfile): 
    232232        self.method = "GET" 
    233233        cherrypy.HTTPError(503, self.msg).set_response() 
  • trunk/cherrypy/_cperror.py

    r1209 r1224  
    7373        # http://ppewww.ph.gla.ac.uk/~flavell/www/post-redirect.html 
    7474        if status is None: 
    75             if cherrypy.response.version >= (1, 1): 
     75            if cherrypy.request.protocol >= (1, 1): 
    7676                status = 303 
    7777            else: 
  • trunk/cherrypy/_cpmodpy.py

    r1158 r1224  
    8383         
    8484        # Run the CherryPy Request object and obtain the response 
    85         requestLine = req.the_request 
    8685        headers = req.headers_in.items() 
    8786        rfile = _ReadOnlyRequest(req) 
    88         response = request.run(requestLine, headers, rfile) 
     87        response = request.run(req.method, req.uri, req.args or "", 
     88                               req.protocol, headers, rfile) 
    8989         
    9090        sendResponse(req, response.status, response.header_list, response.body) 
  • trunk/cherrypy/_cprequest.py

    r1223 r1224  
    6565    path = "" 
    6666    query_string = "" 
    67     protocol = "" 
     67    protocol = (1, 1) 
    6868    params = {} 
    69     version = http.version_from_http("HTTP/1.1") 
    7069     
    7170    # Message attributes 
     
    116115            cherrypy.serving.__dict__.clear() 
    117116     
    118     def run(self, request_line, headers, rfile): 
     117    def run(self, method, path, query_string, protocol, headers, rfile): 
    119118        """Process the Request. 
    120119         
    121         request_line should be of the form "GET /path HTTP/1.0". 
     120        method, path, query_string, and protocol should be pulled directly 
     121            from the Request-Line (e.g. "GET /path?key=val HTTP/1.0"). 
     122        path should be %XX-unquoted, but query_string should not be. 
    122123        headers should be a list of (name, value) tuples. 
    123124        rfile should be a file-like object containing the HTTP request entity. 
     
    132133         
    133134        """ 
    134         self.error_response = cherrypy.HTTPError(500).set_response 
    135          
    136         self.request_line = request_line.strip() 
    137         self.header_list = list(headers) 
    138         self.rfile = rfile 
    139         self.headers = http.HeaderMap() 
    140         self.simple_cookie = Cookie.SimpleCookie() 
    141         self.handler = None 
    142          
    143135        try: 
    144             self.process_request_line() 
     136            self.error_response = cherrypy.HTTPError(500).set_response 
     137             
     138            self.method = method 
     139            self.path = path or "/" 
     140            self.query_string = query_string 
     141            self.protocol = int(protocol[5]), int(protocol[7]) 
     142             
     143            # Rebuild first line of the request (e.g. "GET /path HTTP/1.0"). 
     144            url = path 
     145            if query_string: 
     146                url += '?' + query_string 
     147            self.request_line = '%s %s %s' % (method, url, protocol) 
     148             
     149            self.header_list = list(headers) 
     150            self.rfile = rfile 
     151            self.headers = http.HeaderMap() 
     152            self.simple_cookie = Cookie.SimpleCookie() 
     153            self.handler = None 
    145154             
    146155            # Get the 'Host' header, so we can do HTTPRedirects properly. 
     
    154163                    raise cherrypy.NotFound() 
    155164                self.app = cherrypy.tree.apps[r] 
     165            else: 
     166                self.script_name = self.app.script_name 
    156167             
    157168            # path_info should be the path from the 
     
    228239            self.hooks.run('on_end_resource') 
    229240     
    230     def process_request_line(self): 
    231         """Parse the first line (e.g. "GET /path HTTP/1.1") of the request.""" 
    232         rl = self.request_line 
    233         method, path, qs, proto = http.parse_request_line(rl) 
    234         if path == "*": 
    235             path = "global" 
    236          
    237         self.method = method 
    238         self.path = path 
    239         self.query_string = qs 
    240         self.protocol = proto 
    241          
    242         # Compare request and server HTTP versions, in case our server does 
    243         # not support the requested version. We can't tell the server what 
    244         # version number to write in the response, so we limit our output 
    245         # to min(req, server). We want the following output: 
    246         #     request    server     actual written   supported response 
    247         #     version    version   response version  feature set (resp.v) 
    248         # a     1.0        1.0           1.0                1.0 
    249         # b     1.0        1.1           1.1                1.0 
    250         # c     1.1        1.0           1.0                1.0 
    251         # d     1.1        1.1           1.1                1.1 
    252         # Notice that, in (b), the response will be "HTTP/1.1" even though 
    253         # the client only understands 1.0. RFC 2616 10.5.6 says we should 
    254         # only return 505 if the _major_ version is different. 
    255          
    256         # cherrypy.request.version == request.protocol in a Version instance. 
    257         self.version = http.version_from_http(self.protocol) 
    258          
    259         # cherrypy.response.version should be used to determine whether or 
    260         # not to include a given HTTP/1.1 feature in the response content. 
    261         server_v = cherrypy.config.get('server.protocol_version', 'HTTP/1.0') 
    262         server_v = http.version_from_http(server_v) 
    263         cherrypy.response.version = min(self.version, server_v) 
    264      
    265241    def process_headers(self): 
    266242        self.params = http.parseQueryString(self.query_string) 
     
    291267            # (Bad Request) status code to any HTTP/1.1 request message 
    292268            # which lacks a Host header field. 
    293             if self.version >= (1, 1): 
     269            if self.protocol >= (1, 1): 
    294270                msg = "HTTP/1.1 requires a 'Host' request header." 
    295271                raise cherrypy.HTTPError(400, msg) 
     
    642618    simple_cookie = Cookie.SimpleCookie() 
    643619    body = Body() 
    644     version = (1, 0) 
    645620     
    646621    def __init__(self): 
     
    693668         
    694669        # Transform our header dict into a sorted list of tuples. 
    695         self.header_list = h = headers.output(self.version
     670        self.header_list = h = headers.output(cherrypy.request.protocol
    696671         
    697672        cookie = self.simple_cookie.output() 
  • trunk/cherrypy/_cpwsgi.py

    r1197 r1224  
    77from cherrypy.lib import http 
    88 
    9  
    10 def request_line(environ): 
    11     """Rebuild first line of the request (e.g. "GET /path HTTP/1.0").""" 
    12      
    13     resource = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') 
    14     if not (resource == "*" or resource.startswith("/")): 
    15         resource = "/" + resource 
    16      
    17     qString = environ.get('QUERY_STRING') 
    18     if qString: 
    19         resource += '?' + qString 
    20      
    21     resource = resource.replace(" ", "%20") 
    22      
    23     return ('%s %s %s' % (environ['REQUEST_METHOD'], 
    24                           resource or '/', 
    25                           environ['SERVER_PROTOCOL'] 
    26                           ) 
    27             ) 
    289 
    2910headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization', 
     
    5738        # user after having been mapped to a local account. 
    5839        # Both IIS and Apache set REMOTE_USER, when possible. 
    59         request.login = (env('LOGON_USER') or env('REMOTE_USER') or None) 
     40        request.login = env('LOGON_USER') or env('REMOTE_USER') or None 
    6041         
    6142        request.multithread = environ['wsgi.multithread'] 
     
    6546        if app: 
    6647            request.app = app 
    67             request.script_name = app.script_name 
    6848         
    69         response = request.run(request_line(environ), 
     49        path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') 
     50        response = request.run(environ['REQUEST_METHOD'], path, 
     51                               environ.get('QUERY_STRING'), 
     52                               environ.get('SERVER_PROTOCOL'), 
    7053                               translate_headers(environ), 
    7154                               environ['wsgi.input']) 
     
    161144        except http.MaxSizeExceeded: 
    162145            msg = "Request Entity Too Large" 
    163             proto = self.environ.get("SERVER_PROTOCOL", "HTTP/1.0") 
    164             self.wfile.write("%s 413 %s\r\n" % (proto, msg)) 
     146            self.wfile.write("%s 413 %s\r\n" % (self.server.protocol, msg)) 
    165147            self.wfile.write("Content-Length: %s\r\n\r\n" % len(msg)) 
    166148            self.wfile.write(msg) 
     
    214196                   request_queue_size = conf('server.socket_queue_size'), 
    215197                   ) 
     198        s.protocol = conf('server.protocol_version', 'HTTP/1.0') 
    216199 
  • trunk/cherrypy/_cpwsgiserver.py

    r1218 r1224  
    11"""A high-speed, production ready, thread pooled, generic WSGI server.""" 
    22 
     3import mimetools # todo: use email 
     4import Queue 
     5import re 
     6quoted_slash = re.compile("(?i)%2F") 
     7import rfc822 
    38import socket 
     9import sys 
    410import threading 
    5 import Queue 
    6 import mimetools # todo: use email 
    7 import rfc822 
    8 import sys 
    911import time 
    1012import traceback 
     13from urllib import unquote 
     14from urlparse import urlparse 
    1115 
    1216import errno 
     
    6468            self.ready = False 
    6569            return 
    66         method,path,version = request_line.strip().split(" ", 2) 
    67         if "?" in path: 
    68             path, qs = path.split("?", 1) 
    69         else: 
    70             qs = "" 
     70         
     71        method, path, req_protocol = request_line.strip().split(" ", 2) 
    7172        self.environ["REQUEST_METHOD"] = method 
     73         
     74        # path may be an abs_path (including "http://host.domain.tld"); 
     75        scheme, location, path, params, qs, frag = urlparse(path) 
     76        if params: 
     77            path = path + ";" + params 
     78         
     79        # Unquote the path+params (e.g. "/this%20path" -> "this path"). 
     80        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 
     81        # 
     82        # But note that "...a URI must be separated into its components 
     83        # before the escaped characters within those components can be 
     84        # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 
     85        atoms = [unquote(x) for x in quoted_slash.split(path)] 
     86        path = "%2F".join(atoms) 
    7287         
    7388        for mount_point, wsgi_app in self.server.mount_points: 
     
    89104            return 
    90105         
     106        # Note that, like wsgiref and most other WSGI servers, 
     107        # we unquote the path but not the query string. 
    91108        self.environ["QUERY_STRING"] = qs 
    92         self.environ["SERVER_PROTOCOL"] = version 
    93         self.environ["SERVER_NAME"] = self.server.server_name 
     109         
     110        # Compare request and server HTTP protocol versions, in case our 
     111        # server does not support the requested protocol. Limit our output 
     112        # to min(req, server). We want the following output: 
     113        #     request    server     actual written   supported response 
     114        #     protocol   protocol  response protocol feature set (SERVER_PROTOCOL) 
     115        # a     1.0        1.0           1.0                1.0 
     116        # b     1.0        1.1           1.1                1.0 
     117        # c     1.1        1.0           1.0                1.0 
     118        # d     1.1        1.1           1.1                1.1 
     119        # Notice that, in (b), the response will be "HTTP/1.1" even though 
     120        # the client only understands 1.0. RFC 2616 10.5.6 says we should 
     121        # only return 505 if the _major_ version is different. 
     122        rp = int(req_protocol[5]), int(req_protocol[7]) 
     123        sp = int(self.server.protocol[5]), int(self.server.protocol[7]) 
     124        if sp[0] != rp[0]: 
     125            self.abort("505 HTTP Version Not Supported") 
     126            return 
     127        self.environ["SERVER_PROTOCOL"] = "HTTP/%s.%s" % min(rp, sp) 
     128         
     129        # If the Request-URI was an absoluteURI, use its location atom. 
     130        self.environ["SERVER_NAME"] = location or self.server.server_name 
     131         
    94132        if isinstance(self.server.bind_addr, basestring): 
    95133            # AF_UNIX. This isn't really allowed by WSGI, which doesn't 
     
    102140            self.environ["REMOTE_ADDR"] = self.addr[0] 
    103141            self.environ["REMOTE_PORT"] = str(self.addr[1]) 
     142         
    104143        # then all the http headers 
    105144        headers = mimetools.Message(self.rfile) 
     
    126165    def abort(self, status, msg=""): 
    127166        """Write a simple error message back to the client.""" 
    128         proto = self.environ.get("SERVER_PROTOCOL", "HTTP/1.0") 
    129         self.wfile.write("%s %s\r\n" % (proto, status)) 
     167        self.wfile.write("%s %s\r\n" % (self.server.protocol, status)) 
    130168        self.wfile.write("Content-Length: %s\r\n\r\n" % len(msg)) 
    131169        if msg: 
     
    163201        if "server" not in self.outheaderkeys: 
    164202            self.outheaders.append(("Server", self.server.version)) 
    165         if (self.environ["SERVER_PROTOCOL"] == "HTTP/1.1" 
     203        if (self.server.protocol == "HTTP/1.1" 
    166204            and "connection" not in self.outheaderkeys): 
    167205            self.outheaders.append(("Connection", "close")) 
    168         self.wfile.write(self.environ["SERVER_PROTOCOL"] + " " + self.status + "\r\n") 
     206        self.wfile.write(self.server.protocol + " " + self.status + "\r\n") 
    169207        for (k,v) in self.outheaders: 
    170208            self.wfile.write(k + ": " + v + "\r\n") 
     
    238276    """ 
    239277     
     278    protocol = "HTTP/1.0" 
    240279    version = "CherryPy/3.0.0alpha" 
    241280    ready = False 
  • trunk/cherrypy/lib/caching.py

    r1222 r1224  
    198198            if force or "Pragma" not in cherrypy.response.headers: 
    199199                cherrypy.response.headers["Pragma"] = "no-cache" 
    200             if cherrypy.request.version >= (1, 1): 
     200            if cherrypy.request.protocol >= (1, 1): 
    201201                if force or "Cache-Control" not in cherrypy.response.headers: 
    202202                    cherrypy.response.headers["Cache-Control"] = "no-cache" 
  • trunk/cherrypy/lib/http.py

    r1200 r1224  
    2626HTTPDate = rfc822.formatdate 
    2727import time 
    28 from urllib import unquote 
    29 from urlparse import urlparse 
    3028 
    3129 
     
    3634    return url 
    3735 
    38 def version_from_http(version_str): 
    39     """Return a Version tuple from the given 'HTTP/x.y' string.""" 
    40     return int(version_str[5]), int(version_str[7]) 
     36def protocol_from_http(protocol_str): 
     37    """Return a protocol tuple from the given 'HTTP/x.y' string.""" 
     38    return int(protocol_str[5]), int(protocol_str[7]) 
    4139 
    4240def getRanges(headervalue, content_length): 
     
    240238 
    241239 
    242 quoted_slash = re.compile("(?i)%2F") 
    243  
    244 def parse_request_line(request_line): 
    245     """Return (method, path, querystring, protocol) from a request_line.""" 
    246     method, path, protocol = request_line.split() 
    247      
    248     # path may be an abs_path (including "http://host.domain.tld"); 
    249     # Ignore scheme, location, and fragments (so config lookups work). 
    250     # [Therefore, this assumes all hosts are valid for this server. 
    251     # Note that we are also violating the RFC which says: if the host 
    252     # given is an abs_path, it must override any Host header.] 
    253     scheme, location, path, params, qs, frag = urlparse(path) 
    254      
    255     if params: 
    256         path = path + ";" + params 
    257      
    258     # Unquote the path (e.g. "/this%20path" -> "this path"). 
    259     # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 
    260     # 
    261     # But note that "...a URI must be separated into its components 
    262     # before the escaped characters within those components can be 
    263     # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 
    264     # 
    265     # Note also that cgi.parse_qs will decode the querystring for us. 
    266     atoms = [unquote(x) for x in quoted_slash.split(path)] 
    267     path = "%2F".join(atoms) 
    268      
    269     return method, path, qs, protocol 
    270  
    271  
    272240image_map_pattern = re.compile(r"[0-9]+,[0-9]+") 
    273241 
     
    369337        return header_elements(key, h) 
    370338     
    371     def output(self, version=(1, 1)): 
     339    def output(self, protocol=(1, 1)): 
    372340        """Transform self into a list of (name, value) tuples.""" 
    373341        header_list = [] 
     
    386354                        # outside the US-ASCII character set may assume that 
    387355                        # they represent ISO-8859-1 characters." 
    388                         if version >= (1, 1): 
     356                        if protocol >= (1, 1): 
    389357                            v = v.encode("utf-8") 
    390358                            v = Header(v, 'utf-8').encode() 
  • trunk/cherrypy/lib/static.py

    r1221 r1224  
    7373     
    7474    # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code 
    75     if cherrypy.response.version >= (1, 1): 
     75    if cherrypy.request.protocol >= (1, 1): 
    7676        response.headers["Accept-Ranges"] = "bytes" 
    7777        r = http.getRanges(cherrypy.request.headers.get('Range'), c_len) 
  • trunk/cherrypy/lib/wsgiapp.py

    r1217 r1224  
    3232    environ["QUERY_STRING"] = cherrypy.request.query_string 
    3333    environ["SERVER_PROTOCOL"] = cherrypy.request.protocol 
    34     environ["SERVER_NAME"] = cherrypy.request.wsgi_environ['SERVER_NAME'] 
    35     environ["SERVER_PORT"] = cherrypy.request.wsgi_environ['SERVER_PORT'] 
     34    server_name = getattr(cherrypy.server.httpserver, 'server_name', "None") 
     35    environ["SERVER_NAME"] = server_name  
     36    environ["SERVER_PORT"] = cherrypy.config.get('server.socket_port') 
    3637    environ["REMOTE_HOST"] = cherrypy.request.remote_host 
    3738    environ["REMOTE_ADDR"] = cherrypy.request.remote_addr 
     
    5152    try: 
    5253        environ = cherrypy.request.wsgi_environ 
     54        environ['SCRIPT_NAME'] = cherrypy.request.script_name 
     55        environ['PATH_INFO'] = cherrypy.request.path_info 
    5356    except AttributeError: 
    5457        environ = make_environ() 
    55     environ['SCRIPT_NAME'] = cherrypy.request.script_name 
    56     environ['PATH_INFO'] = cherrypy.request.path_info 
    5758     
    5859    if env: 
  • trunk/cherrypy/test/benchmark.py

    r1219 r1224  
    8888        pass 
    8989     
    90     def run(self, request_line, headers, rfile): 
     90    def run(self, method, path, query_string, protocol, headers, rfile): 
    9191        cherrypy.response.status = "204 No Content" 
    9292        cherrypy.response.header_list = [("Content-Type", 'text/html'), 
  • trunk/cherrypy/test/test_objectmapping.py

    r1157 r1224  
    217217        self.script_name = "" 
    218218         
     219        # Test absoluteURI's in the Request-Line 
     220        self.getPage('http://localhost/') 
     221        self.assertBody('world') 
     222         
    219223        # Test that the "isolated" app doesn't leak url's into the root app. 
    220224        # If it did leak, Root.default() would answer with 

Hosted by WebFaction

Log in as guest/cpguest to create tickets