فهرست مقاله
وقتی دو نامِ میزبان به یک ورودیِ شبکه میرسند، انتظارِ «قاعدهٔ بلندتر حتماً برنده است» میتواند عیبیابی را به مسیرِ نادرست ببرد. نامِ دقیق، wildcard آغازین و wildcard پایانی در یک مسابقهٔ طولِ مشترک نیستند. این درس برای بازبینیِ همین تصمیم، یک آزمایشِ کوچکِ پایتونی میسازد: نامها و قواعد را صریح میدهیم و میپرسیم کدام قاعده در زیرمجموعهٔ تعریفشده انتخاب میشود. نه Nginx اجرا میشود، نه پیکربندی تولید میکنیم و نه نتیجه را رفتارِ مشاهدهشدهٔ یک سایت مینامیم.
۱. انتخاب نام میزبان، پیش از بررسی مسیر

در یک مسئلهٔ مهندسی بهتر است ابتدا بدانیم تصمیم در کدام مرحله گرفته میشود. راهنمای رسمیِ پردازشِ درخواست در Nginx انتخابِ نشانی و درگاهِ listen را از تطبیقِ نام جدا میکند. اینجا فرض میکنیم مجموعهٔ مربوط به همان ورودی از قبل مشخص شده است. تابع فقط نامِ ازپیشمعیارشده و قواعدِ همان مجموعه را میبیند؛ نشانیِ مقصد، درگاه، خطِ درخواست و سرآیندِ خام را دریافت نمیکند.
هر قاعده یک الگو و یک برچسبِ مقصد دارد. مقصدهایی مانند broad و exact صرفاً نامِ محفظههای ساختگیِ آزمایشاند؛ خدمتِ فعال یا نشانیِ upstream نیستند. برای هر میزبان یک رکوردِ تغییرناپذیر برمیگردانیم که نامِ ورودی، الگوی برگزیده، مقصد و ردهٔ تطبیق را دارد. ترتیبِ پاسخها همان ترتیبِ میزبانهای ورودی است. تکرارِ یک میزبان مجاز است و حذفِ خودکارِ رخدادها نداریم.
خواندنِ نام را نیز با حلکردنِ آن یکی نمیگیریم. در درسِ قالبِ نام و اشارهگرهای DNS، مسئله خواندنِ چند بایت و جداکردنِ طولِ مصرفشده از نامِ بازشده بود. این تابع حتی آن کار را هم نمیکند: رشتهٔ آماده میگیرد و میان قواعد انتخاب میکند. چنین تفکیکی کمک میکند خطای قالب، انتخابِ نام و پاسخِ خدمت را بیدلیل به یک علت نسبت ندهیم.
۲. نام دقیق و دو خانوادهٔ wildcard

سندِ رسمیِ Server names ترتیبِ جستوجو را بیان میکند: نامِ دقیق، بلندترین wildcard آغازشده با ستاره، بلندترین wildcard پایانیافته با ستاره و سپس نخستین عبارتِ منظمِ مطابق. برنامهٔ حاضر فقط سه ردهٔ نخست را مدل میکند. بنابراین وجودِ هر تطبیقِ آغازین، بررسیِ برندهٔ پایانی را کنار میزند؛ حتی اگر متنِ الگوی پایانی بلندتر باشد.
برای نمونه، *.lab.test با x.lab.test و x.api.lab.test تطبیق دارد، اما با lab.test ندارد. ستاره باید دستکم یک برچسبِ کامل را بگیرد و میتواند چند برچسب را پوشش دهد. از سوی دیگر badlab.test با وجودِ پایانِ ظاهراً مشابه، مرزِ نقطهٔ لازم را ندارد. در الگوی api.* نیز api.lab و api.lab.test ممکناند، ولی خودِ api کافی نیست. این دو جهت را با یک آزمونِ مبهمِ «رشته شاملِ متن است» جایگزین نمیکنیم.
درونِ هر رده، بلندترین الگوی مطابق انتخاب میشود. برای x.api.lab.test، قاعدهٔ *.api.lab.test از *.lab.test مشخصتر است. اما برای api.very.long.test، اگر فقط *.test و api.very.long.* را داشته باشیم، اولی برنده است. مقایسهٔ طول پس از انتخابِ رده انجام میشود، نه پیش از آن. شکلِ ویژهٔ .example.org که در سند برای نامِ دقیق و wildcard با هم توضیح داده شده، عمداً در رابطِ ما پشتیبانی نمیشود.
۳. یک شاهد دستی و مرز حالتِ بیتطبیق

شاهدِ اصلی شش قاعده دارد: *.lab.test، سپس *.api.lab.test، دو الگوی api.* و api.lab.*، و دو نامِ دقیقِ api.lab.test و lab.test. مقصدِ هر سطر یک برچسبِ جداست. هشت میزبان را میدهیم تا تقدمِ دقیق، دو طولِ wildcard، زیرنامِ چندبخشی، نامِ رأس و مرزِ اشتباه را کنار هم ببینیم؛ این رشتهها دادهٔ مصنوعیاند و از ترافیک یا تنظیماتِ یک سرور استخراج نشدهاند.
انتظارِ دستی برای api.lab.test همان نامِ دقیق است؛ x.api.lab.test قاعدهٔ آغازینِ بلندتر را میگیرد. api.other.test به api.* و api.lab.other به api.lab.* میرسد. lab.test سطرِ دقیقِ خودش را دارد؛ badlab.test در این مجموعه بیتطبیق است. برای x.lab.test قاعدهٔ آغازینِ کوتاهتر کافی است، ولی api.api.lab.test دوباره با قاعدهٔ آغازینِ بلندتر انتخاب میشود.
بیتطبیق را صریحاً با الگو و مقصدِ None و ردهٔ unmatched گزارش میکنیم. این خروجی به معنیِ ردشدنِ درخواست در Nginx نیست. سند توضیح میدهد default_server ویژگیِ listen است، نه نامی جادویی در server_name؛ فرایندِ واقعی مرحلههای دیگری هم دارد. همچنین در درسِ GET شرطی و ETag، بازنمایی از قبل انتخابشده فرض میشد. اینجا هنوز دربارهٔ نسخه، تازگی یا بدنهٔ آن بازنمایی تصمیمی نگرفتهایم.
۴. برنامهٔ کامل و مرجع رابطهای مستقل

رابط فقط tuple دقیقِ حداکثر هشت قاعده و هشت میزبان را میپذیرد. هر سطر نیز یک زوجِ tuple واقعی است. نامها یک تا چهار برچسب دارند؛ هر برچسب فقط یک تا شش حرفِ کوچکِ ASCII است. در الگوی wildcard، قسمتِ ثابت حداکثر سه برچسب دارد. این دستورِ کوچک، انتخابِ آموزشیِ ماست؛ نه فهرستِ همهٔ نامهای مجازِ DNS یا Nginx. بزرگکردنِ حروف، خطِ تیره، رقم، IP، یونیکد، درگاه و نشانیِ کامل در آن پذیرفته نیست.
تمامِ قواعد و میزبانها پیش از نخستین تطبیق و ساختِ نتیجه بررسی میشوند. الگوی تکراری خطاست تا اولویتِ پنهانِ وابسته به سطر نسازیم؛ چند الگوی متفاوت با یک مقصد مجاز است. قاعدهٔ نامعتبرِ آخر حتی با وجودِ یک تطبیقِ دقیقِ قبلی رد میشود. این کنترل روی اشیای دریافتشدهٔ محدود اعمال میشود و وعدهٔ ایمنی برای هر ورودی، ترد یا سرویسِ خارجی نیست.
مسیرِ اصلی از برابری و مرزِ نقطه در پیشوند یا پسوندِ رشته استفاده میکند. مرجعِ مستقل، نام را به واژهای از برچسبها تبدیل میکند و رابطهٔ گسترش با دستکم یک برچسب را میسازد. سپس عضویتِ قسمتِ ثابتِ الگو را در رابطهٔ سمتِ مناسب میسنجد. رده جدا انتخاب میشود و درونِ آن تعدادِ برچسبهای ثابت مقایسه میشود؛ نامهای ثابتِ مطابق یک زنجیرهٔ تودرتو میسازند، پس افزایشِ برچسب در این دامنه همان بلندترشدنِ متن را میدهد.
کد را با نامِ nginx_server_name_priority.py ذخیره کنید. اجرای هدف CPython3.14.5 با کتابخانهٔ استاندارد است و نسخهٔ واقعی در گزارش ثبت میشود. هیچ بسته یا اجرایی از Nginx فراخوانی نمیشود. خانوادهٔ متناهیِ مرجع، تمامِ زیرمجموعههای هشت الگوی اعلامشده را با نامهای یک تا چهاربرچسبی روی دو حرف مقایسه میکند. تمامِ این خانواده تنها یک گروهِ نامدار است؛ شمارِ نمایهها و پرسشهای واقعاً پیمودهشده جدا گزارش میشود.
"""Authored supplied-name selection only; no NGINX, config, HTTP or network."""
from dataclasses import asdict, dataclass, FrozenInstanceError
from itertools import permutations, product
import json
import platform
import re
import sys
AUTHOR_MAIN_ENABLED = True
MAX_RULES = 8
MAX_HOSTS = 8
LABEL = re.compile(r"[a-z]{1,6}", flags=re.ASCII)
@dataclass(frozen=True)
class Choice:
host: str
pattern: str | None
target: str | None
kind: str
def _literal(value, maximum_labels):
if type(value) is not str or not 1 <= len(value) <= 27:
raise ValueError("exact_literal_type_or_length")
parts = value.split(".")
if not 1 <= len(parts) <= maximum_labels:
raise ValueError("literal_label_count")
if any(LABEL.fullmatch(part) is None for part in parts):
raise ValueError("declared_lowercase_label_grammar")
def _admit(rules, hosts):
if type(rules) is not tuple or len(rules) > MAX_RULES:
raise ValueError("exact_rule_tuple_or_count")
if type(hosts) is not tuple or len(hosts) > MAX_HOSTS:
raise ValueError("exact_host_tuple_or_count")
normalized = []
seen = set()
for row in rules:
if type(row) is not tuple or len(row) != 2:
raise ValueError("rule_pair")
pattern, target = row
if type(pattern) is not str or not 1 <= len(pattern) <= 27:
raise ValueError("exact_pattern_type_or_length")
if type(target) is not str or not 1 <= len(target) <= 8:
raise ValueError("exact_target_type_or_length")
if any(char not in "abcdefghijklmnopqrstuvwxyz" for char in target):
raise ValueError("declared_target_label")
if pattern in seen:
raise ValueError("duplicate_pattern")
seen.add(pattern)
if pattern.startswith("*."):
kind, literal = "leading", pattern[2:]
_literal(literal, 3)
elif pattern.endswith(".*"):
kind, literal = "trailing", pattern[:-2]
_literal(literal, 3)
else:
kind, literal = "exact", pattern
_literal(literal, 4)
normalized.append((pattern, target, kind, literal))
for host in hosts:
_literal(host, 4)
return tuple(normalized)
def _eligible(host, kind, literal):
if kind == "exact":
return host == literal
if kind == "leading":
return host.endswith("." + literal)
return host.startswith(literal + ".")
def select_server_names(rules, hosts):
"""Select a supplied name rule, not a server/default context in NGINX.
Every rule/host is admitted before matching or constructing a Choice.
Priority is exact, longest leading wildcard, longest trailing wildcard.
A star consumes at least one whole label. Unmatched is explicit and does
not model default_server, regex, SNI, request parsing or rejection.
"""
admitted = _admit(rules, hosts)
classes = {"exact": 0, "leading": 1, "trailing": 2}
output = []
for host in hosts:
candidates = [row for row in admitted
if _eligible(host, row[2], row[3])]
if candidates:
pattern, target, kind, _ = min(
candidates, key=lambda row: (classes[row[2]], -len(row[0])))
output.append(Choice(host, pattern, target, kind))
else:
output.append(Choice(host, None, None, "unmatched"))
return tuple(output)
def label_relation_reference(rules, hosts):
"""Known-valid expected path using whole label-word extension relations.
Does not use the main matcher, validator, Choice construction, string
startswith/endswith, or length-of-pattern ranking. In a matching class,
eligible literals form a strict nested chain; more literal labels means
the longer wildcard spelling in this admitted grammar.
"""
result = []
for host in hosts:
word = tuple(host.split("."))
suffix_relation = {word[cut:] for cut in range(1, len(word))}
prefix_relation = {word[:cut] for cut in range(1, len(word))}
eligible = []
for pattern, target in rules:
pieces = tuple(pattern.split("."))
if pieces[0] == "*":
literal = pieces[1:]
if literal in suffix_relation:
eligible.append((1, -len(literal), pattern, target, "leading"))
elif pieces[-1] == "*":
literal = pieces[:-1]
if literal in prefix_relation:
eligible.append((2, -len(literal), pattern, target, "trailing"))
elif pieces == word:
eligible.append((0, 0, pattern, target, "exact"))
if eligible:
_, _, pattern, target, kind = min(eligible)
result.append((host, pattern, target, kind))
else:
result.append((host, None, None, "unmatched"))
return tuple(result)
def _plain(rows):
return tuple((x.host, x.pattern, x.target, x.kind) for x in rows)
def _rejects(rules, hosts):
try:
select_server_names(rules, hosts)
except ValueError:
return True
return False
def run_example():
checks = {}
def check(name, condition):
if type(condition) is not bool or not condition:
raise AssertionError(name)
checks[name] = condition
rules = (("*.lab.test", "broad"), ("*.api.lab.test", "deep"),
("api.*", "start"), ("api.lab.*", "long"),
("api.lab.test", "exact"), ("lab.test", "apex"))
hosts = ("api.lab.test", "x.api.lab.test", "api.other.test",
"api.lab.other", "lab.test", "badlab.test",
"x.lab.test", "api.api.lab.test")
expected_patterns = ("api.lab.test", "*.api.lab.test", "api.*",
"api.lab.*", "lab.test", None,
"*.lab.test", "*.api.lab.test")
manual = select_server_names(rules, hosts)
check("manual_eight_names_six_rules",
tuple(x.pattern for x in manual) == expected_patterns
and _plain(manual) == label_relation_reference(rules, hosts))
exact_rules = (("api.*", "tail"), ("*.test", "head"),
("api.test", "exact"))
check("exact_beats_both_wildcard_classes",
select_server_names(exact_rules, ("api.test",))[0]
== Choice("api.test", "api.test", "exact", "exact"))
check("longest_leading_wildcard_in_its_class",
select_server_names((("*.test", "short"),
("*.api.test", "long")),
("x.api.test",))[0].pattern == "*.api.test"
and select_server_names((("*.test", "short"),
("*.a.test", "long")),
("long.a.test",))[0].pattern == "*.a.test")
check("longest_trailing_wildcard_in_its_class",
select_server_names((("api.*", "short"), ("api.lab.*", "long")),
("api.lab.x",))[0].pattern == "api.lab.*")
competing = (("api.very.long.*", "trailing"), ("*.test", "leading"))
priority = select_server_names(competing, ("api.very.long.test",))
check("priority_class_precedes_global_pattern_length",
priority[0].pattern == "*.test"
and len("api.very.long.*") > len("*.test")
and _plain(priority) == label_relation_reference(competing,
("api.very.long.test",)))
bounded = select_server_names((("*.lab.test", "head"),),
("lab.test", "badlab.test", "x.lab.test"))
check("whole_label_boundary_and_nonempty_star_exclude_apex",
tuple(x.pattern for x in bounded) == (None, None, "*.lab.test"))
check("star_consumes_one_or_several_whole_labels",
tuple(x.pattern for x in select_server_names(
(("*.test", "head"),), ("a.test", "a.b.test", "a.b.c.test")))
== ("*.test",) * 3
and select_server_names((("api.*", "tail"),),
("api.a.b.test",))[0].pattern == "api.*")
permutations_checked = 0
for arranged in permutations(rules):
if select_server_names(arranged, hosts) != manual:
raise AssertionError("rule order must not select a different rule")
permutations_checked += 1
check("all_manual_rule_permutations_preserve_selection",
permutations_checked == 720)
alias_rules = (("lab.test", "same"), ("*.lab.test", "same"),
("api.*", "same"))
aliases = select_server_names(alias_rules,
("lab.test", "x.lab.test", "api.other"))
check("distinct_alias_patterns_may_share_one_target",
tuple(x.target for x in aliases) == ("same",) * 3
and tuple(x.kind for x in aliases) == ("exact", "leading", "trailing"))
patterns = ("a", "a.a", "b.a", "*.a", "*.a.a",
"a.*", "a.a.*", "b.*")
universe = tuple(".".join(parts) for length in range(1, 5)
for parts in product(("a", "b"), repeat=length))
family_profiles = family_queries = family_batches = 0
for enabled in product((False, True), repeat=len(patterns)):
fixture = tuple((pattern, "route") for pattern, on
in zip(patterns, enabled, strict=True) if on)
for start in range(0, len(universe), MAX_HOSTS):
batch = universe[start:start + MAX_HOSTS]
if _plain(select_server_names(fixture, batch)) != label_relation_reference(
fixture, batch):
raise AssertionError("independent label relation family")
family_queries += len(batch)
family_batches += 1
family_profiles += 1
check("independent_label_relation_finite_family_ONE_group",
family_profiles == 256 and family_queries == 7680
and family_batches == 1024)
class StrChild(str):
def split(self, *args):
raise AssertionError("string hook must not run")
class TupleChild(tuple):
pass
consumed = []
def lazy_hosts():
consumed.append("pulled")
yield "lab.test"
base = (("lab.test", "route"),)
query = ("lab.test",)
invalid = (
(None, query), ([], query), (TupleChild(base), query),
(base, None), (base, []), (base, TupleChild(query)), (base, lazy_hosts()),
((["lab.test", "route"],), query),
((("lab.test",),), query), ((("lab.test", "route", "extra"),), query),
(((StrChild("lab.test"), "route"),), query),
((("lab.test", StrChild("route")),), query),
((("lab.test", 1),), query), ((("lab.test", ""),), query),
((("lab.test", "Route"),), query), ((("lab.test", "r1"),), query),
(base + base, query),
((("*.lab.*", "route"),), query), ((("w*.test", "route"),), query),
((("www.*.test", "route"),), query), ((("*", "route"),), query),
(((".lab.test", "route"),), query), ((("", "route"),), query),
((("~lab", "route"),), query), ((("$name", "route"),), query),
((("LAB.test", "route"),), query), ((("a..test", "route"),), query),
((("127.0.0.1", "route"),), query), ((("lab.test.", "route"),), query),
(base, (StrChild("lab.test"),)), (base, ("lab.test:80",)),
(base, ("https://lab.test",)), (base, ("a-b.test",)),
(base, ("a\u00e9.test",)), (base, ("",)),
(base + (("bad*.test", "later"),), query),
(base, query + ("bad..test",)),
)
entered = []
old_matcher, old_choice = globals()["_eligible"], globals()["Choice"]
def forbidden(*args, **kwargs):
entered.append("entered")
raise AssertionError("invalid entire declaration must precede matching/results")
try:
globals()["_eligible"] = forbidden
globals()["Choice"] = forbidden
rejected = all(_rejects(a, b) for a, b in invalid)
finally:
globals()["_eligible"], globals()["Choice"] = old_matcher, old_choice
check("whole_late_validation_precedes_match_and_result_construction",
rejected and entered == [] and consumed == [])
longest = ".".join(("a" * 6, "b" * 6, "c" * 6, "d" * 6))
cap_rules = ((longest, "a" * 8),) + tuple((c, "route") for c in "abcdefg")
bad_bounds = (
(cap_rules + (("h", "route"),), query),
(base, query * 9),
((("a" * 7, "route"),), query),
((("a.b.c.d.e", "route"),), query),
((("*.a.b.c.d", "route"),), query),
((("a.b.c.d.*", "route"),), query),
((("a", "r" * 9),), query),
(base, ("a" * 7 + ".test",)),
)
cap_output = select_server_names(cap_rules, (longest,) * 8)
check("inclusive_rule_host_label_and_target_bounds",
len(longest) == 27 and len(cap_output) == 8
and all(x.target == "a" * 8 for x in cap_output)
and all(_rejects(a, b) for a, b in bad_bounds))
check("empty_batches_and_explicit_unmatched",
select_server_names(base, ()) == ()
and select_server_names((), query)
== (Choice("lab.test", None, None, "unmatched"),)
and select_server_names((), ()) == ())
before = repr((rules, hosts))
first = select_server_names(rules, hosts)
select_server_names((("other", "else"),), ("other",))
second = select_server_names(rules, hosts)
frozen = False
try:
first[0].target = "changed"
except FrozenInstanceError:
frozen = True
check("frozen_fresh_results_and_input_preservation",
first == second == manual and first is not second
and all(a is not b for a, b in zip(first, second, strict=True))
and type(first) is tuple and frozen and repr((rules, hosts)) == before)
assert len(checks) == 14 and all(type(v) is bool and v for v in checks.values())
return {
"runtime": {"implementation": platform.python_implementation(),
"python": platform.python_version()},
"scope": "Authored supplied canonical name-rule subset only; not NGINX execution",
"manual_rules": rules, "manual_hosts": hosts,
"manual_choices": [asdict(row) for row in manual],
"class_before_length": {"rules": competing,
"choice": asdict(priority[0])},
"independent_family_ONE_group": {"rule_profiles": family_profiles,
"host_queries": family_queries, "API_batches": family_batches},
"manual_rule_permutations_ONE_group": permutations_checked,
"invalid_profiles_ONE_group": len(invalid),
"invalid_bound_profiles_ONE_group": len(bad_bounds),
"invalid_match_or_result_entries": len(entered),
"arbitrary_iterator_items_consumed": len(consumed),
"limits": {"rules": MAX_RULES, "hosts": MAX_HOSTS,
"host_labels": [1, 4], "wildcard_literal_labels": [1, 3],
"label_characters": [1, 6], "maximum_host_characters": 27,
"target_characters": [1, 8]},
"nginx_executed": False, "network_or_configuration_changed": False,
"unimplemented": ["Regex/config order, leading-dot shorthand and variables",
"Listen/default_server, SNI/TLS, raw Host parsing/normalization",
"IP/IDNA/PSL, URI locations, proxy deployment or security approval",
"NGINX hashtable performance and universal correctness proof"],
"checks": checks, "groups_total": len(checks), "all_passed": True,
}
if __name__ == "__main__":
if not AUTHOR_MAIN_ENABLED:
raise SystemExit("Unexecuted private work draft; ROOT author GO required")
sys.stdout.reconfigure(encoding="utf-8", newline="\n")
sys.stdout.write(json.dumps(run_example(), ensure_ascii=True,
allow_nan=False, indent=2) + "\n")
۵. نتیجهٔ قابل تکرار و چهار پرسش پاسخدار

چهارده گروه، شاهدِ دستی، تقدمِ سه رده، طولِ درونرده، مرزِ کامل، ستارهٔ چندبرچسبی، جابهجاییِ قواعد، نامهای مستعار، مرجعِ مستقل، اعتبارسنجیِ کامل، سقفها و استقلالِ نتیجهها را در بر میگیرند. خروجیِ زیر جای ثبتِ اجرای واقعیِ همین فایل است؛ تعدادِ حالتهای برنامه را به تعدادِ گروهها اضافه نمیکنیم. هیچ اندازهٔ زمان، حافظه، سرعتِ سرور یا برتریِ کارایی از این مثال به دست نمیآید.
{
"runtime": {
"implementation": "CPython",
"python": "3.14.5"
},
"scope": "Authored supplied canonical name-rule subset only; not NGINX execution",
"manual_rules": [
[
"*.lab.test",
"broad"
],
[
"*.api.lab.test",
"deep"
],
[
"api.*",
"start"
],
[
"api.lab.*",
"long"
],
[
"api.lab.test",
"exact"
],
[
"lab.test",
"apex"
]
],
"manual_hosts": [
"api.lab.test",
"x.api.lab.test",
"api.other.test",
"api.lab.other",
"lab.test",
"badlab.test",
"x.lab.test",
"api.api.lab.test"
],
"manual_choices": [
{
"host": "api.lab.test",
"pattern": "api.lab.test",
"target": "exact",
"kind": "exact"
},
{
"host": "x.api.lab.test",
"pattern": "*.api.lab.test",
"target": "deep",
"kind": "leading"
},
{
"host": "api.other.test",
"pattern": "api.*",
"target": "start",
"kind": "trailing"
},
{
"host": "api.lab.other",
"pattern": "api.lab.*",
"target": "long",
"kind": "trailing"
},
{
"host": "lab.test",
"pattern": "lab.test",
"target": "apex",
"kind": "exact"
},
{
"host": "badlab.test",
"pattern": null,
"target": null,
"kind": "unmatched"
},
{
"host": "x.lab.test",
"pattern": "*.lab.test",
"target": "broad",
"kind": "leading"
},
{
"host": "api.api.lab.test",
"pattern": "*.api.lab.test",
"target": "deep",
"kind": "leading"
}
],
"class_before_length": {
"rules": [
[
"api.very.long.*",
"trailing"
],
[
"*.test",
"leading"
]
],
"choice": {
"host": "api.very.long.test",
"pattern": "*.test",
"target": "leading",
"kind": "leading"
}
},
"independent_family_ONE_group": {
"rule_profiles": 256,
"host_queries": 7680,
"API_batches": 1024
},
"manual_rule_permutations_ONE_group": 720,
"invalid_profiles_ONE_group": 37,
"invalid_bound_profiles_ONE_group": 8,
"invalid_match_or_result_entries": 0,
"arbitrary_iterator_items_consumed": 0,
"limits": {
"rules": 8,
"hosts": 8,
"host_labels": [
1,
4
],
"wildcard_literal_labels": [
1,
3
],
"label_characters": [
1,
6
],
"maximum_host_characters": 27,
"target_characters": [
1,
8
]
},
"nginx_executed": false,
"network_or_configuration_changed": false,
"unimplemented": [
"Regex/config order, leading-dot shorthand and variables",
"Listen/default_server, SNI/TLS, raw Host parsing/normalization",
"IP/IDNA/PSL, URI locations, proxy deployment or security approval",
"NGINX hashtable performance and universal correctness proof"
],
"checks": {
"manual_eight_names_six_rules": true,
"exact_beats_both_wildcard_classes": true,
"longest_leading_wildcard_in_its_class": true,
"longest_trailing_wildcard_in_its_class": true,
"priority_class_precedes_global_pattern_length": true,
"whole_label_boundary_and_nonempty_star_exclude_apex": true,
"star_consumes_one_or_several_whole_labels": true,
"all_manual_rule_permutations_preserve_selection": true,
"distinct_alias_patterns_may_share_one_target": true,
"independent_label_relation_finite_family_ONE_group": true,
"whole_late_validation_precedes_match_and_result_construction": true,
"inclusive_rule_host_label_and_target_bounds": true,
"empty_batches_and_explicit_unmatched": true,
"frozen_fresh_results_and_input_preservation": true
},
"groups_total": 14,
"all_passed": true
}
پرسش اول: آیا *.lab.test خودِ lab.test را میگیرد؟ پاسخ: خیر؛ ستاره دستکم یک برچسبِ کامل میخواهد. برای رأس باید نامِ دقیقِ جدا داشته باشیم یا در سامانهٔ واقعی، شکلِ ویژهای را بررسی کنیم که این API اجرا نمیکند. badlab.test نیز زیرنامِ lab.test نیست.
پرسش دوم: چرا الگوی بلندترِ api.very.long.* نمیبرد؟ پاسخ: برای api.very.long.test، وجودِ *.test ردهٔ آغازین را برنده میکند. فقط میان دو الگوی آغازینِ مطابق یا دو الگوی پایانیِ مطابق طول را میسنجیم. مرتبسازیِ یکبارهٔ همهٔ الگوها بر اساسِ طول، همین شاهد را خراب میکند.
پرسش سوم: آیا جابهجاییِ قواعد نتیجه را عوض میکند؟ پاسخ: در زیرمجموعهٔ حاضر با الگوهای یکتا خیر. نخستینتطبیقِ عبارتِ منظم در سند، قاعدهٔ ردهٔ دیگری است که حذف کردهایم. این خاصیت را به یک پیکربندیِ دارای regex، تکرار، گوشدادن روی چند درگاه یا وراثت تعمیم نمیدهیم.
پرسش چهارم: آیا گذشتنِ بررسیها انتخابِ واقعیِ سرور را تأیید میکند؟ پاسخ: خیر؛ فقط تابعِ انتخابِ نام در قراردادِ صریح آزموده شده است. SNI، پردازشِ Host، default_server، انتخابِ location، TLS و اجرای خدمت مستقلاند. دو سندِ رسمی از یک پروژهاند و تأییدِ دو تولیدکنندهٔ مستقل محسوب نمیشوند. تاریخِ دسترسی ۳۰ مهر ۱۴۰۵ است؛ تاریخِ انتشارِ نخست معلوم نیست و زمانِ تغییرِ HTTP نیز جای آن را نمیگیرد. متنهای مرتبط از نسخههای ذخیرهشدهٔ قبلاً منتشرشده خوانده شدهاند.
منابع و مطالعهٔ بیشتر
- NGINX؛ سند کاملِ Server names و تقدمِ نامهای دقیق و wildcard
- NGINX؛ سند کاملِ پردازش درخواست و تفکیکِ listen از نام میزبان
بررسی منابع: ۳۰ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

