Skip to content


Subversion checkout URL

You can clone with
Download ZIP


Do not remove empty GET/POST parameters #585

wants to merge 2 commits into from

5 participants


Is there a reason why tornado currently removes empty GET/POST parameters? It is causing issues with validating forms generated by formalchemy, because formalchemy expects GET/POST parameters to be there, even if they are empty.


This pull request passes (merged ebf17aa into 2b07385).


Especially useful for OAuth basestring calculation, when you need list of all parameters even if they have empty values.


This has come up several times before and I think it's a good change (with the exception that I'm not sure whether the singular RequestHandler.get_argument should treat empty arguments as unspecified). It is potentially backwards-compatibile, though, so I'd like to hold off until tornado 3.0 (which is not that far off - I'm going to do a 2.4 release soon and then next will probably be 3.0).

@bdarnell bdarnell closed this pull request from a commit
@bdarnell bdarnell Merge commit 'ebf17aa'
Closes #585.
@bdarnell bdarnell closed this in f1ae398

FYI, this pull does not enable empty POST parameters. I've submitted a pull here with the update: #614


@kung-foo nice catch, but still incomplete :P your pull does not enable empty POST parameters for requests with Content-Type multipart/form-data


ah yes. now I remember starting to look into the multipart processing code and thinking, wtf. nope. :neutral_face:


@kung-foo false alarm :blush: your patch seems to be fine. I've checked again parse_multipart_form_data implementation and figured out that there is no filter for empty values on multipart data forms. maybe need to add tests for this case.

@techiev2 techiev2 referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@Rudd-O Rudd-O referenced this pull request from a commit in Rudd-O/tornado
@bdarnell bdarnell Merge commit 'ebf17aa'
Closes #585.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 16, 2012
  1. @ysimonson
Commits on Sep 5, 2012
  1. @ysimonson
This page is out of date. Refresh to see the latest.
39 tornado/
@@ -230,20 +230,14 @@ def authorize_redirect(self, callback_uri=None, extra_params=None,
raise Exception("This service does not support oauth_callback")
if http_client is None:
http_client = httpclient.AsyncHTTPClient()
- if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a":
- http_client.fetch(
- self._oauth_request_token_url(callback_uri=callback_uri,
- extra_params=extra_params),
- self.async_callback(
- self._on_request_token,
- callback_uri))
- else:
- http_client.fetch(
- self._oauth_request_token_url(),
- self.async_callback(
- self._on_request_token, self._OAUTH_AUTHORIZE_URL,
- callback_uri))
+ request_token_url = self._oauth_request_token_url(
+ callback_uri=callback_uri, extra_params=extra_params)
+ callback = self.async_callback(
+ self._on_request_token, self._OAUTH_AUTHORIZE_URL, callback_uri)
+ http_client.fetch(request_token_url, callback)
def get_authenticated_user(self, callback, http_client=None):
"""Gets the OAuth authorized user and access token on callback.
@@ -288,14 +282,17 @@ def _oauth_request_token_url(self, callback_uri=None, extra_params=None):
oauth_version=getattr(self, "_OAUTH_VERSION", "1.0a"),
+ if callback_uri == "oob":
+ args["oauth_callback"] = "oob"
+ elif callback_uri:
+ args["oauth_callback"] = urlparse.urljoin(
+ self.request.full_url(), callback_uri)
+ if extra_params:
+ args.update(extra_params)
if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a":
- if callback_uri == "oob":
- args["oauth_callback"] = "oob"
- elif callback_uri:
- args["oauth_callback"] = urlparse.urljoin(
- self.request.full_url(), callback_uri)
- if extra_params:
- args.update(extra_params)
signature = _oauth10a_signature(consumer_token, "GET", url, args)
signature = _oauth_signature(consumer_token, "GET", url, args)
7 tornado/
@@ -382,12 +382,7 @@ def __init__(self, method, uri, version="HTTP/1.0", headers=None,
self._finish_time = None
self.path, sep, self.query = uri.partition('?')
- arguments = parse_qs_bytes(self.query)
- self.arguments = {}
- for name, values in arguments.iteritems():
- values = [v for v in values if v]
- if values:
- self.arguments[name] = values
+ self.arguments = parse_qs_bytes(self.query, keep_blank_values=True)
def supports_http_1_1(self):
"""Returns True if this request supports HTTP/1.1 semantics"""
5 tornado/test/
@@ -300,6 +300,11 @@ def test_query_string_encoding(self):
data = json_decode(response.body)
self.assertEqual(data, {u"foo": [u"\u00e9"]})
+ def test_empty_query_string(self):
+ response = self.fetch("/echo?foo=&foo=")
+ data = json_decode(response.body)
+ self.assertEqual(data, {u"foo": [u"", u""]})
def test_types(self):
headers = {"Cookie": "foo=bar"}
response = self.fetch("/typecheck?foo=bar", headers=headers)
Something went wrong with that request. Please try again.