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:
committed by
Fran Fitzpatrick
parent
018d886b9e
commit
fa1d69c803
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user