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

File twisted-web-request-args-2-rlotun.patch, 5.6 KB (added by rlotun, 4 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)