Search code examples
pythonformsquery-stringcherrypy

CherryPy combining querystring value with form value in POST body


I'm maintaining someone else's old CherryPy code, and have an interesting situation I'm trying to understand. Consider this code which demonstrates it:

import cherrypy

class HelloWorld(object):
    def index(self, name):
        html = """<form method="post">
            <input type="text" name="name" value="%s" /></form>
            Hello %s!""" % (name, name)
        return html
    index.exposed = True

cherrypy.quickstart(HelloWorld())

Run it with python hello.py and go to http://127.0.0.1:8080/?name=foo. The output is a text input box with "foo" followed by "Hello foo!".

But if I edit the text in the box and replace it with "bar" and hit enter (submitting the form), the result is not "bar" in the input box and "Hello bar!" below but (apologies for ascii art input box):

 +---------------------+
 | [u'foo', u'bar']    |
 +---------------------+

 Hello [u'foo', u'bar']!

It seems that CherryPy is combining the URL querystring "name" argument value with the form "name" value submitted in the body of the POST request and providing a list with the two values to the exposed index() method.

From what I can tell of the code I'm maintaining, it didn't always work this way. So that leads to my two questions:

  1. Did CherryPy change how it handles this situation in some version prior to 3.7.0 (which is what I'm testing with)?
  2. Can I configure CherryPy to revert to that old behavior, or at least to have the POST value override the querystring value?

(I'm not intimately familiar with the CherryPy documentation, but I couldn't find the answer there.)


Solution

  • I don't think it was the change in 3.x series. You can directly access GET and POST params like in the following snippet. However using unique names is more advised and less error-prone way.

    import urlparse
    import cherrypy
    
    class HelloWorld:
    
      @cherrypy.expose
      def index(self, name):
        postParams = cherrypy.request.body.params
        getParams  = urlparse.parse_qs(cherrypy.request.query_string)
        print(postParams, getParams)
        # ({u'name': u'bar'}, {'name': ['foo']})
    

    Update

    Good research @TimB. By skipping through the codebase I couldn't find where the thing happens. And yes CherryPy 3.2+ is like the series on its own.

    CherryPy is quite configurable, and your case is not an exception, when you're able to deal with its notions. Specifically you can write a CherryPy tool to override the mixed request params with POST params. It's just a few lines.

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    
    import urlparse
    
    import cherrypy
    
    
    config = {
      'global' : {
        'server.socket_host' : '127.0.0.1',
        'server.socket_port' : 8080,
        'server.thread_pool' : 8
      },
      '/' : {
         'tools.postoverride.on' : True,
       }
    }
    
    def postOverride():
      cherrypy.request.params.update(cherrypy.request.body.params)
    
    cherrypy.tools.postoverride = cherrypy.Tool('before_handler', postOverride)
    
    
    class HelloWorld:
    
      @cherrypy.expose
      def index(self, name):
        html = """<form method="post">
          <input type="text" name="name" value="%s" /></form>
          Hello %s!""" % (name, name)
        return html
    
    
    if __name__ == '__main__':
      cherrypy.quickstart(HelloWorld(), '/', config)