feat(Request): Refine the new protocol, port, and netloc attrs (#978)

See also PR #917

This patch does the following:

    * Moves the docstring for the deprecated "protocol" alias to be
      below the new, recommended "scheme" attribute.
    * Changes the type of "port" to an int to be consistent with the
      parse_host() utility function.
    * Expand the "port" docstring.
    * Add a docstring for "netloc".
    * Implement "uri" in terms of "netloc".
    * Handle default port in the try block, since that it is not
      necessary to do so in the case that SERVER_PORT is used.
    * In the tests, inherit from TestCase instead of the deprecated
      TestBase class.
    * DRY up the tests.
    * Test the new attributes with both the HTTP/1.0 and HTTP/1.1
      protocols.
This commit is contained in:
Kurt Griffiths
2017-01-25 11:25:44 -07:00
committed by Fran Fitzpatrick
parent 018d886b9e
commit fa1d69c803
2 changed files with 130 additions and 149 deletions

View File

@@ -72,11 +72,17 @@ class Request(object):
options (dict): Set of global options passed from the API handler.
Attributes:
protocol (str): Deprecated alias for `scheme`. May be removed in a future release.
scheme (str): Either 'http' or 'https'.
protocol (str): Deprecated alias for `scheme`. Will be removed
in a future release.
method (str): HTTP method requested (e.g., 'GET', 'POST', etc.)
host (str): Hostname requested by the client
port (str): Port used for the request
port (int): Port used for the request. If the request URL does
not specify a port, the default one for the given schema is
returned (80 for HTTP and 443 for HTTPS).
netloc (str): Returns the 'host:port' portion of the request
URL. The port may be ommitted if it is the default one for
the URL's schema (80 for HTTP and 443 for HTTPS).
subdomain (str): Leftmost (i.e., most specific) subdomain from the
hostname. If only a single domain name is given, `subdomain`
will be ``None``.
@@ -551,36 +557,19 @@ class Request(object):
def scheme(self):
return self.env['wsgi.url_scheme']
# TODO(kgriffs): Remove this deprecated alias in Falcon 2.0
protocol = scheme
@property
def uri(self):
if self._cached_uri is None:
env = self.env
protocol = env['wsgi.url_scheme']
# NOTE(kgriffs): According to PEP-3333 we should first
# try to use the Host header if present.
#
# PERF(kgriffs): try..except is faster than .get
try:
host = env['HTTP_HOST']
except KeyError:
host = env['SERVER_NAME']
port = env['SERVER_PORT']
if protocol == 'https':
if port != '443':
host += ':' + port
else:
if port != '80':
host += ':' + port
protocol = self.env['wsgi.url_scheme']
# PERF: For small numbers of items, '+' is faster
# than ''.join(...). Concatenation is also generally
# faster than formatting.
value = (protocol + '://' +
host +
self.netloc +
self.app +
self.path)
@@ -700,20 +689,42 @@ class Request(object):
def port(self):
try:
host_header = self.env['HTTP_HOST']
host, port = parse_host(host_header)
except KeyError:
port = self.env['SERVER_PORT']
if not port:
port = '80' if self.scheme == 'http' else '443'
default_port = 80 if self.env['wsgi.url_scheme'] == 'http' else 443
host, port = parse_host(host_header, default_port=default_port)
except KeyError:
# NOTE(kgriffs): Normalize to an int, since that is the type
# returned by parse_host().
#
# NOTE(kgriffs): In the case that SERVER_PORT was used,
# PEP-3333 requires that the port never be an empty string.
port = int(self.env['SERVER_PORT'])
return port
@property
def netloc(self):
env = self.env
protocol = env['wsgi.url_scheme']
# NOTE(kgriffs): According to PEP-3333 we should first
# try to use the Host header if present.
#
# PERF(kgriffs): try..except is faster than .get
try:
return self.env['HTTP_HOST']
netloc_value = env['HTTP_HOST']
except KeyError:
return self.host + ':' + self.port
netloc_value = env['SERVER_NAME']
port = env['SERVER_PORT']
if protocol == 'https':
if port != '443':
netloc_value += ':' + port
else:
if port != '80':
netloc_value += ':' + port
return netloc_value
# ------------------------------------------------------------------------
# Methods

View File

@@ -9,11 +9,15 @@ from falcon.request import Request, RequestOptions
import falcon.testing as testing
import falcon.uri
_PROTOCOLS = ['HTTP/1.0', 'HTTP/1.1']
@ddt.ddt
class TestReqVars(testing.TestBase):
class TestReqVars(testing.TestCase):
def setUp(self):
super(TestReqVars, self).setUp()
def before(self):
self.qs = 'marker=deadbeef&limit=10'
self.headers = {
@@ -651,6 +655,90 @@ class TestReqVars(testing.TestBase):
def test_content_length_method(self):
self.assertEqual(self.req.get_header('content-length'), '4829')
# TODO(kgriffs): Migrate to pytest and parametrized fixtures
# to DRY things up a bit.
@ddt.data(*_PROTOCOLS)
def test_port_explicit(self, protocol):
port = 9000
req = Request(testing.create_environ(
protocol=protocol,
port=port,
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
self.assertEqual(req.port, port)
@ddt.data(*_PROTOCOLS)
def test_scheme_https(self, protocol):
scheme = 'https'
req = Request(testing.create_environ(
protocol=protocol,
scheme=scheme,
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
self.assertEqual(req.scheme, scheme)
self.assertEqual(req.port, 443)
@ddt.data(*_PROTOCOLS)
def test_scheme_http(self, protocol):
scheme = 'http'
req = Request(testing.create_environ(
protocol=protocol,
scheme=scheme,
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
self.assertEqual(req.scheme, scheme)
self.assertEqual(req.port, 80)
@ddt.data(*_PROTOCOLS)
def test_netloc_default_port(self, protocol):
req = Request(testing.create_environ(
protocol=protocol,
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
self.assertEqual(req.netloc, 'falconframework.org')
@ddt.data(*_PROTOCOLS)
def test_netloc_nondefault_port(self, protocol):
req = Request(testing.create_environ(
protocol=protocol,
port='8080',
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
self.assertEqual(req.netloc, 'falconframework.org:8080')
@ddt.data(*_PROTOCOLS)
def test_netloc_from_env(self, protocol):
port = 9000
host = 'example.org'
env = testing.create_environ(
protocol=protocol,
host=host,
port=port,
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers)
req = Request(env)
self.assertEqual(req.port, port)
self.assertEqual(req.netloc, '{0}:{1}'.format(host, port))
# -------------------------------------------------------------------------
# Helpers
# -------------------------------------------------------------------------
@@ -678,121 +766,3 @@ class TestReqVars(testing.TestBase):
except error_type as ex:
self.assertEqual(ex.title, title)
self.assertEqual(ex.description, description)
def test_port_implicit_http(self):
req = Request(testing.create_environ(
protocol='HTTP/1.0',
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
self.assertEqual(req.port, '80')
def test_port_implicit_https(self):
req = Request(testing.create_environ(
protocol='HTTP/1.0',
scheme='https',
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
self.assertEqual(req.port, '443')
def test_port_explicit(self):
PORT = 9000
req = Request(testing.create_environ(
protocol='HTTP/1.0',
port=PORT,
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
self.assertEqual(req.port, str(PORT))
def test_port_from_env(self):
PORT = str(9000)
HTTP_HOST = '{0}:{1}'.format('example.org', PORT)
env = testing.create_environ(
protocol='HTTP/1.0',
port=PORT,
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers)
env.update({'HTTP_HOST': HTTP_HOST})
req = Request(env)
self.assertEqual(req.port, int(PORT))
def test_port_from_scheme_http(self):
HTTP_HOST = 'example.com'
env = testing.create_environ(
protocol='HTTP/1.0',
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers)
env.update({'HTTP_HOST': HTTP_HOST})
req = Request(env)
self.assertEqual(req.port, '80')
def test_port_from_scheme_https(self):
HTTP_HOST = 'example.com'
env = testing.create_environ(
protocol='HTTP/1.0',
scheme='https',
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers)
env.update({'HTTP_HOST': HTTP_HOST})
req = Request(env)
self.assertEqual(req.port, '443')
def test_scheme_https(self):
_scheme = 'https'
req = Request(testing.create_environ(
protocol='HTTP/1.0',
scheme=_scheme,
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
self.assertEqual(req.scheme, _scheme)
def test_scheme_http(self):
_scheme = 'http'
req = Request(testing.create_environ(
protocol='HTTP/1.0',
scheme=_scheme,
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
self.assertEqual(req.scheme, _scheme)
def test_netloc(self):
req = Request(testing.create_environ(
protocol='HTTP/1.0',
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers))
_netloc = '{host}:{port}'.format(host=req.host, port=req.port)
self.assertEqual(req.netloc, _netloc)
def test_netloc_from_env(self):
PORT = str(9000)
HTTP_HOST = '{0}:{1}'.format('example.org', PORT)
env = testing.create_environ(
protocol='HTTP/1.0',
port=PORT,
app=self.app,
path='/hello',
query_string=self.qs,
headers=self.headers)
env.update({'HTTP_HOST': HTTP_HOST})
req = Request(env)
self.assertEqual(req.netloc, HTTP_HOST)