From db76b0be3586218e3f724e6f77737363f251552e Mon Sep 17 00:00:00 2001 From: lixxu Date: Thu, 8 Nov 2018 10:48:21 +0800 Subject: [PATCH] add port to url_for, and update doc for it, reset scheme and external if server not specified in url_for --- docs/sanic/routing.md | 10 +++++----- sanic/app.py | 21 +++++++++++++++++++-- sanic/testing.py | 7 ++++++- tests/test_url_building.py | 4 ++-- tests/test_url_for_static.py | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 10 deletions(-) diff --git a/docs/sanic/routing.md b/docs/sanic/routing.md index e9cb0aefef..955bdd7cd5 100644 --- a/docs/sanic/routing.md +++ b/docs/sanic/routing.md @@ -164,21 +164,21 @@ url = app.url_for('post_handler', post_id=5, arg_one='one', arg_two='two') url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two']) # /posts/5?arg_one=one&arg_one=two ``` -- Also some special arguments (`_anchor`, `_external`, `_scheme`, `_method`, `_server`) passed to `url_for` will have special url building (`_method` is not support now and will be ignored). For example: +- Also some special arguments (`_anchor`, `_external`, `_scheme`, `_method`, `_server`, `_port`) passed to `url_for` will have special url building (`_method` is not support now and will be ignored). For example: ```python url = app.url_for('post_handler', post_id=5, arg_one='one', _anchor='anchor') # /posts/5?arg_one=one#anchor -url = app.url_for('post_handler', post_id=5, arg_one='one', _external=True) -# //server/posts/5?arg_one=one +url = app.url_for('post_handler', post_id=5, arg_one='one', _external=True, _server='server') +# http://server/posts/5?arg_one=one # _external requires passed argument _server or SERVER_NAME in app.config or url will be same as no _external -url = app.url_for('post_handler', post_id=5, arg_one='one', _scheme='http', _external=True) +url = app.url_for('post_handler', post_id=5, arg_one='one', _scheme='http', _external=True, _server='server') # http://server/posts/5?arg_one=one # when specifying _scheme, _external must be True # you can pass all special arguments one time -url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'], arg_two=2, _anchor='anchor', _scheme='http', _external=True, _server='another_server:8888') +url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'], arg_two=2, _anchor='anchor', _scheme='http', _external=True, _server='another_server', _port=8888) # http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor ``` - All valid parameters must be passed to `url_for` to build a URL. If a parameter is not supplied, or if a parameter does not match the specified type, a `URLBuildError` will be thrown. diff --git a/sanic/app.py b/sanic/app.py index c2a24464a1..14119bac16 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -637,14 +637,19 @@ def url_for(self, view_name: str, **kwargs): if external: if not scheme: - if ":" in netloc[:8]: + if netloc and ":" in netloc[:8]: scheme = netloc[:8].split(":", 1)[0] else: scheme = "http" - if "://" in netloc[:8]: + if netloc and "://" in netloc[:8]: netloc = netloc.split("://", 1)[-1] + if not netloc: + scheme = "" + external = False + + port = kwargs.pop("_port", self.config.get("SERVER_PORT", "")) or "" for match in matched_params: name, _type, pattern = self.router.parse_parameter_string(match) # we only want to match against each individual parameter @@ -690,6 +695,18 @@ def url_for(self, view_name: str, **kwargs): # parse the remainder of the keyword arguments into a querystring query_string = urlencode(kwargs, doseq=True) if kwargs else "" + + if external and port: + try: + port = int(port) + except (ValueError, TypeError): + pass # just ignore it + else: + if (scheme == "http" and port != 80) or ( + scheme == "https" and port != 443 + ): + netloc = "{}:{}".format(netloc, port) + # scheme://netloc/path;parameters?query#fragment out = urlunparse((scheme, netloc, out, "", query_string, anchor)) diff --git a/sanic/testing.py b/sanic/testing.py index eda52d61b6..955a6655e8 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -17,6 +17,11 @@ def __init__(self, app, port=PORT): async def _local_request(self, method, uri, cookies=None, *args, **kwargs): import aiohttp + if int(aiohttp.__version__.split(".", 1)[0]) > 2: + ssl_kw = dict(ssl=False) + else: + ssl_kw = dict(verify_ssl=False) + if uri.startswith(("http:", "https:", "ftp:", "ftps://" "//")): url = uri else: @@ -25,7 +30,7 @@ async def _local_request(self, method, uri, cookies=None, *args, **kwargs): ) logger.info(url) - conn = aiohttp.TCPConnector(verify_ssl=False) + conn = aiohttp.TCPConnector(**ssl_kw) async with aiohttp.ClientSession( cookies=cookies, connector=conn ) as session: diff --git a/tests/test_url_building.py b/tests/test_url_building.py index bff3a5500b..5e8c176555 100644 --- a/tests/test_url_building.py +++ b/tests/test_url_building.py @@ -15,12 +15,12 @@ URL_FOR_VALUE2 = '/myurl?arg1=v1&arg1=v2#anchor' URL_FOR_ARGS3 = dict( arg1='v1', _anchor='anchor', _scheme='http', - _server='{}:{}'.format(test_host, test_port), _external=True + _server=test_host, _port=test_port, _external=True ) URL_FOR_VALUE3 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host, test_port) URL_FOR_ARGS4 = dict(arg1='v1', _anchor='anchor', _external=True, - _server='http://{}:{}'.format(test_host, test_port)) + _server='http://{}'.format(test_host), _port=test_port) URL_FOR_VALUE4 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host, test_port) diff --git a/tests/test_url_for_static.py b/tests/test_url_for_static.py index 7b316aef16..20c9bdff13 100644 --- a/tests/test_url_for_static.py +++ b/tests/test_url_for_static.py @@ -77,10 +77,42 @@ def test_static_file(app, static_file_directory, file_name): uri = app.url_for('static', _external=True, _server='http://localhost') assert uri == 'http://localhost/testing.file' + uri = app.url_for('static', _external=True, _server='http://localhost', + _port=80) + assert uri == 'http://localhost/testing.file' + + uri = app.url_for('static', _external=True, _server='https://localhost', + _port=443) + assert uri == 'https://localhost/testing.file' + + uri = app.url_for('static', _external=True, _server='http://localhost', + _port=8080) + assert uri == 'http://localhost:8080/testing.file' + + uri = app.url_for('static', _external=True, _server='https://localhost', + _port=4433) + assert uri == 'https://localhost:4433/testing.file' + uri = app.url_for('static', name='test_bp_static.static', _external=True, _server='http://localhost') assert uri == 'http://localhost/bp/testing.file' + uri = app.url_for('static', name='test_bp_static.static', + _external=True, _server='http://localhost', _port=80) + assert uri == 'http://localhost/bp/testing.file' + + uri = app.url_for('static', name='test_bp_static.static', + _external=True, _server='https://localhost', _port=443) + assert uri == 'https://localhost/bp/testing.file' + + uri = app.url_for('static', name='test_bp_static.static', + _external=True, _server='http://localhost', _port=8080) + assert uri == 'http://localhost:8080/bp/testing.file' + + uri = app.url_for('static', name='test_bp_static.static', + _external=True, _server='https://localhost', _port=4433) + assert uri == 'https://localhost:4433/bp/testing.file' + # test for defined name uri = app.url_for('static', name='testing_file') assert uri == '/testing2.file'