perf(Request): Don't normalize CONTENT_* headers

Rather than always normalizing these headers up front, this patch
simply uses the names as they already exist in env. It was found
that this makes Request.__init__ take slightly less time to execute.

Issue #261
This commit is contained in:
Kurt Griffiths
2014-10-13 16:07:49 -05:00
parent 614c6ee197
commit f28c90a5b4
3 changed files with 26 additions and 29 deletions

View File

@@ -40,6 +40,7 @@ DEFAULT_ERROR_LOG_FORMAT = (u'{0:%Y-%m-%d %H:%M:%S} [FALCON] [ERROR]'
TRUE_STRINGS = ('true', 'True', 'yes')
FALSE_STRINGS = ('false', 'False', 'no')
WSGI_CONTENT_HEADERS = ('CONTENT_TYPE', 'CONTENT_LENGTH')
class Request(object):
@@ -225,13 +226,12 @@ class Request(object):
else:
self._params = {}
helpers.normalize_headers(env)
self._cached_headers = {}
self._cached_uri = None
self._cached_relative_uri = None
self.content_type = self._get_header_by_wsgi_name('HTTP_CONTENT_TYPE')
self.content_type = self._get_header_by_wsgi_name('CONTENT_TYPE')
# NOTE(kgriffs): Wrap wsgi.input if needed to make read() more robust,
# normalizing semantics between, e.g., gunicorn and wsgiref.
@@ -276,7 +276,7 @@ class Request(object):
@property
def content_length(self):
value = self._get_header_by_wsgi_name('HTTP_CONTENT_LENGTH')
value = self._get_header_by_wsgi_name('CONTENT_LENGTH')
if value:
try:
@@ -452,6 +452,9 @@ class Request(object):
# anyway.
headers[name[5:].replace('_', '-')] = value
elif name in WSGI_CONTENT_HEADERS:
headers[name.replace('_', '-')] = value
return self._cached_headers.copy()
@property
@@ -529,13 +532,26 @@ class Request(object):
"""
wsgi_name = name.upper().replace('-', '_')
# Use try..except to optimize for the header existing in most cases
try:
# Don't take the time to cache beforehand, using HTTP naming.
# This will be faster, assuming that most headers are looked
# up only once, and not all headers will be requested.
return self.env['HTTP_' + name.upper().replace('-', '_')]
return self.env['HTTP_' + wsgi_name]
except KeyError:
# NOTE(kgriffs): There are a couple headers that do not
# use the HTTP prefix in the env, so try those. We expect
# people to usually just use the relevant helper properties
# to access these instead of .get_header.
if wsgi_name in WSGI_CONTENT_HEADERS:
try:
return self.env[wsgi_name]
except KeyError:
pass
if not required:
return None

View File

@@ -13,31 +13,6 @@
# limitations under the License.
def normalize_headers(env):
"""Normalize HTTP headers in an WSGI environ dictionary.
Args:
env: A WSGI environ dictionary to normalize (in-place)
Raises:
KeyError: The env dictionary did not contain a key that is required by
PEP-333.
TypeError: env is not dictionary-like. In other words, it has no
attribute '__getitem__'.
"""
# NOTE(kgriffs): Per the WSGI spec, HOST, Content-Type, and
# CONTENT_LENGTH are not under HTTP_* and so we normalize
# that here.
if 'CONTENT_TYPE' in env:
env['HTTP_CONTENT_TYPE'] = env['CONTENT_TYPE']
if 'CONTENT_LENGTH' in env:
env['HTTP_CONTENT_LENGTH'] = env['CONTENT_LENGTH']
class Body(object):
"""Wrap wsgi.input streams to make them more robust.

View File

@@ -227,6 +227,12 @@ class TestHeaders(testing.TestBase):
self.assertEqual(body, [])
def test_content_header_missing(self):
environ = testing.create_environ()
req = falcon.Request(environ)
for header in ('Content-Type', 'Content-Length'):
self.assertIs(req.get_header(header), None)
def test_passthrough_req_headers(self):
req_headers = {
'X-Auth-Token': 'Setec Astronomy',