feat(Request): Also accept query params from POSTed forms

This patch adds support for merging the body of an
'application/x-www-form-urlencoded' request into the params collection
so they can be queried via get_param & Co.

Credit goes to sorenh for the originaly idea and POC. Thanks Soren!

Partially implements issue #180
This commit is contained in:
kgriffs
2014-04-14 16:05:09 -04:00
2 changed files with 38 additions and 6 deletions

View File

@@ -123,13 +123,14 @@ class Request(object):
'_cached_headers',
'_cached_uri',
'_cached_relative_uri',
'content_type',
'env',
'method',
'_params',
'path',
'query_string',
'stream',
'_wsgierrors'
'_wsgierrors',
)
def __init__(self, env):
@@ -179,6 +180,8 @@ class Request(object):
self._cached_uri = None
self._cached_relative_uri = None
self.content_type = self._get_header_by_wsgi_name('HTTP_CONTENT_TYPE')
# NOTE(kgriffs): Wrap wsgi.input if needed to make read() more robust,
# normalizing semantics between, e.g., gunicorn and wsgiref.
if isinstance(self.stream, NativeStream): # pragma: nocover
@@ -186,6 +189,26 @@ class Request(object):
# covered since the test that does so uses multiprocessing.
self.stream = helpers.Body(self.stream, self.content_length)
# PERF(kgriffs): Technically, we should spend a few more
# cycles and parse the content type for real, but
# this heuristic will work virtually all the time.
if (self.content_type and
'application/x-www-form-urlencoded' in self.content_type):
# NOTE(kgriffs): This assumes self.stream has been patched
# above in the case of wsgiref, so that self.content_length
# is not needed. Normally we just avoid accessing
# self.content_length, because it is a little expensive
# to call. We could cache self.content_length, but the
# overhead to do that won't usually be helpful, since
# content length will only ever be read once per
# request in most cases.
body = self.stream.read()
body = body.decode('ascii')
extra_params = uri.parse_query_string(uri.decode(body))
self._params.update(extra_params)
# ------------------------------------------------------------------------
# Properties
# ------------------------------------------------------------------------
@@ -233,10 +256,6 @@ class Request(object):
return None
@property
def content_type(self):
return self._get_header_by_wsgi_name('HTTP_CONTENT_TYPE')
@property
def date(self):
http_date = self._get_header_by_wsgi_name('HTTP_DATE')

View File

@@ -2,7 +2,7 @@ import falcon
import falcon.testing as testing
class TestQueryParams(testing.TestBase):
class _TestQueryParams(testing.TestBase):
def before(self):
self.resource = testing.TestResource()
@@ -242,3 +242,16 @@ class TestQueryParams(testing.TestBase):
self.assertRaises(falcon.HTTPBadRequest,
req.get_param_as_list, 'coord', transform=int)
class PostQueryParams(_TestQueryParams):
def simulate_request(self, path, query_string):
headers = {"Content-Type": "application/x-www-form-urlencoded"}
super(PostQueryParams, self).simulate_request(path, body=query_string,
headers=headers)
class GetQueryParams(_TestQueryParams):
def simulate_request(self, path, query_string):
super(GetQueryParams, self).simulate_request(
path, query_string=query_string)