@@ -46,8 +46,9 @@ class Map:
4646 arguments besides the `rules` as keyword arguments!
4747
4848 :param rules: sequence of url rules for this map.
49- :param default_subdomain: The default subdomain for rules without a
50- subdomain defined.
49+ :param default_subdomain: The default subdomain used by
50+ :meth:`bind_to_environ` and :meth:`bind` if the current subdomain can't
51+ be determined.
5152 :param strict_slashes: If a rule ends with a slash but the matched
5253 URL does not, redirect to the URL with a trailing slash.
5354 :param merge_slashes: Merge consecutive slashes when matching or
@@ -62,10 +63,16 @@ class Map:
6263 :param sort_parameters: If set to `True` the url parameters are sorted.
6364 See `url_encode` for more details.
6465 :param sort_key: The sort key function for `url_encode`.
65- :param host_matching: if set to `True` it enables the host matching
66- feature and disables the subdomain one. If
67- enabled the `host` parameter to rules is used
68- instead of the `subdomain` one.
66+ :param host_matching: Whether to route based on the full ``Host`` rather
67+ than subdomains of ``server_name``. Uses the ``host`` parameter for
68+ each :class:`Rule`.
69+ :param subdomain_matching: Whether to detect the subdomain from ``Host`` and
70+ ``server_name``, and route based on it. Uses the ``subdomain`` parameter
71+ of each :class:`Rule`. Enabled by default, but always disabled if
72+ ``host_matching`` is enabled.
73+
74+ .. versionchanged:: 3.1
75+ The ``subdomain_matching`` parameter was added.
6976
7077 .. versionchanged:: 3.0
7178 The ``charset`` and ``encoding_errors`` parameters were removed.
@@ -102,6 +109,8 @@ def __init__(
102109 sort_parameters : bool = False ,
103110 sort_key : t .Callable [[t .Any ], t .Any ] | None = None ,
104111 host_matching : bool = False ,
112+ * ,
113+ subdomain_matching : bool = True ,
105114 ) -> None :
106115 self ._matcher = StateMachineMatcher (merge_slashes )
107116 self ._rules_by_endpoint : dict [t .Any , list [Rule ]] = {}
@@ -111,6 +120,7 @@ def __init__(
111120 self .default_subdomain = default_subdomain
112121 self .strict_slashes = strict_slashes
113122 self .redirect_defaults = redirect_defaults
123+ self .subdomain_matching = subdomain_matching and not host_matching
114124 self .host_matching = host_matching
115125
116126 self .converters = self .default_converters .copy ()
@@ -255,42 +265,52 @@ def bind_to_environ(
255265 server_name : str | None = None ,
256266 subdomain : str | None = None ,
257267 ) -> MapAdapter :
258- """Like :meth:`bind` but you can pass it an WSGI environment and it
259- will fetch the information from that dictionary. Note that because of
260- limitations in the protocol there is no way to get the current
261- subdomain and real `server_name` from the environment. If you don't
262- provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or
263- `HTTP_HOST` if provided) as used `server_name` with disabled subdomain
264- feature.
265-
266- If `subdomain` is `None` but an environment and a server name is
267- provided it will calculate the current subdomain automatically.
268- Example: `server_name` is ``'example.com'`` and the `SERVER_NAME`
269- in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated
270- subdomain will be ``'staging.dev'``.
271-
272- If the object passed as environ has an environ attribute, the value of
273- this attribute is used instead. This allows you to pass request
274- objects. Additionally `PATH_INFO` added as a default of the
275- :class:`MapAdapter` so that you don't have to pass the path info to
276- the match method.
268+ """Call :meth:`bind` with information from the WSGI environ.
269+ ``PATH_INFO`` is used so it doesn't need to be passed to
270+ :meth:`~.MapAdapter.match`.
271+
272+ The WSGI environ does not have information to determine what subdomain
273+ was accessed, so ``server_name`` or ``subdomain`` must be passed in for
274+ :attr:`subdomain_matching`. For example, if ``Host`` is
275+ ``abc.example.test`` and ``server_name`` is ``example.test``,
276+ ``subdomain`` is determined to be ``abc``. If the ``server_name`` is not
277+ a suffix of the current ``Host``, then :attr:`default_subdomain` or
278+ ``"<invalid>"`` is used.
279+
280+ :param environ: The WSGI environ for the request. Can also be a
281+ ``Request`` with an ``environ`` attribute.
282+ :param server_name: When subdomain matching is enabled and ``subdomain``
283+ is not given, the subdomain is determined by removing this
284+ ``host:port`` as a suffix from the request's ``Host``. If the scheme
285+ is ``http``, ``https``, ``ws``, or ``wss``, the corresponding port
286+ 80 or 443 will be removed.
287+ :param subdomain: Route using this subdomain rather than determining it
288+ using ``server_name``.
289+
290+ .. versionchanged:: 3.2
291+ If ``server_name`` is not a suffix of ``Host``,
292+ :attr:`default_subdomain` is used if set, rather than always
293+ ``"<invalid>"``.
294+
295+ .. versionchanged:: 3.2
296+ ``subdomain_matching`` can be disabled.
297+
298+ .. versionchanged:: 3.2
299+ ``server_name`` is ignored if ``host_matching`` is enabled.
277300
278301 .. versionchanged:: 1.0.0
279- If the passed server name specifies port 443, it will match
280- if the incoming scheme is ``https`` without a port.
302+ If ``server_name`` specifies port 443, it will match if the scheme
303+ is ``https`` and ``Host`` does not specify a port.
281304
282- .. versionchanged:: 1.0.0
283- A warning is shown when the passed server name does not
284- match the incoming WSGI server name.
305+ .. versionchanged:: 1.0
306+ A warning is shown when ``server_name`` is not a suffix of ``Host``.
285307
286308 .. versionchanged:: 0.8
287- This will no longer raise a ValueError when an unexpected server
288- name was passed .
309+ ``"<invalid>"`` is used as the subdomain if ``server_name`` is not a
310+ suffix of ``Host``, rather than raising ``ValueError`` .
289311
290312 .. versionchanged:: 0.5
291- previously this method accepted a bogus `calculate_subdomain`
292- parameter that did not have any effect. It was removed because
293- of that.
313+ Removed the ``calculate_subdomain`` parameter which was not used.
294314
295315 :param environ: a WSGI environment.
296316 :param server_name: an optional server name hint (see above).
@@ -307,7 +327,7 @@ def bind_to_environ(
307327 if upgrade and env .get ("HTTP_UPGRADE" , "" ).lower () == "websocket" :
308328 scheme = "wss" if scheme == "https" else "ws"
309329
310- if server_name is None :
330+ if server_name is None or self . host_matching :
311331 server_name = wsgi_server_name
312332 else :
313333 server_name = server_name .lower ()
@@ -318,24 +338,24 @@ def bind_to_environ(
318338 elif scheme in {"https" , "wss" } and server_name .endswith (":443" ):
319339 server_name = server_name [:- 4 ]
320340
321- if subdomain is None and not self .host_matching :
341+ if subdomain is None and self . subdomain_matching and not self .host_matching :
322342 cur_server_name = wsgi_server_name .split ("." )
323343 real_server_name = server_name .split ("." )
324344 offset = - len (real_server_name )
325345
326346 if cur_server_name [offset :] != real_server_name :
327- # This can happen even with valid configs if the server was
328- # accessed directly by IP address under some situations.
329- # Instead of raising an exception like in Werkzeug 0.7 or
330- # earlier we go by an invalid subdomain which will result
331- # in a 404 error on matching.
347+ # Host does not have server_name as a suffix. This can happen if
348+ # the server was accessed by IP, or other names point to it in
349+ # DNS. Use a placeholder subdomain, which can result in a 404 on
350+ # matching or be detected in a wildcard endpoint.
332351 warnings .warn (
333352 f"Current server name { wsgi_server_name !r} doesn't match configured"
334353 f" server name { server_name !r} " ,
335354 stacklevel = 2 ,
336355 )
337- subdomain = "<invalid>"
356+ subdomain = self . default_subdomain or "<invalid>"
338357 else :
358+ # Remove server_name as a suffix from Host to get the subdomain.
339359 subdomain = "." .join (filter (None , cur_server_name [:offset ]))
340360
341361 def _get_wsgi_string (name : str ) -> str | None :
0 commit comments