Ticket #4519: twisted-web-request-args-2-rlotun.patch

File twisted-web-request-args-2-rlotun.patch, 5.6 KB (added by rlotun, 3 years ago)

Patch to address review concerns.

  • twisted/web/http.py

     
    563563    sentLength = 0 # content-length of response, or total bytes sent via chunking 
    564564    etag = None 
    565565    lastModified = None 
    566     args = None 
     566    _args = None # .args is a property that will parse and store request args here 
     567    _orig_method = None # http method request originally called with 
     568    _orig_uri = None # uri request originally called with 
    567569    path = None 
    568570    content = None 
    569571    _forceSSL = 0 
     
    725727        @param version: The HTTP version of this request. 
    726728        """ 
    727729        self.content.seek(0,0) 
    728         self.args = {} 
    729730        self.stack = [] 
    730731 
    731         self.method, self.uri = command, path 
     732        # since argument parsing is now deferred to a property we want to 
     733        # have explicit references to the original method and original uri 
     734        # to parse against in case they are subsequently changed by 
     735        # application code 
     736        self.method, self.uri = self._orig_method, self._orig_uri = command, path 
    732737        self.clientproto = version 
    733738        x = self.uri.split('?', 1) 
    734739 
     
    736741            self.path = self.uri 
    737742        else: 
    738743            self.path, argstring = x 
    739             self.args = parse_qs(argstring, 1) 
    740744 
    741745        # cache the client and server information, we'll need this later to be 
    742746        # serialized and sent with the request so CGIs will work remotely 
    743747        self.client = self.channel.transport.getPeer() 
    744748        self.host = self.channel.transport.getHost() 
    745749 
    746         # Argument processing 
    747         args = self.args 
     750        self.process() 
     751 
     752    def args(self): 
     753        """ 
     754        Return query or post arguments dict of the request on demand. 
     755 
     756        If the arguments have already been parsed, they will be returned 
     757        immediately, otherwise the first access will parse arguments 
     758        from the url or body and return it. 
     759        """ 
     760        if self._args is not None: 
     761            return self._args 
     762 
     763        # Parse query string arguments 
     764        x = self._orig_uri.split('?', 1) 
     765        if len(x) != 1: 
     766            self._args = parse_qs(x[1], 1) 
     767        else: 
     768            self._args = {} 
     769 
     770        # Parse post body arguments 
     771        args = self._args 
    748772        ctype = self.requestHeaders.getRawHeaders('content-type') 
    749773        if ctype is not None: 
    750774            ctype = ctype[0] 
    751775 
    752         if self.method == "POST" and ctype: 
     776        if self._orig_method == "POST" and ctype: 
     777 
     778            # preserve location in content stream 
     779            curr_loc = self.content.tell() 
     780            self.content.seek(0, 0) 
     781 
    753782            mfd = 'multipart/form-data' 
    754783            key, pdict = cgi.parse_header(ctype) 
    755784            if key == 'application/x-www-form-urlencoded': 
     
    768797                        self.channel.transport.loseConnection() 
    769798                        return 
    770799                    raise 
    771             self.content.seek(0, 0) 
     800            # restore previous location in content stream 
     801            self.content.seek(curr_loc, 0) 
     802        return args 
     803    args = property(args) 
    772804 
    773         self.process() 
    774  
    775  
    776805    def __repr__(self): 
    777806        return '<%s %s %s>'% (self.method, self.uri, self.clientproto) 
    778807 
  • twisted/web/test/test_http.py

     
    826826 
    827827        self.runRequest(httpRequest, MyRequest) 
    828828 
     829    def test_formNotParsedUntilArgsAccessed(self): 
     830        """ 
     831        The post body is not parsed until request.args is accessed. 
     832        """ 
     833        query = 'key=value' 
     834        httpRequest = '''\ 
     835POST / HTTP/1.0 
     836Content-Length: %d 
     837Content-Type: application/x-www-form-urlencoded 
     838 
     839%s''' % (len(query), query) 
     840 
     841        testcase = self 
     842        self.parse_qs_calls = [] 
     843        orig_parse_qs = http.parse_qs 
     844        def parse_qs(query_string, *args): 
     845            testcase.parse_qs_calls.append(query_string) 
     846            return orig_parse_qs(query_string, *args) 
     847        self.patch(http, 'parse_qs', parse_qs) 
     848        class MyRequest(http.Request): 
     849            def process(self): 
     850                testcase.assertEquals(self.method, "POST") 
     851                # Arguments have not been parsed: 
     852                testcase.assertEquals(testcase.parse_qs_calls, []) 
     853                # But have been after first access to self.args: 
     854                testcase.assertEquals(self.args["key"], ["value"]) 
     855                testcase.assertEquals(testcase.parse_qs_calls, [query]) 
     856 
     857                # Reading from the content file-like must produce the entire 
     858                # request body. 
     859                testcase.assertEquals(self.content.read(), query) 
     860                testcase.didRequest = 1 
     861                self.finish() 
     862 
     863        self.runRequest(httpRequest, MyRequest) 
     864 
    829865    def testMissingContentDisposition(self): 
    830866        req = '''\ 
    831867POST / HTTP/1.0 
     
    864900                # The tempfile API used to create content returns an 
    865901                # instance of a different type depending on what platform 
    866902                # we're running on.  The point here is to verify that the 
    867                 # request body is in a file that's on the filesystem.  
     903                # request body is in a file that's on the filesystem. 
    868904                # Having a fileno method that returns an int is a somewhat 
    869905                # close approximation of this. -exarkun 
    870906                testcase.assertIsInstance(self.content.fileno(), int)