{"id":62984,"date":"2026-01-21T03:25:44","date_gmt":"2026-01-20T19:25:44","guid":{"rendered":"https:\/\/www.wsisp.com\/helps\/62984.html"},"modified":"2026-01-21T03:25:44","modified_gmt":"2026-01-20T19:25:44","slug":"http-%e7%8a%b6%e6%80%81%e7%a0%81%ef%bc%9a%e5%ae%a2%e6%88%b7%e7%ab%af%e4%b8%8e%e6%9c%8d%e5%8a%a1%e5%99%a8%e7%9a%84%e9%80%9a%e4%bf%a1%e8%af%ad%e8%a8%80-%e7%ac%ac%e5%9b%9b%e9%83%a8-3","status":"publish","type":"post","link":"https:\/\/www.wsisp.com\/helps\/62984.html","title":{"rendered":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u56db\u90e8\u5206\uff1a\u5ba2\u6237\u7aef\u9519\u8bef\u72b6\u6001\u7801\uff084xx\uff09\u6df1\u5ea6\u89e3\u8bfb\uff08\u4e09\uff09"},"content":{"rendered":"<h2>\u7b2c22\u7ae0&#xff1a;429 Too Many Requests &#8211; \u901f\u7387\u9650\u5236\u5b9e\u73b0\u6df1\u5ea6\u5206\u6790<\/h2>\n<h3>22.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h3>\n<p>429 Too Many Requests\u00a0\u72b6\u6001\u7801\u8868\u793a\u7528\u6237\u5728\u7ed9\u5b9a\u65f6\u95f4\u5185\u53d1\u9001\u4e86\u592a\u591a\u8bf7\u6c42&#xff08;&#034;\u901f\u7387\u9650\u5236&#034;&#xff09;\u3002\u8be5\u72b6\u6001\u7801\u7684\u6838\u5fc3\u542b\u4e49\u662f&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u5ba2\u6237\u7aef\u8bf7\u6c42\u9891\u7387\u8d85\u8fc7\u670d\u52a1\u5668\u9650\u5236<\/p>\n<\/li>\n<li>\n<p>\u670d\u52a1\u5668\u6682\u65f6\u62d2\u7edd\u5904\u7406\u5f53\u524d\u8bf7\u6c42<\/p>\n<\/li>\n<li>\n<p>\u5ba2\u6237\u7aef\u5e94\u8be5\u5728\u7b49\u5f85\u4e00\u6bb5\u65f6\u95f4\u540e\u91cd\u8bd5<\/p>\n<\/li>\n<\/ul>\n<p>\u5173\u952e\u7279\u6027&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u901f\u7387\u9650\u5236&#xff1a;\u9650\u5236\u5355\u4f4d\u65f6\u95f4\u5185\u7684\u8bf7\u6c42\u6570\u91cf<\/p>\n<\/li>\n<li>\n<p>\u53ef\u6062\u590d\u9519\u8bef&#xff1a;\u7b49\u5f85\u4e00\u6bb5\u65f6\u95f4\u540e\u53ef\u6062\u590d\u6b63\u5e38<\/p>\n<\/li>\n<li>\n<p>\u5305\u542b\u91cd\u8bd5\u4fe1\u606f&#xff1a;\u54cd\u5e94\u901a\u5e38\u5305\u542b\u4f55\u65f6\u53ef\u91cd\u8bd5\u7684\u6307\u793a<\/p>\n<\/li>\n<\/ul>\n<p>\u534f\u8bae\u8981\u6c42&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u5e94\u5305\u542b\u63cf\u8ff0\u9650\u5236\u8be6\u60c5\u7684\u54cd\u5e94\u4f53<\/p>\n<\/li>\n<li>\n<p>\u53ef\u4ee5\u5305\u542b\u00a0Retry-After\u00a0\u5934\u90e8\u6307\u793a\u91cd\u8bd5\u65f6\u95f4<\/p>\n<\/li>\n<li>\n<p>\u53ef\u80fd\u5305\u542b\u901f\u7387\u9650\u5236\u914d\u989d\u4fe1\u606f<\/p>\n<\/li>\n<\/ul>\n<h3>22.2 \u901f\u7387\u9650\u5236\u7b56\u7565\u5206\u7c7b<\/h3>\n<h4>22.2.1 \u5e38\u89c1\u901f\u7387\u9650\u5236\u7b97\u6cd5<\/h4>\n<p>python<\/p>\n<p># \u901f\u7387\u9650\u5236\u7b97\u6cd5\u5206\u7c7b\u4e0e\u5b9e\u73b0<br \/>\nfrom abc import ABC, abstractmethod<br \/>\nimport time<br \/>\nfrom typing import Dict, List, Optional, Tuple<br \/>\nimport math<br \/>\nfrom collections import deque<br \/>\nimport threading<\/p>\n<p>class RateLimiter(ABC):<br \/>\n    &#034;&#034;&#034;\u901f\u7387\u9650\u5236\u5668\u62bd\u8c61\u57fa\u7c7b&#034;&#034;&#034;<\/p>\n<p>    &#064;abstractmethod<br \/>\n    def is_allowed(self, key: str, weight: int &#061; 1) -&gt; Tuple[bool, Dict]:<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u662f\u5426\u5141\u8bb8\u8bf7\u6c42&#034;&#034;&#034;<br \/>\n        pass<\/p>\n<p>    &#064;abstractmethod<br \/>\n    def get_remaining(self, key: str) -&gt; int:<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u5269\u4f59\u914d\u989d&#034;&#034;&#034;<br \/>\n        pass<\/p>\n<p>    &#064;abstractmethod<br \/>\n    def get_reset_time(self, key: str) -&gt; float:<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u91cd\u7f6e\u65f6\u95f4&#034;&#034;&#034;<br \/>\n        pass<\/p>\n<p>class TokenBucketLimiter(RateLimiter):<br \/>\n    &#034;&#034;&#034;\u4ee4\u724c\u6876\u7b97\u6cd5\u5b9e\u73b0&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, capacity: int, refill_rate: float):<br \/>\n        &#034;&#034;&#034;<br \/>\n        Args:<br \/>\n            capacity: \u6876\u5bb9\u91cf<br \/>\n            refill_rate: \u6bcf\u79d2\u8865\u5145\u7684\u4ee4\u724c\u6570<br \/>\n        &#034;&#034;&#034;<br \/>\n        self.capacity &#061; capacity<br \/>\n        self.refill_rate &#061; refill_rate<br \/>\n        self.buckets: Dict[str, Dict] &#061; {}<br \/>\n        self.lock &#061; threading.RLock()<\/p>\n<p>    def is_allowed(self, key: str, weight: int &#061; 1) -&gt; Tuple[bool, Dict]:<br \/>\n        with self.lock:<br \/>\n            current_time &#061; time.time()<\/p>\n<p>            # \u83b7\u53d6\u6216\u521b\u5efa\u6876<br \/>\n            if key not in self.buckets:<br \/>\n                self.buckets[key] &#061; {<br \/>\n                    &#039;tokens&#039;: self.capacity,<br \/>\n                    &#039;last_refill&#039;: current_time<br \/>\n                }<\/p>\n<p>            bucket &#061; self.buckets[key]<\/p>\n<p>            # \u8865\u5145\u4ee4\u724c<br \/>\n            time_passed &#061; current_time &#8211; bucket[&#039;last_refill&#039;]<br \/>\n            tokens_to_add &#061; time_passed * self.refill_rate<br \/>\n            bucket[&#039;tokens&#039;] &#061; min(self.capacity, bucket[&#039;tokens&#039;] &#043; tokens_to_add)<br \/>\n            bucket[&#039;last_refill&#039;] &#061; current_time<\/p>\n<p>            # \u68c0\u67e5\u662f\u5426\u6709\u8db3\u591f\u4ee4\u724c<br \/>\n            if bucket[&#039;tokens&#039;] &gt;&#061; weight:<br \/>\n                bucket[&#039;tokens&#039;] -&#061; weight<br \/>\n                remaining &#061; bucket[&#039;tokens&#039;]<br \/>\n                reset_time &#061; current_time &#043; (self.capacity &#8211; bucket[&#039;tokens&#039;]) \/ self.refill_rate<\/p>\n<p>                return True, {<br \/>\n                    &#039;remaining&#039;: int(remaining),<br \/>\n                    &#039;reset_time&#039;: reset_time,<br \/>\n                    &#039;limit&#039;: self.capacity<br \/>\n                }<br \/>\n            else:<br \/>\n                # \u8ba1\u7b97\u9700\u8981\u7b49\u5f85\u7684\u65f6\u95f4<br \/>\n                tokens_needed &#061; weight &#8211; bucket[&#039;tokens&#039;]<br \/>\n                wait_time &#061; tokens_needed \/ self.refill_rate<br \/>\n                remaining &#061; 0<br \/>\n                reset_time &#061; current_time &#043; wait_time<\/p>\n<p>                return False, {<br \/>\n                    &#039;remaining&#039;: remaining,<br \/>\n                    &#039;reset_time&#039;: reset_time,<br \/>\n                    &#039;retry_after&#039;: math.ceil(wait_time),<br \/>\n                    &#039;limit&#039;: self.capacity<br \/>\n                }<\/p>\n<p>    def get_remaining(self, key: str) -&gt; int:<br \/>\n        with self.lock:<br \/>\n            if key not in self.buckets:<br \/>\n                return self.capacity<\/p>\n<p>            bucket &#061; self.buckets[key]<br \/>\n            current_time &#061; time.time()<\/p>\n<p>            # \u8865\u5145\u4ee4\u724c<br \/>\n            time_passed &#061; current_time &#8211; bucket[&#039;last_refill&#039;]<br \/>\n            tokens_to_add &#061; time_passed * self.refill_rate<br \/>\n            tokens &#061; min(self.capacity, bucket[&#039;tokens&#039;] &#043; tokens_to_add)<\/p>\n<p>            return int(tokens)<\/p>\n<p>    def get_reset_time(self, key: str) -&gt; float:<br \/>\n        with self.lock:<br \/>\n            if key not in self.buckets:<br \/>\n                return time.time()<\/p>\n<p>            bucket &#061; self.buckets[key]<br \/>\n            current_time &#061; time.time()<\/p>\n<p>            # \u8ba1\u7b97\u4ee4\u724c\u5b8c\u5168\u8865\u5145\u7684\u65f6\u95f4<br \/>\n            if bucket[&#039;tokens&#039;] &lt; self.capacity:<br \/>\n                tokens_needed &#061; self.capacity &#8211; bucket[&#039;tokens&#039;]<br \/>\n                return current_time &#043; tokens_needed \/ self.refill_rate<br \/>\n            else:<br \/>\n                return current_time<\/p>\n<p>class FixedWindowLimiter(RateLimiter):<br \/>\n    &#034;&#034;&#034;\u56fa\u5b9a\u7a97\u53e3\u7b97\u6cd5\u5b9e\u73b0&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, requests_per_window: int, window_seconds: int):<br \/>\n        &#034;&#034;&#034;<br \/>\n        Args:<br \/>\n            requests_per_window: \u6bcf\u4e2a\u7a97\u53e3\u5141\u8bb8\u7684\u8bf7\u6c42\u6570<br \/>\n            window_seconds: \u7a97\u53e3\u5927\u5c0f&#xff08;\u79d2&#xff09;<br \/>\n        &#034;&#034;&#034;<br \/>\n        self.requests_per_window &#061; requests_per_window<br \/>\n        self.window_seconds &#061; window_seconds<br \/>\n        self.windows: Dict[str, Dict] &#061; {}<br \/>\n        self.lock &#061; threading.RLock()<\/p>\n<p>    def is_allowed(self, key: str, weight: int &#061; 1) -&gt; Tuple[bool, Dict]:<br \/>\n        with self.lock:<br \/>\n            current_time &#061; time.time()<br \/>\n            window_start &#061; math.floor(current_time \/ self.window_seconds) * self.window_seconds<\/p>\n<p>            # \u83b7\u53d6\u6216\u521b\u5efa\u7a97\u53e3<br \/>\n            if key not in self.windows:<br \/>\n                self.windows[key] &#061; {<br \/>\n                    &#039;window_start&#039;: window_start,<br \/>\n                    &#039;count&#039;: 0<br \/>\n                }<\/p>\n<p>            window &#061; self.windows[key]<\/p>\n<p>            # \u5982\u679c\u7a97\u53e3\u5df2\u8fc7\u671f&#xff0c;\u91cd\u7f6e<br \/>\n            if window[&#039;window_start&#039;] &lt; window_start:<br \/>\n                window[&#039;window_start&#039;] &#061; window_start<br \/>\n                window[&#039;count&#039;] &#061; 0<\/p>\n<p>            # \u68c0\u67e5\u662f\u5426\u8d85\u8fc7\u9650\u5236<br \/>\n            if window[&#039;count&#039;] &#043; weight &lt;&#061; self.requests_per_window:<br \/>\n                window[&#039;count&#039;] &#043;&#061; weight<br \/>\n                remaining &#061; self.requests_per_window &#8211; window[&#039;count&#039;]<br \/>\n                reset_time &#061; window[&#039;window_start&#039;] &#043; self.window_seconds<\/p>\n<p>                return True, {<br \/>\n                    &#039;remaining&#039;: remaining,<br \/>\n                    &#039;reset_time&#039;: reset_time,<br \/>\n                    &#039;limit&#039;: self.requests_per_window<br \/>\n                }<br \/>\n            else:<br \/>\n                # \u5df2\u8d85\u8fc7\u9650\u5236<br \/>\n                remaining &#061; 0<br \/>\n                reset_time &#061; window[&#039;window_start&#039;] &#043; self.window_seconds<br \/>\n                retry_after &#061; math.ceil(reset_time &#8211; current_time)<\/p>\n<p>                return False, {<br \/>\n                    &#039;remaining&#039;: remaining,<br \/>\n                    &#039;reset_time&#039;: reset_time,<br \/>\n                    &#039;retry_after&#039;: retry_after,<br \/>\n                    &#039;limit&#039;: self.requests_per_window<br \/>\n                }<\/p>\n<p>    def get_remaining(self, key: str) -&gt; int:<br \/>\n        with self.lock:<br \/>\n            current_time &#061; time.time()<br \/>\n            window_start &#061; math.floor(current_time \/ self.window_seconds) * self.window_seconds<\/p>\n<p>            if key not in self.windows:<br \/>\n                return self.requests_per_window<\/p>\n<p>            window &#061; self.windows[key]<\/p>\n<p>            # \u5982\u679c\u7a97\u53e3\u5df2\u8fc7\u671f&#xff0c;\u8fd4\u56de\u5b8c\u6574\u914d\u989d<br \/>\n            if window[&#039;window_start&#039;] &lt; window_start:<br \/>\n                return self.requests_per_window<\/p>\n<p>            return max(0, self.requests_per_window &#8211; window[&#039;count&#039;])<\/p>\n<p>    def get_reset_time(self, key: str) -&gt; float:<br \/>\n        with self.lock:<br \/>\n            current_time &#061; time.time()<br \/>\n            window_start &#061; math.floor(current_time \/ self.window_seconds) * self.window_seconds<\/p>\n<p>            if key not in self.windows:<br \/>\n                return window_start &#043; self.window_seconds<\/p>\n<p>            window &#061; self.windows[key]<\/p>\n<p>            # \u5982\u679c\u7a97\u53e3\u5df2\u8fc7\u671f&#xff0c;\u8fd4\u56de\u4e0b\u4e00\u4e2a\u7a97\u53e3\u7ed3\u675f\u65f6\u95f4<br \/>\n            if window[&#039;window_start&#039;] &lt; window_start:<br \/>\n                return window_start &#043; self.window_seconds<\/p>\n<p>            return window[&#039;window_start&#039;] &#043; self.window_seconds<\/p>\n<p>class SlidingWindowLogLimiter(RateLimiter):<br \/>\n    &#034;&#034;&#034;\u6ed1\u52a8\u7a97\u53e3\u65e5\u5fd7\u7b97\u6cd5\u5b9e\u73b0&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, requests_per_window: int, window_seconds: int):<br \/>\n        &#034;&#034;&#034;<br \/>\n        Args:<br \/>\n            requests_per_window: \u6bcf\u4e2a\u7a97\u53e3\u5141\u8bb8\u7684\u8bf7\u6c42\u6570<br \/>\n            window_seconds: \u7a97\u53e3\u5927\u5c0f&#xff08;\u79d2&#xff09;<br \/>\n        &#034;&#034;&#034;<br \/>\n        self.requests_per_window &#061; requests_per_window<br \/>\n        self.window_seconds &#061; window_seconds<br \/>\n        self.logs: Dict[str, deque] &#061; {}<br \/>\n        self.lock &#061; threading.RLock()<\/p>\n<p>    def is_allowed(self, key: str, weight: int &#061; 1) -&gt; Tuple[bool, Dict]:<br \/>\n        with self.lock:<br \/>\n            current_time &#061; time.time()<br \/>\n            window_start &#061; current_time &#8211; self.window_seconds<\/p>\n<p>            # \u83b7\u53d6\u6216\u521b\u5efa\u65e5\u5fd7\u961f\u5217<br \/>\n            if key not in self.logs:<br \/>\n                self.logs[key] &#061; deque()<\/p>\n<p>            log &#061; self.logs[key]<\/p>\n<p>            # \u6e05\u7406\u8fc7\u671f\u8bb0\u5f55<br \/>\n            while log and log[0] &lt; window_start:<br \/>\n                log.popleft()<\/p>\n<p>            # \u68c0\u67e5\u662f\u5426\u8d85\u8fc7\u9650\u5236<br \/>\n            if len(log) &#043; weight &lt;&#061; self.requests_per_window:<br \/>\n                # \u5141\u8bb8\u8bf7\u6c42&#xff0c;\u8bb0\u5f55\u65f6\u95f4\u6233<br \/>\n                for _ in range(weight):<br \/>\n                    log.append(current_time)<\/p>\n<p>                remaining &#061; self.requests_per_window &#8211; len(log)<br \/>\n                # \u8ba1\u7b97\u91cd\u7f6e\u65f6\u95f4&#xff08;\u6700\u65e9\u8bf7\u6c42\u7684\u65f6\u95f4 &#043; \u7a97\u53e3\u5927\u5c0f&#xff09;<br \/>\n                reset_time &#061; log[0] &#043; self.window_seconds if log else current_time &#043; self.window_seconds<\/p>\n<p>                return True, {<br \/>\n                    &#039;remaining&#039;: remaining,<br \/>\n                    &#039;reset_time&#039;: reset_time,<br \/>\n                    &#039;limit&#039;: self.requests_per_window<br \/>\n                }<br \/>\n            else:<br \/>\n                # \u5df2\u8d85\u8fc7\u9650\u5236<br \/>\n                remaining &#061; 0<br \/>\n                # \u8ba1\u7b97\u6700\u65e9\u53ef\u91cd\u8bd5\u65f6\u95f4<br \/>\n                retry_time &#061; log[0] &#043; self.window_seconds if log else current_time<br \/>\n                retry_after &#061; math.ceil(retry_time &#8211; current_time)<br \/>\n                reset_time &#061; retry_time<\/p>\n<p>                return False, {<br \/>\n                    &#039;remaining&#039;: remaining,<br \/>\n                    &#039;reset_time&#039;: reset_time,<br \/>\n                    &#039;retry_after&#039;: max(0, retry_after),<br \/>\n                    &#039;limit&#039;: self.requests_per_window<br \/>\n                }<\/p>\n<p>    def get_remaining(self, key: str) -&gt; int:<br \/>\n        with self.lock:<br \/>\n            current_time &#061; time.time()<br \/>\n            window_start &#061; current_time &#8211; self.window_seconds<\/p>\n<p>            if key not in self.logs:<br \/>\n                return self.requests_per_window<\/p>\n<p>            log &#061; self.logs[key]<\/p>\n<p>            # \u6e05\u7406\u8fc7\u671f\u8bb0\u5f55<br \/>\n            while log and log[0] &lt; window_start:<br \/>\n                log.popleft()<\/p>\n<p>            return max(0, self.requests_per_window &#8211; len(log))<\/p>\n<p>    def get_reset_time(self, key: str) -&gt; float:<br \/>\n        with self.lock:<br \/>\n            current_time &#061; time.time()<br \/>\n            window_start &#061; current_time &#8211; self.window_seconds<\/p>\n<p>            if key not in self.logs:<br \/>\n                return current_time &#043; self.window_seconds<\/p>\n<p>            log &#061; self.logs[key]<\/p>\n<p>            # \u6e05\u7406\u8fc7\u671f\u8bb0\u5f55<br \/>\n            while log and log[0] &lt; window_start:<br \/>\n                log.popleft()<\/p>\n<p>            if log:<br \/>\n                # \u6700\u65e9\u8bf7\u6c42\u7684\u65f6\u95f4 &#043; \u7a97\u53e3\u5927\u5c0f<br \/>\n                return log[0] &#043; self.window_seconds<br \/>\n            else:<br \/>\n                return current_time &#043; self.window_seconds<\/p>\n<p>class AdaptiveRateLimiter:<br \/>\n    &#034;&#034;&#034;\u81ea\u9002\u5e94\u901f\u7387\u9650\u5236\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, base_limiter: RateLimiter, adaptation_config: Dict &#061; None):<br \/>\n        self.base_limiter &#061; base_limiter<br \/>\n        self.config &#061; {<br \/>\n            &#039;min_requests_per_second&#039;: 1,<br \/>\n            &#039;max_requests_per_second&#039;: 100,<br \/>\n            &#039;scale_up_factor&#039;: 1.1,  # \u589e\u52a010%<br \/>\n            &#039;scale_down_factor&#039;: 0.9,  # \u51cf\u5c1110%<br \/>\n            &#039;scale_up_interval&#039;: 60,  # \u6bcf60\u79d2\u8bc4\u4f30\u4e00\u6b21\u589e\u52a0<br \/>\n            &#039;scale_down_threshold&#039;: 0.8,  # \u4f7f\u7528\u738780%\u65f6\u8003\u8651\u589e\u52a0<br \/>\n            &#039;load_shedding_threshold&#039;: 0.95,  # \u4f7f\u7528\u738795%\u65f6\u5f00\u59cb\u964d\u7ea7<br \/>\n            ** (adaptation_config or {})<br \/>\n        }<\/p>\n<p>        self.usage_history &#061; deque(maxlen&#061;100)<br \/>\n        self.adaptation_history &#061; []<br \/>\n        self.last_adaptation_time &#061; time.time()<\/p>\n<p>    def is_allowed(self, key: str, weight: int &#061; 1) -&gt; Tuple[bool, Dict]:<br \/>\n        # \u68c0\u67e5\u57fa\u7840\u9650\u5236\u5668<br \/>\n        allowed, result &#061; self.base_limiter.is_allowed(key, weight)<\/p>\n<p>        # \u8bb0\u5f55\u4f7f\u7528\u60c5\u51b5<br \/>\n        self.record_usage(key, allowed, result)<\/p>\n<p>        # \u81ea\u9002\u5e94\u8c03\u6574<br \/>\n        self.adaptive_adjustment()<\/p>\n<p>        return allowed, result<\/p>\n<p>    def record_usage(self, key: str, allowed: bool, result: Dict):<br \/>\n        &#034;&#034;&#034;\u8bb0\u5f55\u4f7f\u7528\u60c5\u51b5&#034;&#034;&#034;<br \/>\n        current_time &#061; time.time()<\/p>\n<p>        usage &#061; {<br \/>\n            &#039;timestamp&#039;: current_time,<br \/>\n            &#039;key&#039;: key,<br \/>\n            &#039;allowed&#039;: allowed,<br \/>\n            &#039;remaining&#039;: result.get(&#039;remaining&#039;, 0),<br \/>\n            &#039;limit&#039;: result.get(&#039;limit&#039;, 0)<br \/>\n        }<\/p>\n<p>        self.usage_history.append(usage)<\/p>\n<p>    def adaptive_adjustment(self):<br \/>\n        &#034;&#034;&#034;\u81ea\u9002\u5e94\u8c03\u6574&#034;&#034;&#034;<br \/>\n        current_time &#061; time.time()<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u5230\u8fbe\u8c03\u6574\u95f4\u9694<br \/>\n        if current_time &#8211; self.last_adaptation_time &lt; self.config[&#039;scale_up_interval&#039;]:<br \/>\n            return<\/p>\n<p>        # \u5206\u6790\u6700\u8fd1\u7684\u4f7f\u7528\u60c5\u51b5<br \/>\n        recent_history &#061; [u for u in self.usage_history<br \/>\n                         if u[&#039;timestamp&#039;] &gt; current_time &#8211; self.config[&#039;scale_up_interval&#039;]]<\/p>\n<p>        if not recent_history:<br \/>\n            return<\/p>\n<p>        # \u8ba1\u7b97\u5e73\u5747\u4f7f\u7528\u7387<br \/>\n        total_requests &#061; len(recent_history)<br \/>\n        allowed_requests &#061; sum(1 for u in recent_history if u[&#039;allowed&#039;])<br \/>\n        usage_rate &#061; allowed_requests \/ total_requests if total_requests &gt; 0 else 0<\/p>\n<p>        # \u8c03\u6574\u903b\u8f91<br \/>\n        if usage_rate &gt; self.config[&#039;scale_down_threshold&#039;]:<br \/>\n            # \u4f7f\u7528\u7387\u9ad8&#xff0c;\u8003\u8651\u589e\u52a0\u9650\u5236<br \/>\n            self.scale_up()<br \/>\n        elif usage_rate &lt; 0.5:<br \/>\n            # \u4f7f\u7528\u7387\u4f4e&#xff0c;\u8003\u8651\u51cf\u5c11\u9650\u5236<br \/>\n            self.scale_down()<\/p>\n<p>        self.last_adaptation_time &#061; current_time<\/p>\n<p>    def scale_up(self):<br \/>\n        &#034;&#034;&#034;\u589e\u52a0\u901f\u7387\u9650\u5236&#034;&#034;&#034;<br \/>\n        # \u83b7\u53d6\u5f53\u524d\u9650\u5236\u5668\u914d\u7f6e<br \/>\n        if isinstance(self.base_limiter, TokenBucketLimiter):<br \/>\n            new_capacity &#061; min(<br \/>\n                self.base_limiter.capacity * self.config[&#039;scale_up_factor&#039;],<br \/>\n                self.config[&#039;max_requests_per_second&#039;] * 60  # \u8f6c\u6362\u4e3a\u6bcf\u5206\u949f<br \/>\n            )<br \/>\n            self.base_limiter.capacity &#061; new_capacity<\/p>\n<p>            self.adaptation_history.append({<br \/>\n                &#039;timestamp&#039;: time.time(),<br \/>\n                &#039;action&#039;: &#039;scale_up&#039;,<br \/>\n                &#039;new_capacity&#039;: new_capacity,<br \/>\n                &#039;reason&#039;: &#039;high_usage_rate&#039;<br \/>\n            })<\/p>\n<p>        elif isinstance(self.base_limiter, FixedWindowLimiter):<br \/>\n            new_limit &#061; min(<br \/>\n                self.base_limiter.requests_per_window * self.config[&#039;scale_up_factor&#039;],<br \/>\n                self.config[&#039;max_requests_per_second&#039;] * self.base_limiter.window_seconds<br \/>\n            )<br \/>\n            self.base_limiter.requests_per_window &#061; int(new_limit)<\/p>\n<p>            self.adaptation_history.append({<br \/>\n                &#039;timestamp&#039;: time.time(),<br \/>\n                &#039;action&#039;: &#039;scale_up&#039;,<br \/>\n                &#039;new_limit&#039;: new_limit,<br \/>\n                &#039;reason&#039;: &#039;high_usage_rate&#039;<br \/>\n            })<\/p>\n<p>    def scale_down(self):<br \/>\n        &#034;&#034;&#034;\u51cf\u5c11\u901f\u7387\u9650\u5236&#034;&#034;&#034;<br \/>\n        if isinstance(self.base_limiter, TokenBucketLimiter):<br \/>\n            new_capacity &#061; max(<br \/>\n                self.base_limiter.capacity * self.config[&#039;scale_down_factor&#039;],<br \/>\n                self.config[&#039;min_requests_per_second&#039;] * 60<br \/>\n            )<br \/>\n            self.base_limiter.capacity &#061; new_capacity<\/p>\n<p>            self.adaptation_history.append({<br \/>\n                &#039;timestamp&#039;: time.time(),<br \/>\n                &#039;action&#039;: &#039;scale_down&#039;,<br \/>\n                &#039;new_capacity&#039;: new_capacity,<br \/>\n                &#039;reason&#039;: &#039;low_usage_rate&#039;<br \/>\n            })<\/p>\n<p>        elif isinstance(self.base_limiter, FixedWindowLimiter):<br \/>\n            new_limit &#061; max(<br \/>\n                self.base_limiter.requests_per_window * self.config[&#039;scale_down_factor&#039;],<br \/>\n                self.config[&#039;min_requests_per_second&#039;] * self.base_limiter.window_seconds<br \/>\n            )<br \/>\n            self.base_limiter.requests_per_window &#061; int(new_limit)<\/p>\n<p>            self.adaptation_history.append({<br \/>\n                &#039;timestamp&#039;: time.time(),<br \/>\n                &#039;action&#039;: &#039;scale_down&#039;,<br \/>\n                &#039;new_limit&#039;: new_limit,<br \/>\n                &#039;reason&#039;: &#039;low_usage_rate&#039;<br \/>\n            })<\/p>\n<h4>22.2.2 \u591a\u5c42\u901f\u7387\u9650\u5236\u7b56\u7565<\/h4>\n<p>python<\/p>\n<p># \u591a\u5c42\u901f\u7387\u9650\u5236\u7b56\u7565<br \/>\nfrom typing import List, Dict, Optional, Tuple<br \/>\nimport hashlib<\/p>\n<p>class MultiLayerRateLimiter:<br \/>\n    &#034;&#034;&#034;\u591a\u5c42\u901f\u7387\u9650\u5236\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, layers_config: List[Dict]):<br \/>\n        &#034;&#034;&#034;<br \/>\n        Args:<br \/>\n            layers_config: \u5404\u5c42\u914d\u7f6e\u5217\u8868&#xff0c;\u6309\u987a\u5e8f\u68c0\u67e5<br \/>\n                [<br \/>\n                    {<br \/>\n                        &#039;limiter_class&#039;: TokenBucketLimiter,<br \/>\n                        &#039;args&#039;: [100, 1.0],  # capacity, refill_rate<br \/>\n                        &#039;kwargs&#039;: {},<br \/>\n                        &#039;key_builder&#039;: &#039;ip&#039;,  # ip, user, endpoint, custom<br \/>\n                        &#039;cost&#039;: 1,  # \u8bf7\u6c42\u6210\u672c<br \/>\n                        &#039;name&#039;: &#039;ip_layer&#039;<br \/>\n                    },<br \/>\n                    &#8230;<br \/>\n                ]<br \/>\n        &#034;&#034;&#034;<br \/>\n        self.layers &#061; []<br \/>\n        self.layer_configs &#061; layers_config<br \/>\n        self.init_layers()<\/p>\n<p>    def init_layers(self):<br \/>\n        &#034;&#034;&#034;\u521d\u59cb\u5316\u5404\u5c42\u9650\u5236\u5668&#034;&#034;&#034;<br \/>\n        for config in self.layer_configs:<br \/>\n            limiter_class &#061; config[&#039;limiter_class&#039;]<br \/>\n            args &#061; config.get(&#039;args&#039;, [])<br \/>\n            kwargs &#061; config.get(&#039;kwargs&#039;, {})<\/p>\n<p>            limiter &#061; limiter_class(*args, **kwargs)<\/p>\n<p>            self.layers.append({<br \/>\n                &#039;limiter&#039;: limiter,<br \/>\n                &#039;key_builder&#039;: config.get(&#039;key_builder&#039;, &#039;ip&#039;),<br \/>\n                &#039;cost&#039;: config.get(&#039;cost&#039;, 1),<br \/>\n                &#039;name&#039;: config.get(&#039;name&#039;, &#039;unnamed_layer&#039;),<br \/>\n                &#039;priority&#039;: config.get(&#039;priority&#039;, 0)<br \/>\n            })<\/p>\n<p>        # \u6309\u4f18\u5148\u7ea7\u6392\u5e8f<br \/>\n        self.layers.sort(key&#061;lambda x: x[&#039;priority&#039;], reverse&#061;True)<\/p>\n<p>    def is_allowed(self, request_info: Dict) -&gt; Tuple[bool, Dict]:<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u68c0\u67e5\u8bf7\u6c42\u662f\u5426\u88ab\u5141\u8bb8<\/p>\n<p>        Args:<br \/>\n            request_info: \u8bf7\u6c42\u4fe1\u606f<br \/>\n                {<br \/>\n                    &#039;ip&#039;: &#039;127.0.0.1&#039;,<br \/>\n                    &#039;user_id&#039;: &#039;user123&#039;,<br \/>\n                    &#039;endpoint&#039;: &#039;\/api\/users&#039;,<br \/>\n                    &#039;method&#039;: &#039;GET&#039;,<br \/>\n                    &#039;headers&#039;: {&#8230;}<br \/>\n                }<\/p>\n<p>        Returns:<br \/>\n            (allowed, result)<br \/>\n        &#034;&#034;&#034;<br \/>\n        results &#061; []<\/p>\n<p>        for layer in self.layers:<br \/>\n            # \u6784\u5efa\u952e<br \/>\n            key &#061; self.build_key(layer[&#039;key_builder&#039;], request_info)<br \/>\n            cost &#061; layer[&#039;cost&#039;]<\/p>\n<p>            # \u68c0\u67e5\u8be5\u5c42\u9650\u5236<br \/>\n            allowed, result &#061; layer[&#039;limiter&#039;].is_allowed(key, cost)<br \/>\n            result[&#039;layer&#039;] &#061; layer[&#039;name&#039;]<\/p>\n<p>            results.append(result)<\/p>\n<p>            if not allowed:<br \/>\n                # \u8be5\u5c42\u88ab\u9650\u5236&#xff0c;\u7acb\u5373\u8fd4\u56de<br \/>\n                return False, {<br \/>\n                    &#039;allowed&#039;: False,<br \/>\n                    &#039;first_blocking_layer&#039;: layer[&#039;name&#039;],<br \/>\n                    &#039;details&#039;: results,<br \/>\n                    &#039;retry_after&#039;: result.get(&#039;retry_after&#039;),<br \/>\n                    &#039;reset_time&#039;: result.get(&#039;reset_time&#039;)<br \/>\n                }<\/p>\n<p>        # \u6240\u6709\u5c42\u90fd\u5141\u8bb8<br \/>\n        return True, {<br \/>\n            &#039;allowed&#039;: True,<br \/>\n            &#039;details&#039;: results<br \/>\n        }<\/p>\n<p>    def build_key(self, key_builder: str, request_info: Dict) -&gt; str:<br \/>\n        &#034;&#034;&#034;\u6784\u5efa\u9650\u5236\u952e&#034;&#034;&#034;<br \/>\n        if key_builder &#061;&#061; &#039;ip&#039;:<br \/>\n            return f&#034;ip:{request_info.get(&#039;ip&#039;, &#039;unknown&#039;)}&#034;<\/p>\n<p>        elif key_builder &#061;&#061; &#039;user&#039;:<br \/>\n            user_id &#061; request_info.get(&#039;user_id&#039;)<br \/>\n            if user_id:<br \/>\n                return f&#034;user:{user_id}&#034;<br \/>\n            else:<br \/>\n                # \u672a\u8ba4\u8bc1\u7528\u6237\u4f7f\u7528IP<br \/>\n                return f&#034;ip:{request_info.get(&#039;ip&#039;, &#039;unknown&#039;)}&#034;<\/p>\n<p>        elif key_builder &#061;&#061; &#039;endpoint&#039;:<br \/>\n            endpoint &#061; request_info.get(&#039;endpoint&#039;, &#039;unknown&#039;)<br \/>\n            method &#061; request_info.get(&#039;method&#039;, &#039;GET&#039;)<br \/>\n            return f&#034;endpoint:{method}:{endpoint}&#034;<\/p>\n<p>        elif key_builder &#061;&#061; &#039;user_endpoint&#039;:<br \/>\n            user_id &#061; request_info.get(&#039;user_id&#039;, &#039;anonymous&#039;)<br \/>\n            endpoint &#061; request_info.get(&#039;endpoint&#039;, &#039;unknown&#039;)<br \/>\n            method &#061; request_info.get(&#039;method&#039;, &#039;GET&#039;)<br \/>\n            return f&#034;user_endpoint:{user_id}:{method}:{endpoint}&#034;<\/p>\n<p>        elif key_builder &#061;&#061; &#039;custom&#039;:<br \/>\n            # \u81ea\u5b9a\u4e49\u952e\u6784\u5efa\u903b\u8f91<br \/>\n            custom_data &#061; request_info.get(&#039;custom_key_data&#039;, &#039;&#039;)<br \/>\n            key_hash &#061; hashlib.md5(custom_data.encode()).hexdigest()<br \/>\n            return f&#034;custom:{key_hash}&#034;<\/p>\n<p>        else:<br \/>\n            # \u9ed8\u8ba4\u4f7f\u7528IP<br \/>\n            return f&#034;ip:{request_info.get(&#039;ip&#039;, &#039;unknown&#039;)}&#034;<\/p>\n<p>    def get_quotas(self, request_info: Dict) -&gt; Dict:<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u6240\u6709\u5c42\u7684\u914d\u989d\u4fe1\u606f&#034;&#034;&#034;<br \/>\n        quotas &#061; {}<\/p>\n<p>        for layer in self.layers:<br \/>\n            key &#061; self.build_key(layer[&#039;key_builder&#039;], request_info)<br \/>\n            limiter &#061; layer[&#039;limiter&#039;]<\/p>\n<p>            remaining &#061; limiter.get_remaining(key)<br \/>\n            reset_time &#061; limiter.get_reset_time(key)<\/p>\n<p>            quotas[layer[&#039;name&#039;]] &#061; {<br \/>\n                &#039;remaining&#039;: remaining,<br \/>\n                &#039;reset_time&#039;: reset_time,<br \/>\n                &#039;limit&#039;: self.get_layer_limit(layer),<br \/>\n                &#039;key_builder&#039;: layer[&#039;key_builder&#039;]<br \/>\n            }<\/p>\n<p>        return quotas<\/p>\n<p>    def get_layer_limit(self, layer: Dict) -&gt; int:<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u5c42\u7684\u9650\u5236\u503c&#034;&#034;&#034;<br \/>\n        limiter &#061; layer[&#039;limiter&#039;]<\/p>\n<p>        if isinstance(limiter, TokenBucketLimiter):<br \/>\n            return limiter.capacity<br \/>\n        elif isinstance(limiter, FixedWindowLimiter):<br \/>\n            return limiter.requests_per_window<br \/>\n        elif isinstance(limiter, SlidingWindowLogLimiter):<br \/>\n            return limiter.requests_per_window<br \/>\n        else:<br \/>\n            return 0<\/p>\n<p>    def get_rate_limit_headers(self, request_info: Dict) -&gt; Dict[str, str]:<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u901f\u7387\u9650\u5236\u5934\u90e8\u4fe1\u606f&#034;&#034;&#034;<br \/>\n        quotas &#061; self.get_quotas(request_info)<\/p>\n<p>        # \u627e\u5230\u6700\u4e25\u683c\u7684\u9650\u5236<br \/>\n        strictest_layer &#061; None<br \/>\n        strictest_remaining &#061; float(&#039;inf&#039;)<\/p>\n<p>        for name, quota in quotas.items():<br \/>\n            if quota[&#039;remaining&#039;] &lt; strictest_remaining:<br \/>\n                strictest_remaining &#061; quota[&#039;remaining&#039;]<br \/>\n                strictest_layer &#061; name<\/p>\n<p>        if strictest_layer:<br \/>\n            quota &#061; quotas[strictest_layer]<\/p>\n<p>            headers &#061; {<br \/>\n                &#039;X-RateLimit-Limit&#039;: str(quota[&#039;limit&#039;]),<br \/>\n                &#039;X-RateLimit-Remaining&#039;: str(quota[&#039;remaining&#039;]),<br \/>\n                &#039;X-RateLimit-Reset&#039;: str(int(quota[&#039;reset_time&#039;]))<br \/>\n            }<\/p>\n<p>            # \u6dfb\u52a0\u91cd\u8bd5\u5934\u90e8&#xff08;\u5982\u679c\u5269\u4f59\u4e3a0&#xff09;<br \/>\n            if quota[&#039;remaining&#039;] &lt;&#061; 0:<br \/>\n                retry_after &#061; max(0, math.ceil(quota[&#039;reset_time&#039;] &#8211; time.time()))<br \/>\n                headers[&#039;Retry-After&#039;] &#061; str(retry_after)<\/p>\n<p>            return headers<\/p>\n<p>        return {}<\/p>\n<p># \u4f7f\u7528\u793a\u4f8b<br \/>\ndef create_production_rate_limiter():<br \/>\n    &#034;&#034;&#034;\u521b\u5efa\u751f\u4ea7\u73af\u5883\u4f7f\u7528\u7684\u591a\u5c42\u901f\u7387\u9650\u5236\u5668&#034;&#034;&#034;<\/p>\n<p>    layers_config &#061; [<br \/>\n        # \u7b2c\u4e00\u5c42&#xff1a;IP\u57fa\u7840\u9650\u5236&#xff08;\u9632\u6b62\u6ee5\u7528&#xff09;<br \/>\n        {<br \/>\n            &#039;limiter_class&#039;: TokenBucketLimiter,<br \/>\n            &#039;args&#039;: [100, 1.0],  # 100\u8bf7\u6c42&#xff0c;\u6bcf\u79d2\u8865\u51451\u4e2a<br \/>\n            &#039;key_builder&#039;: &#039;ip&#039;,<br \/>\n            &#039;cost&#039;: 1,<br \/>\n            &#039;name&#039;: &#039;ip_basic&#039;,<br \/>\n            &#039;priority&#039;: 10<br \/>\n        },<\/p>\n<p>        # \u7b2c\u4e8c\u5c42&#xff1a;\u7528\u6237\u7ea7\u522b\u9650\u5236<br \/>\n        {<br \/>\n            &#039;limiter_class&#039;: FixedWindowLimiter,<br \/>\n            &#039;args&#039;: [1000, 3600],  # 1000\u8bf7\u6c42\/\u5c0f\u65f6<br \/>\n            &#039;key_builder&#039;: &#039;user&#039;,<br \/>\n            &#039;cost&#039;: 1,<br \/>\n            &#039;name&#039;: &#039;user_hourly&#039;,<br \/>\n            &#039;priority&#039;: 20<br \/>\n        },<\/p>\n<p>        # \u7b2c\u4e09\u5c42&#xff1a;\u7aef\u70b9\u7ea7\u522b\u9650\u5236<br \/>\n        {<br \/>\n            &#039;limiter_class&#039;: SlidingWindowLogLimiter,<br \/>\n            &#039;args&#039;: [60, 60],  # 60\u8bf7\u6c42\/\u5206\u949f<br \/>\n            &#039;key_builder&#039;: &#039;endpoint&#039;,<br \/>\n            &#039;cost&#039;: 1,<br \/>\n            &#039;name&#039;: &#039;endpoint_minute&#039;,<br \/>\n            &#039;priority&#039;: 30<br \/>\n        },<\/p>\n<p>        # \u7b2c\u56db\u5c42&#xff1a;\u654f\u611f\u7aef\u70b9\u66f4\u4e25\u683c\u9650\u5236<br \/>\n        {<br \/>\n            &#039;limiter_class&#039;: FixedWindowLimiter,<br \/>\n            &#039;args&#039;: [10, 60],  # 10\u8bf7\u6c42\/\u5206\u949f&#xff08;\u7528\u4e8e\u654f\u611f\u64cd\u4f5c&#xff09;<br \/>\n            &#039;key_builder&#039;: &#039;user_endpoint&#039;,<br \/>\n            &#039;cost&#039;: 1,<br \/>\n            &#039;name&#039;: &#039;sensitive_endpoint&#039;,<br \/>\n            &#039;priority&#039;: 40,<br \/>\n            &#039;condition&#039;: lambda req: req.get(&#039;endpoint&#039;, &#039;&#039;).startswith(&#039;\/api\/admin\/&#039;)<br \/>\n        }<br \/>\n    ]<\/p>\n<p>    return MultiLayerRateLimiter(layers_config)<\/p>\n<h3>22.3 \u8be6\u7ec6\u5b9e\u73b0\u4e0e\u6700\u4f73\u5b9e\u8df5<\/h3>\n<h4>22.3.1 \u5206\u5e03\u5f0f\u901f\u7387\u9650\u5236\u5b9e\u73b0<\/h4>\n<p>python<\/p>\n<p># \u57fa\u4e8eRedis\u7684\u5206\u5e03\u5f0f\u901f\u7387\u9650\u5236\u5668<br \/>\nimport redis<br \/>\nimport json<br \/>\nimport pickle<br \/>\nfrom typing import Optional, Tuple, Dict, Any<br \/>\nimport time<br \/>\nimport hashlib<\/p>\n<p>class RedisRateLimiter:<br \/>\n    &#034;&#034;&#034;\u57fa\u4e8eRedis\u7684\u5206\u5e03\u5f0f\u901f\u7387\u9650\u5236\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, redis_client: redis.Redis, namespace: str &#061; &#034;ratelimit&#034;):<br \/>\n        self.redis &#061; redis_client<br \/>\n        self.namespace &#061; namespace<br \/>\n        self.lua_scripts &#061; self.load_lua_scripts()<\/p>\n<p>    def load_lua_scripts(self) -&gt; Dict[str, str]:<br \/>\n        &#034;&#034;&#034;\u52a0\u8f7dLua\u811a\u672c&#xff08;\u539f\u5b50\u64cd\u4f5c&#xff09;&#034;&#034;&#034;<\/p>\n<p>        # \u4ee4\u724c\u6876\u7b97\u6cd5\u7684Lua\u811a\u672c<br \/>\n        token_bucket_script &#061; &#034;&#034;&#034;<br \/>\n        local key &#061; KEYS[1]<br \/>\n        local capacity &#061; tonumber(ARGV[1])<br \/>\n        local refill_rate &#061; tonumber(ARGV[2])<br \/>\n        local weight &#061; tonumber(ARGV[3])<br \/>\n        local now &#061; tonumber(ARGV[4])<\/p>\n<p>        local bucket &#061; redis.call(&#039;HMGET&#039;, key, &#039;tokens&#039;, &#039;last_refill&#039;)<br \/>\n        local tokens<br \/>\n        local last_refill<\/p>\n<p>        if bucket[1] &#061;&#061; false then<br \/>\n            &#8212; \u521d\u59cb\u5316\u6876<br \/>\n            tokens &#061; capacity<br \/>\n            last_refill &#061; now<br \/>\n            redis.call(&#039;HMSET&#039;, key, &#039;tokens&#039;, tokens, &#039;last_refill&#039;, last_refill)<br \/>\n        else<br \/>\n            tokens &#061; tonumber(bucket[1])<br \/>\n            last_refill &#061; tonumber(bucket[2])<\/p>\n<p>            &#8212; \u8865\u5145\u4ee4\u724c<br \/>\n            local time_passed &#061; now &#8211; last_refill<br \/>\n            local tokens_to_add &#061; time_passed * refill_rate<br \/>\n            tokens &#061; math.min(capacity, tokens &#043; tokens_to_add)<br \/>\n            last_refill &#061; now<br \/>\n        end<\/p>\n<p>        &#8212; \u68c0\u67e5\u662f\u5426\u6709\u8db3\u591f\u4ee4\u724c<br \/>\n        if tokens &gt;&#061; weight then<br \/>\n            tokens &#061; tokens &#8211; weight<br \/>\n            redis.call(&#039;HMSET&#039;, key, &#039;tokens&#039;, tokens, &#039;last_refill&#039;, last_refill)<\/p>\n<p>            &#8212; \u8ba1\u7b97\u91cd\u7f6e\u65f6\u95f4<br \/>\n            local reset_time &#061; now &#043; (capacity &#8211; tokens) \/ refill_rate<\/p>\n<p>            return {1, tokens, reset_time, capacity}<br \/>\n        else<br \/>\n            &#8212; \u8ba1\u7b97\u9700\u8981\u7b49\u5f85\u7684\u65f6\u95f4<br \/>\n            local tokens_needed &#061; weight &#8211; tokens<br \/>\n            local wait_time &#061; tokens_needed \/ refill_rate<br \/>\n            local reset_time &#061; now &#043; wait_time<\/p>\n<p>            return {0, tokens, reset_time, capacity, wait_time}<br \/>\n        end<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        # \u56fa\u5b9a\u7a97\u53e3\u7b97\u6cd5\u7684Lua\u811a\u672c<br \/>\n        fixed_window_script &#061; &#034;&#034;&#034;<br \/>\n        local key &#061; KEYS[1]<br \/>\n        local limit &#061; tonumber(ARGV[1])<br \/>\n        local window &#061; tonumber(ARGV[2])<br \/>\n        local weight &#061; tonumber(ARGV[3])<br \/>\n        local now &#061; tonumber(ARGV[4])<\/p>\n<p>        local window_start &#061; math.floor(now \/ window) * window<br \/>\n        local window_key &#061; key .. &#039;:&#039; .. tostring(window_start)<\/p>\n<p>        &#8212; \u83b7\u53d6\u5f53\u524d\u8ba1\u6570<br \/>\n        local count &#061; redis.call(&#039;GET&#039;, window_key)<br \/>\n        if count &#061;&#061; false then<br \/>\n            count &#061; 0<br \/>\n        else<br \/>\n            count &#061; tonumber(count)<br \/>\n        end<\/p>\n<p>        &#8212; \u68c0\u67e5\u662f\u5426\u8d85\u8fc7\u9650\u5236<br \/>\n        if count &#043; weight &lt;&#061; limit then<br \/>\n            &#8212; \u589e\u52a0\u8ba1\u6570<br \/>\n            redis.call(&#039;INCRBY&#039;, window_key, weight)<br \/>\n            &#8212; \u8bbe\u7f6e\u8fc7\u671f\u65f6\u95f4&#xff08;\u7a97\u53e3\u7ed3\u675f\u65f6&#xff09;<br \/>\n            redis.call(&#039;EXPIRE&#039;, window_key, window)<\/p>\n<p>            local remaining &#061; limit &#8211; (count &#043; weight)<br \/>\n            local reset_time &#061; window_start &#043; window<\/p>\n<p>            return {1, remaining, reset_time, limit}<br \/>\n        else<br \/>\n            &#8212; \u5df2\u8d85\u8fc7\u9650\u5236<br \/>\n            local remaining &#061; 0<br \/>\n            local reset_time &#061; window_start &#043; window<br \/>\n            local retry_after &#061; reset_time &#8211; now<\/p>\n<p>            return {0, remaining, reset_time, limit, retry_after}<br \/>\n        end<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        # \u6ed1\u52a8\u7a97\u53e3\u7b97\u6cd5\u7684Lua\u811a\u672c<br \/>\n        sliding_window_script &#061; &#034;&#034;&#034;<br \/>\n        local key &#061; KEYS[1]<br \/>\n        local limit &#061; tonumber(ARGV[1])<br \/>\n        local window &#061; tonumber(ARGV[2])<br \/>\n        local weight &#061; tonumber(ARGV[3])<br \/>\n        local now &#061; tonumber(ARGV[4])<\/p>\n<p>        local window_start &#061; now &#8211; window<\/p>\n<p>        &#8212; \u83b7\u53d6\u6240\u6709\u8bb0\u5f55<br \/>\n        local records &#061; redis.call(&#039;ZRANGEBYSCORE&#039;, key, window_start, now)<br \/>\n        local count &#061; #records<\/p>\n<p>        &#8212; \u68c0\u67e5\u662f\u5426\u8d85\u8fc7\u9650\u5236<br \/>\n        if count &#043; weight &lt;&#061; limit then<br \/>\n            &#8212; \u6dfb\u52a0\u65b0\u8bb0\u5f55<br \/>\n            for i &#061; 1, weight do<br \/>\n                redis.call(&#039;ZADD&#039;, key, now, now .. &#039;:&#039; .. i)<br \/>\n            end<\/p>\n<p>            &#8212; \u6e05\u7406\u8fc7\u671f\u8bb0\u5f55<br \/>\n            redis.call(&#039;ZREMRANGEBYSCORE&#039;, key, 0, window_start)<\/p>\n<p>            &#8212; \u8bbe\u7f6e\u8fc7\u671f\u65f6\u95f4<br \/>\n            redis.call(&#039;EXPIRE&#039;, key, window)<\/p>\n<p>            &#8212; \u8ba1\u7b97\u5269\u4f59\u914d\u989d<br \/>\n            local remaining &#061; limit &#8211; (count &#043; weight)<\/p>\n<p>            &#8212; \u8ba1\u7b97\u91cd\u7f6e\u65f6\u95f4&#xff08;\u6700\u65e9\u8bb0\u5f55\u7684\u65f6\u95f4 &#043; \u7a97\u53e3\u5927\u5c0f&#xff09;<br \/>\n            local reset_time<br \/>\n            if count &gt; 0 then<br \/>\n                local oldest &#061; redis.call(&#039;ZRANGE&#039;, key, 0, 0, &#039;WITHSCORES&#039;)[2]<br \/>\n                reset_time &#061; oldest &#043; window<br \/>\n            else<br \/>\n                reset_time &#061; now &#043; window<br \/>\n            end<\/p>\n<p>            return {1, remaining, reset_time, limit}<br \/>\n        else<br \/>\n            &#8212; \u5df2\u8d85\u8fc7\u9650\u5236<br \/>\n            local remaining &#061; 0<\/p>\n<p>            &#8212; \u8ba1\u7b97\u6700\u65e9\u53ef\u91cd\u8bd5\u65f6\u95f4<br \/>\n            local reset_time<br \/>\n            if count &gt; 0 then<br \/>\n                local oldest &#061; redis.call(&#039;ZRANGE&#039;, key, 0, 0, &#039;WITHSCORES&#039;)[2]<br \/>\n                reset_time &#061; oldest &#043; window<br \/>\n            else<br \/>\n                reset_time &#061; now &#043; window<br \/>\n            end<\/p>\n<p>            local retry_after &#061; reset_time &#8211; now<\/p>\n<p>            return {0, remaining, reset_time, limit, retry_after}<br \/>\n        end<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        return {<br \/>\n            &#039;token_bucket&#039;: self.redis.script_load(token_bucket_script),<br \/>\n            &#039;fixed_window&#039;: self.redis.script_load(fixed_window_script),<br \/>\n            &#039;sliding_window&#039;: self.redis.script_load(sliding_window_script)<br \/>\n        }<\/p>\n<p>    def token_bucket_is_allowed(self, key: str, capacity: int,<br \/>\n                                refill_rate: float, weight: int &#061; 1) -&gt; Tuple[bool, Dict]:<br \/>\n        &#034;&#034;&#034;\u4ee4\u724c\u6876\u7b97\u6cd5\u68c0\u67e5&#034;&#034;&#034;<br \/>\n        redis_key &#061; f&#034;{self.namespace}:token_bucket:{key}&#034;<br \/>\n        now &#061; time.time()<\/p>\n<p>        result &#061; self.redis.evalsha(<br \/>\n            self.lua_scripts[&#039;token_bucket&#039;],<br \/>\n            1,  # key\u6570\u91cf<br \/>\n            redis_key,<br \/>\n            capacity,<br \/>\n            refill_rate,<br \/>\n            weight,<br \/>\n            now<br \/>\n        )<\/p>\n<p>        allowed &#061; bool(result[0])<br \/>\n        tokens &#061; result[1]<br \/>\n        reset_time &#061; result[2]<br \/>\n        limit &#061; result[3]<\/p>\n<p>        if allowed:<br \/>\n            return True, {<br \/>\n                &#039;remaining&#039;: int(tokens),<br \/>\n                &#039;reset_time&#039;: reset_time,<br \/>\n                &#039;limit&#039;: limit<br \/>\n            }<br \/>\n        else:<br \/>\n            wait_time &#061; result[4]<br \/>\n            return False, {<br \/>\n                &#039;remaining&#039;: int(tokens),<br \/>\n                &#039;reset_time&#039;: reset_time,<br \/>\n                &#039;retry_after&#039;: math.ceil(wait_time),<br \/>\n                &#039;limit&#039;: limit<br \/>\n            }<\/p>\n<p>    def fixed_window_is_allowed(self, key: str, limit: int,<br \/>\n                                window_seconds: int, weight: int &#061; 1) -&gt; Tuple[bool, Dict]:<br \/>\n        &#034;&#034;&#034;\u56fa\u5b9a\u7a97\u53e3\u7b97\u6cd5\u68c0\u67e5&#034;&#034;&#034;<br \/>\n        redis_key &#061; f&#034;{self.namespace}:fixed_window:{key}&#034;<br \/>\n        now &#061; time.time()<\/p>\n<p>        result &#061; self.redis.evalsha(<br \/>\n            self.lua_scripts[&#039;fixed_window&#039;],<br \/>\n            1,<br \/>\n            redis_key,<br \/>\n            limit,<br \/>\n            window_seconds,<br \/>\n            weight,<br \/>\n            now<br \/>\n        )<\/p>\n<p>        allowed &#061; bool(result[0])<br \/>\n        remaining &#061; result[1]<br \/>\n        reset_time &#061; result[2]<br \/>\n        limit_val &#061; result[3]<\/p>\n<p>        if allowed:<br \/>\n            return True, {<br \/>\n                &#039;remaining&#039;: int(remaining),<br \/>\n                &#039;reset_time&#039;: reset_time,<br \/>\n                &#039;limit&#039;: limit_val<br \/>\n            }<br \/>\n        else:<br \/>\n            retry_after &#061; result[4]<br \/>\n            return False, {<br \/>\n                &#039;remaining&#039;: int(remaining),<br \/>\n                &#039;reset_time&#039;: reset_time,<br \/>\n                &#039;retry_after&#039;: math.ceil(retry_after),<br \/>\n                &#039;limit&#039;: limit_val<br \/>\n            }<\/p>\n<p>    def sliding_window_is_allowed(self, key: str, limit: int,<br \/>\n                                  window_seconds: int, weight: int &#061; 1) -&gt; Tuple[bool, Dict]:<br \/>\n        &#034;&#034;&#034;\u6ed1\u52a8\u7a97\u53e3\u7b97\u6cd5\u68c0\u67e5&#034;&#034;&#034;<br \/>\n        redis_key &#061; f&#034;{self.namespace}:sliding_window:{key}&#034;<br \/>\n        now &#061; time.time()<\/p>\n<p>        result &#061; self.redis.evalsha(<br \/>\n            self.lua_scripts[&#039;sliding_window&#039;],<br \/>\n            1,<br \/>\n            redis_key,<br \/>\n            limit,<br \/>\n            window_seconds,<br \/>\n            weight,<br \/>\n            now<br \/>\n        )<\/p>\n<p>        allowed &#061; bool(result[0])<br \/>\n        remaining &#061; result[1]<br \/>\n        reset_time &#061; result[2]<br \/>\n        limit_val &#061; result[3]<\/p>\n<p>        if allowed:<br \/>\n            return True, {<br \/>\n                &#039;remaining&#039;: int(remaining),<br \/>\n                &#039;reset_time&#039;: reset_time,<br \/>\n                &#039;limit&#039;: limit_val<br \/>\n            }<br \/>\n        else:<br \/>\n            retry_after &#061; result[4]<br \/>\n            return False, {<br \/>\n                &#039;remaining&#039;: int(remaining),<br \/>\n                &#039;reset_time&#039;: reset_time,<br \/>\n                &#039;retry_after&#039;: math.ceil(retry_after),<br \/>\n                &#039;limit&#039;: limit_val<br \/>\n            }<\/p>\n<p>    def get_quotas(self, key: str, algorithm: str &#061; &#039;token_bucket&#039;,<br \/>\n                   **kwargs) -&gt; Optional[Dict]:<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u914d\u989d\u4fe1\u606f&#034;&#034;&#034;<br \/>\n        if algorithm &#061;&#061; &#039;token_bucket&#039;:<br \/>\n            capacity &#061; kwargs.get(&#039;capacity&#039;, 100)<br \/>\n            refill_rate &#061; kwargs.get(&#039;refill_rate&#039;, 1.0)<\/p>\n<p>            redis_key &#061; f&#034;{self.namespace}:token_bucket:{key}&#034;<br \/>\n            bucket &#061; self.redis.hgetall(redis_key)<\/p>\n<p>            if not bucket:<br \/>\n                return {<br \/>\n                    &#039;remaining&#039;: capacity,<br \/>\n                    &#039;limit&#039;: capacity,<br \/>\n                    &#039;reset_time&#039;: time.time()<br \/>\n                }<\/p>\n<p>            tokens &#061; float(bucket.get(b&#039;tokens&#039;, 0))<br \/>\n            last_refill &#061; float(bucket.get(b&#039;last_refill&#039;, time.time()))<\/p>\n<p>            # \u8865\u5145\u4ee4\u724c<br \/>\n            now &#061; time.time()<br \/>\n            time_passed &#061; now &#8211; last_refill<br \/>\n            tokens_to_add &#061; time_passed * refill_rate<br \/>\n            tokens &#061; min(capacity, tokens &#043; tokens_to_add)<\/p>\n<p>            # \u8ba1\u7b97\u91cd\u7f6e\u65f6\u95f4<br \/>\n            if tokens &lt; capacity:<br \/>\n                reset_time &#061; now &#043; (capacity &#8211; tokens) \/ refill_rate<br \/>\n            else:<br \/>\n                reset_time &#061; now<\/p>\n<p>            return {<br \/>\n                &#039;remaining&#039;: int(tokens),<br \/>\n                &#039;limit&#039;: capacity,<br \/>\n                &#039;reset_time&#039;: reset_time<br \/>\n            }<\/p>\n<p>        elif algorithm &#061;&#061; &#039;fixed_window&#039;:<br \/>\n            limit &#061; kwargs.get(&#039;limit&#039;, 100)<br \/>\n            window_seconds &#061; kwargs.get(&#039;window_seconds&#039;, 60)<\/p>\n<p>            now &#061; time.time()<br \/>\n            window_start &#061; math.floor(now \/ window_seconds) * window_seconds<br \/>\n            redis_key &#061; f&#034;{self.namespace}:fixed_window:{key}:{window_start}&#034;<\/p>\n<p>            count &#061; int(self.redis.get(redis_key) or 0)<br \/>\n            remaining &#061; max(0, limit &#8211; count)<br \/>\n            reset_time &#061; window_start &#043; window_seconds<\/p>\n<p>            return {<br \/>\n                &#039;remaining&#039;: remaining,<br \/>\n                &#039;limit&#039;: limit,<br \/>\n                &#039;reset_time&#039;: reset_time<br \/>\n            }<\/p>\n<p>        return None<\/p>\n<p>    def reset_limits(self, key: str, algorithm: str &#061; &#039;token_bucket&#039;):<br \/>\n        &#034;&#034;&#034;\u91cd\u7f6e\u9650\u5236&#034;&#034;&#034;<br \/>\n        if algorithm &#061;&#061; &#039;token_bucket&#039;:<br \/>\n            redis_key &#061; f&#034;{self.namespace}:token_bucket:{key}&#034;<br \/>\n            self.redis.delete(redis_key)<br \/>\n        elif algorithm &#061;&#061; &#039;fixed_window&#039;:<br \/>\n            # \u5220\u9664\u6240\u6709\u76f8\u5173\u7684\u7a97\u53e3\u952e<br \/>\n            pattern &#061; f&#034;{self.namespace}:fixed_window:{key}:*&#034;<br \/>\n            keys &#061; self.redis.keys(pattern)<br \/>\n            if keys:<br \/>\n                self.redis.delete(*keys)<br \/>\n        elif algorithm &#061;&#061; &#039;sliding_window&#039;:<br \/>\n            redis_key &#061; f&#034;{self.namespace}:sliding_window:{key}&#034;<br \/>\n            self.redis.delete(redis_key)<\/p>\n<p>class DistributedRateLimiter:<br \/>\n    &#034;&#034;&#034;\u5206\u5e03\u5f0f\u901f\u7387\u9650\u5236\u7ba1\u7406\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, redis_client: redis.Redis, config: Dict):<br \/>\n        self.redis &#061; redis_client<br \/>\n        self.config &#061; config<br \/>\n        self.redis_limiter &#061; RedisRateLimiter(redis_client)<\/p>\n<p>        # \u672c\u5730\u7f13\u5b58&#xff0c;\u51cf\u5c11Redis\u8bbf\u95ee<br \/>\n        self.local_cache &#061; {}<br \/>\n        self.cache_ttl &#061; 1  # \u672c\u5730\u7f13\u5b581\u79d2<\/p>\n<p>    def is_allowed(self, identifier: str, rule_name: str) -&gt; Tuple[bool, Dict]:<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u662f\u5426\u5141\u8bb8\u8bf7\u6c42&#034;&#034;&#034;<\/p>\n<p>        # \u68c0\u67e5\u672c\u5730\u7f13\u5b58<br \/>\n        cache_key &#061; f&#034;{identifier}:{rule_name}&#034;<br \/>\n        cached &#061; self.local_cache.get(cache_key)<br \/>\n        if cached and time.time() &#8211; cached[&#039;timestamp&#039;] &lt; self.cache_ttl:<br \/>\n            return cached[&#039;allowed&#039;], cached[&#039;result&#039;]<\/p>\n<p>        # \u83b7\u53d6\u89c4\u5219\u914d\u7f6e<br \/>\n        rule &#061; self.config.get(&#039;rules&#039;, {}).get(rule_name)<br \/>\n        if not rule:<br \/>\n            # \u6ca1\u6709\u89c4\u5219\u914d\u7f6e&#xff0c;\u9ed8\u8ba4\u5141\u8bb8<br \/>\n            return True, {&#039;allowed&#039;: True, &#039;rule&#039;: rule_name}<\/p>\n<p>        algorithm &#061; rule.get(&#039;algorithm&#039;, &#039;token_bucket&#039;)<br \/>\n        params &#061; rule.get(&#039;params&#039;, {})<\/p>\n<p>        # \u6839\u636e\u7b97\u6cd5\u8c03\u7528\u76f8\u5e94\u7684Redis\u9650\u5236\u5668<br \/>\n        if algorithm &#061;&#061; &#039;token_bucket&#039;:<br \/>\n            allowed, result &#061; self.redis_limiter.token_bucket_is_allowed(<br \/>\n                f&#034;{rule_name}:{identifier}&#034;,<br \/>\n                params.get(&#039;capacity&#039;, 100),<br \/>\n                params.get(&#039;refill_rate&#039;, 1.0),<br \/>\n                params.get(&#039;cost&#039;, 1)<br \/>\n            )<br \/>\n        elif algorithm &#061;&#061; &#039;fixed_window&#039;:<br \/>\n            allowed, result &#061; self.redis_limiter.fixed_window_is_allowed(<br \/>\n                f&#034;{rule_name}:{identifier}&#034;,<br \/>\n                params.get(&#039;limit&#039;, 100),<br \/>\n                params.get(&#039;window_seconds&#039;, 60),<br \/>\n                params.get(&#039;cost&#039;, 1)<br \/>\n            )<br \/>\n        elif algorithm &#061;&#061; &#039;sliding_window&#039;:<br \/>\n            allowed, result &#061; self.redis_limiter.sliding_window_is_allowed(<br \/>\n                f&#034;{rule_name}:{identifier}&#034;,<br \/>\n                params.get(&#039;limit&#039;, 100),<br \/>\n                params.get(&#039;window_seconds&#039;, 60),<br \/>\n                params.get(&#039;cost&#039;, 1)<br \/>\n            )<br \/>\n        else:<br \/>\n            # \u672a\u77e5\u7b97\u6cd5&#xff0c;\u9ed8\u8ba4\u5141\u8bb8<br \/>\n            return True, {&#039;allowed&#039;: True, &#039;rule&#039;: rule_name}<\/p>\n<p>        # \u6dfb\u52a0\u89c4\u5219\u4fe1\u606f\u5230\u7ed3\u679c<br \/>\n        result[&#039;rule&#039;] &#061; rule_name<br \/>\n        result[&#039;algorithm&#039;] &#061; algorithm<\/p>\n<p>        # \u7f13\u5b58\u7ed3\u679c<br \/>\n        self.local_cache[cache_key] &#061; {<br \/>\n            &#039;allowed&#039;: allowed,<br \/>\n            &#039;result&#039;: result,<br \/>\n            &#039;timestamp&#039;: time.time()<br \/>\n        }<\/p>\n<p>        return allowed, result<\/p>\n<p>    def get_all_quotas(self, identifier: str) -&gt; Dict:<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u6240\u6709\u89c4\u5219\u7684\u914d\u989d\u4fe1\u606f&#034;&#034;&#034;<br \/>\n        quotas &#061; {}<\/p>\n<p>        for rule_name, rule in self.config.get(&#039;rules&#039;, {}).items():<br \/>\n            algorithm &#061; rule.get(&#039;algorithm&#039;, &#039;token_bucket&#039;)<br \/>\n            params &#061; rule.get(&#039;params&#039;, {})<\/p>\n<p>            quota &#061; self.redis_limiter.get_quotas(<br \/>\n                f&#034;{rule_name}:{identifier}&#034;,<br \/>\n                algorithm,<br \/>\n                **params<br \/>\n            )<\/p>\n<p>            if quota:<br \/>\n                quotas[rule_name] &#061; {<br \/>\n                    **quota,<br \/>\n                    &#039;algorithm&#039;: algorithm,<br \/>\n                    &#039;rule&#039;: rule_name<br \/>\n                }<\/p>\n<p>        return quotas<\/p>\n<p>    def create_rate_limit_response(self, identifier: str, rule_name: str,<br \/>\n                                  result: Dict) -&gt; Tuple[Dict, Dict]:<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u901f\u7387\u9650\u5236\u54cd\u5e94&#034;&#034;&#034;<\/p>\n<p>        if result.get(&#039;allowed&#039;, True):<br \/>\n            # \u5141\u8bb8\u8bf7\u6c42&#xff0c;\u6dfb\u52a0\u901f\u7387\u9650\u5236\u5934\u90e8<br \/>\n            headers &#061; {<br \/>\n                &#039;X-RateLimit-Limit&#039;: str(result.get(&#039;limit&#039;, 0)),<br \/>\n                &#039;X-RateLimit-Remaining&#039;: str(result.get(&#039;remaining&#039;, 0)),<br \/>\n                &#039;X-RateLimit-Reset&#039;: str(int(result.get(&#039;reset_time&#039;, time.time()))),<br \/>\n                &#039;X-RateLimit-Rule&#039;: rule_name<br \/>\n            }<\/p>\n<p>            return {&#039;allowed&#039;: True}, headers<\/p>\n<p>        else:<br \/>\n            # \u8bf7\u6c42\u88ab\u9650\u5236<br \/>\n            retry_after &#061; result.get(&#039;retry_after&#039;, 60)<br \/>\n            reset_time &#061; result.get(&#039;reset_time&#039;, time.time() &#043; retry_after)<\/p>\n<p>            # \u54cd\u5e94\u4f53<br \/>\n            response_body &#061; {<br \/>\n                &#039;error&#039;: {<br \/>\n                    &#039;code&#039;: &#039;RATE_LIMIT_EXCEEDED&#039;,<br \/>\n                    &#039;message&#039;: &#039;Too many requests&#039;,<br \/>\n                    &#039;details&#039;: {<br \/>\n                        &#039;rule&#039;: rule_name,<br \/>\n                        &#039;retry_after&#039;: retry_after,<br \/>\n                        &#039;reset_time&#039;: reset_time,<br \/>\n                        &#039;limit&#039;: result.get(&#039;limit&#039;, 0),<br \/>\n                        &#039;remaining&#039;: result.get(&#039;remaining&#039;, 0)<br \/>\n                    }<br \/>\n                }<br \/>\n            }<\/p>\n<p>            # \u54cd\u5e94\u5934\u90e8<br \/>\n            headers &#061; {<br \/>\n                &#039;Retry-After&#039;: str(retry_after),<br \/>\n                &#039;X-RateLimit-Limit&#039;: str(result.get(&#039;limit&#039;, 0)),<br \/>\n                &#039;X-RateLimit-Remaining&#039;: str(result.get(&#039;remaining&#039;, 0)),<br \/>\n                &#039;X-RateLimit-Reset&#039;: str(int(reset_time)),<br \/>\n                &#039;X-RateLimit-Rule&#039;: rule_name<br \/>\n            }<\/p>\n<p>            return response_body, headers<\/p>\n<p># \u914d\u7f6e\u793a\u4f8b<br \/>\nRATE_LIMIT_CONFIG &#061; {<br \/>\n    &#039;rules&#039;: {<br \/>\n        &#039;ip_global&#039;: {<br \/>\n            &#039;algorithm&#039;: &#039;token_bucket&#039;,<br \/>\n            &#039;params&#039;: {<br \/>\n                &#039;capacity&#039;: 1000,<br \/>\n                &#039;refill_rate&#039;: 10.0,  # 10\u8bf7\u6c42\/\u79d2<br \/>\n                &#039;cost&#039;: 1<br \/>\n            },<br \/>\n            &#039;description&#039;: &#039;\u5168\u5c40IP\u9650\u5236&#039;<br \/>\n        },<br \/>\n        &#039;user_auth&#039;: {<br \/>\n            &#039;algorithm&#039;: &#039;fixed_window&#039;,<br \/>\n            &#039;params&#039;: {<br \/>\n                &#039;limit&#039;: 10,<br \/>\n                &#039;window_seconds&#039;: 60,  # 10\u8bf7\u6c42\/\u5206\u949f<br \/>\n                &#039;cost&#039;: 1<br \/>\n            },<br \/>\n            &#039;description&#039;: &#039;\u7528\u6237\u8ba4\u8bc1\u9650\u5236&#039;<br \/>\n        },<br \/>\n        &#039;api_endpoint&#039;: {<br \/>\n            &#039;algorithm&#039;: &#039;sliding_window&#039;,<br \/>\n            &#039;params&#039;: {<br \/>\n                &#039;limit&#039;: 60,<br \/>\n                &#039;window_seconds&#039;: 60,  # 60\u8bf7\u6c42\/\u5206\u949f<br \/>\n                &#039;cost&#039;: 1<br \/>\n            },<br \/>\n            &#039;description&#039;: &#039;API\u7aef\u70b9\u9650\u5236&#039;<br \/>\n        },<br \/>\n        &#039;upload_endpoint&#039;: {<br \/>\n            &#039;algorithm&#039;: &#039;token_bucket&#039;,<br \/>\n            &#039;params&#039;: {<br \/>\n                &#039;capacity&#039;: 10,<br \/>\n                &#039;refill_rate&#039;: 0.1,  # \u6bcf10\u79d21\u4e2a\u8bf7\u6c42<br \/>\n                &#039;cost&#039;: 1<br \/>\n            },<br \/>\n            &#039;description&#039;: &#039;\u4e0a\u4f20\u7aef\u70b9\u9650\u5236&#039;<br \/>\n        }<br \/>\n    },<br \/>\n    &#039;default_rule&#039;: &#039;ip_global&#039;<br \/>\n}<\/p>\n<h4>22.3.2 \u667a\u80fd\u901f\u7387\u9650\u5236\u4e2d\u95f4\u4ef6<\/h4>\n<p>python<\/p>\n<p># \u667a\u80fd\u901f\u7387\u9650\u5236\u4e2d\u95f4\u4ef6<br \/>\nfrom flask import Flask, request, jsonify, g<br \/>\nimport time<br \/>\nfrom functools import wraps<br \/>\nimport uuid<\/p>\n<p>class IntelligentRateLimitMiddleware:<br \/>\n    &#034;&#034;&#034;\u667a\u80fd\u901f\u7387\u9650\u5236\u4e2d\u95f4\u4ef6&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, app: Flask &#061; None, config: Dict &#061; None):<br \/>\n        self.app &#061; app<br \/>\n        self.config &#061; {<br \/>\n            &#039;enabled&#039;: True,<br \/>\n            &#039;default_limits&#039;: {<br \/>\n                &#039;ip&#039;: &#039;100 per hour&#039;,<br \/>\n                &#039;user&#039;: &#039;1000 per day&#039;,<br \/>\n                &#039;endpoint&#039;: &#039;60 per minute&#039;<br \/>\n            },<br \/>\n            &#039;exempt_paths&#039;: [&#039;\/health&#039;, &#039;\/metrics&#039;],<br \/>\n            &#039;exempt_methods&#039;: [&#039;OPTIONS&#039;],<br \/>\n            &#039;cost_calculator&#039;: self.default_cost_calculator,<br \/>\n            &#039;identifier_extractor&#039;: self.default_identifier_extractor,<br \/>\n            &#039;response_handler&#039;: self.default_response_handler,<br \/>\n            &#039;adaptive_enabled&#039;: True,<br \/>\n            &#039;anomaly_detection_enabled&#039;: True,<br \/>\n            ** (config or {})<br \/>\n        }<\/p>\n<p>        # \u521d\u59cb\u5316\u9650\u5236\u5668<br \/>\n        self.rate_limiter &#061; DistributedRateLimiter(<br \/>\n            redis_client&#061;redis.Redis(),<br \/>\n            config&#061;RATE_LIMIT_CONFIG<br \/>\n        )<\/p>\n<p>        # \u5f02\u5e38\u68c0\u6d4b\u5668<br \/>\n        self.anomaly_detector &#061; RateLimitAnomalyDetector()<\/p>\n<p>        # \u81ea\u9002\u5e94\u8c03\u6574\u5668<br \/>\n        self.adaptive_adjuster &#061; AdaptiveRateAdjuster()<\/p>\n<p>        if app:<br \/>\n            self.init_app(app)<\/p>\n<p>    def init_app(self, app: Flask):<br \/>\n        &#034;&#034;&#034;\u521d\u59cb\u5316Flask\u5e94\u7528&#034;&#034;&#034;<br \/>\n        self.app &#061; app<\/p>\n<p>        # \u6ce8\u518c\u4e2d\u95f4\u4ef6<br \/>\n        &#064;app.before_request<br \/>\n        def rate_limit_check():<br \/>\n            self.check_rate_limit()<\/p>\n<p>        # \u6ce8\u518c\u9519\u8bef\u5904\u7406\u5668<br \/>\n        &#064;app.errorhandler(429)<br \/>\n        def handle_rate_limit(e):<br \/>\n            return self.handle_rate_limit_error(e)<\/p>\n<p>    def check_rate_limit(self):<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u901f\u7387\u9650\u5236&#034;&#034;&#034;<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u542f\u7528<br \/>\n        if not self.config[&#039;enabled&#039;]:<br \/>\n            return<\/p>\n<p>        # \u68c0\u67e5\u8c41\u514d\u8def\u5f84<br \/>\n        if request.path in self.config[&#039;exempt_paths&#039;]:<br \/>\n            return<\/p>\n<p>        # \u68c0\u67e5\u8c41\u514d\u65b9\u6cd5<br \/>\n        if request.method in self.config[&#039;exempt_methods&#039;]:<br \/>\n            return<\/p>\n<p>        # \u63d0\u53d6\u6807\u8bc6\u7b26<br \/>\n        identifiers &#061; self.extract_identifiers(request)<\/p>\n<p>        # \u786e\u5b9a\u9002\u7528\u7684\u89c4\u5219<br \/>\n        rules &#061; self.determine_applicable_rules(request, identifiers)<\/p>\n<p>        # \u68c0\u67e5\u6bcf\u4e2a\u89c4\u5219<br \/>\n        for rule_name, identifier in rules:<br \/>\n            allowed, result &#061; self.rate_limiter.is_allowed(identifier, rule_name)<\/p>\n<p>            if not allowed:<br \/>\n                # \u68c0\u6d4b\u5f02\u5e38\u884c\u4e3a<br \/>\n                if self.config[&#039;anomaly_detection_enabled&#039;]:<br \/>\n                    self.anomaly_detector.record_violation(<br \/>\n                        identifier, rule_name, request<br \/>\n                    )<\/p>\n<p>                # \u89e6\u53d1\u901f\u7387\u9650\u5236<br \/>\n                self.trigger_rate_limit(identifier, rule_name, result)<br \/>\n                break<\/p>\n<p>        # \u8bb0\u5f55\u8bf7\u6c42&#xff08;\u7528\u4e8e\u81ea\u9002\u5e94\u8c03\u6574&#xff09;<br \/>\n        if self.config[&#039;adaptive_enabled&#039;]:<br \/>\n            self.adaptive_adjuster.record_request(request, identifiers)<\/p>\n<p>    def extract_identifiers(self, request) -&gt; Dict[str, str]:<br \/>\n        &#034;&#034;&#034;\u63d0\u53d6\u6807\u8bc6\u7b26&#034;&#034;&#034;<br \/>\n        identifiers &#061; {}<\/p>\n<p>        # \u63d0\u53d6IP<br \/>\n        if request.headers.get(&#039;X-Forwarded-For&#039;):<br \/>\n            ip &#061; request.headers[&#039;X-Forwarded-For&#039;].split(&#039;,&#039;)[0].strip()<br \/>\n        else:<br \/>\n            ip &#061; request.remote_addr<br \/>\n        identifiers[&#039;ip&#039;] &#061; ip<\/p>\n<p>        # \u63d0\u53d6\u7528\u6237ID&#xff08;\u5982\u679c\u5df2\u8ba4\u8bc1&#xff09;<br \/>\n        if hasattr(g, &#039;user&#039;) and g.user:<br \/>\n            identifiers[&#039;user&#039;] &#061; g.user.id<br \/>\n        elif request.headers.get(&#039;X-User-ID&#039;):<br \/>\n            identifiers[&#039;user&#039;] &#061; request.headers[&#039;X-User-ID&#039;]<\/p>\n<p>        # \u63d0\u53d6API\u5bc6\u94a5<br \/>\n        if request.headers.get(&#039;X-API-Key&#039;):<br \/>\n            identifiers[&#039;api_key&#039;] &#061; request.headers[&#039;X-API-Key&#039;]<\/p>\n<p>        # \u63d0\u53d6\u4f1a\u8bddID<br \/>\n        if request.cookies.get(&#039;session_id&#039;):<br \/>\n            identifiers[&#039;session&#039;] &#061; request.cookies[&#039;session_id&#039;]<\/p>\n<p>        return identifiers<\/p>\n<p>    def determine_applicable_rules(self, request, identifiers: Dict) -&gt; List[Tuple[str, str]]:<br \/>\n        &#034;&#034;&#034;\u786e\u5b9a\u9002\u7528\u7684\u89c4\u5219&#034;&#034;&#034;<br \/>\n        rules &#061; []<\/p>\n<p>        # \u57fa\u7840\u89c4\u5219&#xff1a;IP\u9650\u5236<br \/>\n        if &#039;ip&#039; in identifiers:<br \/>\n            rules.append((&#039;ip_global&#039;, identifiers[&#039;ip&#039;]))<\/p>\n<p>        # \u7528\u6237\u89c4\u5219<br \/>\n        if &#039;user&#039; in identifiers:<br \/>\n            # \u7528\u6237\u7279\u5b9a\u9650\u5236<br \/>\n            rules.append((&#039;user_auth&#039;, identifiers[&#039;user&#039;]))<\/p>\n<p>            # \u7aef\u70b9\u7279\u5b9a\u7528\u6237\u9650\u5236<br \/>\n            endpoint_rule &#061; f&#034;user_endpoint:{request.path}&#034;<br \/>\n            rules.append((endpoint_rule, identifiers[&#039;user&#039;]))<\/p>\n<p>        # API\u5bc6\u94a5\u89c4\u5219<br \/>\n        if &#039;api_key&#039; in identifiers:<br \/>\n            rules.append((&#039;api_key&#039;, identifiers[&#039;api_key&#039;]))<\/p>\n<p>        # \u7aef\u70b9\u89c4\u5219<br \/>\n        endpoint_key &#061; f&#034;endpoint:{request.method}:{request.path}&#034;<br \/>\n        rules.append((&#039;api_endpoint&#039;, endpoint_key))<\/p>\n<p>        # \u7279\u6b8a\u7aef\u70b9\u89c4\u5219<br \/>\n        if request.path.startswith(&#039;\/api\/upload&#039;):<br \/>\n            rules.append((&#039;upload_endpoint&#039;, identifiers.get(&#039;ip&#039;, &#039;unknown&#039;)))<\/p>\n<p>        return rules<\/p>\n<p>    def default_cost_calculator(self, request) -&gt; int:<br \/>\n        &#034;&#034;&#034;\u9ed8\u8ba4\u6210\u672c\u8ba1\u7b97\u5668&#034;&#034;&#034;<br \/>\n        # \u6839\u636e\u8bf7\u6c42\u5927\u5c0f\u3001\u590d\u6742\u5ea6\u7b49\u8ba1\u7b97\u6210\u672c<br \/>\n        cost &#061; 1<\/p>\n<p>        # \u8bf7\u6c42\u4f53\u5927\u5c0f\u6210\u672c<br \/>\n        content_length &#061; request.content_length or 0<br \/>\n        if content_length &gt; 1024 * 1024:  # 1MB<br \/>\n            cost &#043;&#061; 1<br \/>\n        elif content_length &gt; 10 * 1024 * 1024:  # 10MB<br \/>\n            cost &#043;&#061; 5<\/p>\n<p>        # \u590d\u6742\u64cd\u4f5c\u6210\u672c<br \/>\n        if request.method in [&#039;POST&#039;, &#039;PUT&#039;, &#039;DELETE&#039;]:<br \/>\n            cost &#043;&#061; 1<\/p>\n<p>        # \u654f\u611f\u7aef\u70b9\u6210\u672c<br \/>\n        if request.path.startswith(&#039;\/api\/admin\/&#039;):<br \/>\n            cost &#043;&#061; 2<\/p>\n<p>        return cost<\/p>\n<p>    def default_identifier_extractor(self, request) -&gt; str:<br \/>\n        &#034;&#034;&#034;\u9ed8\u8ba4\u6807\u8bc6\u7b26\u63d0\u53d6\u5668&#034;&#034;&#034;<br \/>\n        # \u4f7f\u7528IP\u4f5c\u4e3a\u9ed8\u8ba4\u6807\u8bc6\u7b26<br \/>\n        if request.headers.get(&#039;X-Forwarded-For&#039;):<br \/>\n            return request.headers[&#039;X-Forwarded-For&#039;].split(&#039;,&#039;)[0].strip()<br \/>\n        return request.remote_addr<\/p>\n<p>    def default_response_handler(self, rule_name: str, result: Dict) -&gt; Tuple[Dict, int, Dict]:<br \/>\n        &#034;&#034;&#034;\u9ed8\u8ba4\u54cd\u5e94\u5904\u7406\u5668&#034;&#034;&#034;<br \/>\n        retry_after &#061; result.get(&#039;retry_after&#039;, 60)<\/p>\n<p>        response_body &#061; {<br \/>\n            &#039;error&#039;: {<br \/>\n                &#039;code&#039;: &#039;RATE_LIMIT_EXCEEDED&#039;,<br \/>\n                &#039;message&#039;: &#039;Too many requests. Please try again later.&#039;,<br \/>\n                &#039;details&#039;: {<br \/>\n                    &#039;rule&#039;: rule_name,<br \/>\n                    &#039;retry_after&#039;: retry_after,<br \/>\n                    &#039;reset_time&#039;: result.get(&#039;reset_time&#039;),<br \/>\n                    &#039;limit&#039;: result.get(&#039;limit&#039;),<br \/>\n                    &#039;remaining&#039;: result.get(&#039;remaining&#039;)<br \/>\n                }<br \/>\n            }<br \/>\n        }<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Retry-After&#039;: str(retry_after),<br \/>\n            &#039;X-RateLimit-Limit&#039;: str(result.get(&#039;limit&#039;, 0)),<br \/>\n            &#039;X-RateLimit-Remaining&#039;: str(result.get(&#039;remaining&#039;, 0)),<br \/>\n            &#039;X-RateLimit-Reset&#039;: str(int(result.get(&#039;reset_time&#039;, time.time()))),<br \/>\n            &#039;X-RateLimit-Rule&#039;: rule_name<br \/>\n        }<\/p>\n<p>        return response_body, 429, headers<\/p>\n<p>    def trigger_rate_limit(self, identifier: str, rule_name: str, result: Dict):<br \/>\n        &#034;&#034;&#034;\u89e6\u53d1\u901f\u7387\u9650\u5236&#034;&#034;&#034;<br \/>\n        # \u8bb0\u5f55\u9650\u5236\u4e8b\u4ef6<br \/>\n        self.log_rate_limit_event(identifier, rule_name, result)<\/p>\n<p>        # \u751f\u6210\u54cd\u5e94<br \/>\n        response_body, status_code, headers &#061; self.config[&#039;response_handler&#039;](<br \/>\n            rule_name, result<br \/>\n        )<\/p>\n<p>        # \u629b\u51fa\u5f02\u5e38&#xff0c;\u7531\u9519\u8bef\u5904\u7406\u5668\u5904\u7406<br \/>\n        from werkzeug.exceptions import TooManyRequests<br \/>\n        raise TooManyRequests(response&#061;jsonify(response_body), headers&#061;headers)<\/p>\n<p>    def log_rate_limit_event(self, identifier: str, rule_name: str, result: Dict):<br \/>\n        &#034;&#034;&#034;\u8bb0\u5f55\u901f\u7387\u9650\u5236\u4e8b\u4ef6&#034;&#034;&#034;<br \/>\n        event &#061; {<br \/>\n            &#039;timestamp&#039;: time.time(),<br \/>\n            &#039;identifier&#039;: identifier,<br \/>\n            &#039;rule&#039;: rule_name,<br \/>\n            &#039;remaining&#039;: result.get(&#039;remaining&#039;, 0),<br \/>\n            &#039;limit&#039;: result.get(&#039;limit&#039;, 0),<br \/>\n            &#039;retry_after&#039;: result.get(&#039;retry_after&#039;, 0),<br \/>\n            &#039;request_path&#039;: request.path,<br \/>\n            &#039;request_method&#039;: request.method,<br \/>\n            &#039;user_agent&#039;: request.headers.get(&#039;User-Agent&#039;),<br \/>\n            &#039;client_ip&#039;: request.remote_addr<br \/>\n        }<\/p>\n<p>        # \u5728\u5b9e\u9645\u5e94\u7528\u4e2d&#xff0c;\u8fd9\u91cc\u4f1a\u8bb0\u5f55\u5230\u65e5\u5fd7\u7cfb\u7edf<br \/>\n        print(f&#034;[Rate Limit] {json.dumps(event)}&#034;)<\/p>\n<p>        # \u5199\u5165\u6587\u4ef6\u65e5\u5fd7<br \/>\n        with open(&#039;rate_limit_events.log&#039;, &#039;a&#039;) as f:<br \/>\n            f.write(json.dumps(event) &#043; &#039;\\\\n&#039;)<\/p>\n<p>    def handle_rate_limit_error(self, error):<br \/>\n        &#034;&#034;&#034;\u5904\u7406\u901f\u7387\u9650\u5236\u9519\u8bef&#034;&#034;&#034;<br \/>\n        # \u4ece\u9519\u8bef\u4e2d\u63d0\u53d6\u54cd\u5e94\u548c\u5934\u90e8<br \/>\n        response &#061; error.response<br \/>\n        return response<\/p>\n<p>    def get_rate_limit_info(self, identifier: str) -&gt; Dict:<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u901f\u7387\u9650\u5236\u4fe1\u606f&#034;&#034;&#034;<br \/>\n        return self.rate_limiter.get_all_quotas(identifier)<\/p>\n<p>class RateLimitAnomalyDetector:<br \/>\n    &#034;&#034;&#034;\u901f\u7387\u9650\u5236\u5f02\u5e38\u68c0\u6d4b\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.violation_history &#061; {}<br \/>\n        self.anomaly_patterns &#061; {}<\/p>\n<p>    def record_violation(self, identifier: str, rule_name: str, request):<br \/>\n        &#034;&#034;&#034;\u8bb0\u5f55\u8fdd\u89c4&#034;&#034;&#034;<br \/>\n        key &#061; f&#034;{identifier}:{rule_name}&#034;<\/p>\n<p>        if key not in self.violation_history:<br \/>\n            self.violation_history[key] &#061; {<br \/>\n                &#039;count&#039;: 0,<br \/>\n                &#039;first_violation&#039;: time.time(),<br \/>\n                &#039;last_violation&#039;: time.time(),<br \/>\n                &#039;requests&#039;: []<br \/>\n            }<\/p>\n<p>        record &#061; self.violation_history[key]<br \/>\n        record[&#039;count&#039;] &#043;&#061; 1<br \/>\n        record[&#039;last_violation&#039;] &#061; time.time()<\/p>\n<p>        # \u8bb0\u5f55\u8bf7\u6c42\u8be6\u60c5<br \/>\n        request_info &#061; {<br \/>\n            &#039;timestamp&#039;: time.time(),<br \/>\n            &#039;path&#039;: request.path,<br \/>\n            &#039;method&#039;: request.method,<br \/>\n            &#039;user_agent&#039;: request.headers.get(&#039;User-Agent&#039;),<br \/>\n            &#039;client_ip&#039;: request.remote_addr<br \/>\n        }<br \/>\n        record[&#039;requests&#039;].append(request_info)<\/p>\n<p>        # \u68c0\u6d4b\u5f02\u5e38\u6a21\u5f0f<br \/>\n        self.detect_anomaly(key, record)<\/p>\n<p>    def detect_anomaly(self, key: str, record: Dict):<br \/>\n        &#034;&#034;&#034;\u68c0\u6d4b\u5f02\u5e38\u6a21\u5f0f&#034;&#034;&#034;<\/p>\n<p>        # \u68c0\u6d4b\u9891\u7e41\u8fdd\u89c4<br \/>\n        time_window &#061; 300  # 5\u5206\u949f<br \/>\n        violations_in_window &#061; sum(<br \/>\n            1 for req in record[&#039;requests&#039;]<br \/>\n            if time.time() &#8211; req[&#039;timestamp&#039;] &lt; time_window<br \/>\n        )<\/p>\n<p>        if violations_in_window &gt; 10:<br \/>\n            # \u9891\u7e41\u8fdd\u89c4&#xff0c;\u53ef\u80fd\u662f\u6076\u610f\u653b\u51fb<br \/>\n            self.trigger_anomaly_alert(key, &#039;frequent_violations&#039;, {<br \/>\n                &#039;violations_in_window&#039;: violations_in_window,<br \/>\n                &#039;time_window&#039;: time_window<br \/>\n            })<\/p>\n<p>        # \u68c0\u6d4b\u5206\u5e03\u5f0f\u653b\u51fb\u6a21\u5f0f<br \/>\n        if self.is_distributed_attack_pattern(record):<br \/>\n            self.trigger_anomaly_alert(key, &#039;distributed_attack&#039;, {<br \/>\n                &#039;request_patterns&#039;: len(set(r[&#039;client_ip&#039;] for r in record[&#039;requests&#039;]))<br \/>\n            })<\/p>\n<p>    def is_distributed_attack_pattern(self, record: Dict) -&gt; bool:<br \/>\n        &#034;&#034;&#034;\u68c0\u6d4b\u5206\u5e03\u5f0f\u653b\u51fb\u6a21\u5f0f&#034;&#034;&#034;<br \/>\n        if len(record[&#039;requests&#039;]) &lt; 20:<br \/>\n            return False<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u6765\u81ea\u591a\u4e2aIP\u5730\u5740<br \/>\n        unique_ips &#061; set(r[&#039;client_ip&#039;] for r in record[&#039;requests&#039;])<br \/>\n        if len(unique_ips) &gt; 5:<br \/>\n            return True<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u6709\u89c4\u5f8b\u7684\u8bf7\u6c42\u6a21\u5f0f<br \/>\n        timestamps &#061; [r[&#039;timestamp&#039;] for r in record[&#039;requests&#039;]]<br \/>\n        if len(timestamps) &gt;&#061; 10:<br \/>\n            intervals &#061; [timestamps[i&#043;1] &#8211; timestamps[i] for i in range(len(timestamps)-1)]<br \/>\n            avg_interval &#061; sum(intervals) \/ len(intervals)<\/p>\n<p>            # \u68c0\u67e5\u95f4\u9694\u662f\u5426\u8fc7\u4e8e\u89c4\u5f8b<br \/>\n            if avg_interval &gt; 0:<br \/>\n                deviation &#061; sum(abs(i &#8211; avg_interval) for i in intervals) \/ len(intervals)<br \/>\n                if deviation \/ avg_interval &lt; 0.1:  # \u504f\u5dee\u5c0f\u4e8e10%<br \/>\n                    return True<\/p>\n<p>        return False<\/p>\n<p>    def trigger_anomaly_alert(self, key: str, anomaly_type: str, details: Dict):<br \/>\n        &#034;&#034;&#034;\u89e6\u53d1\u5f02\u5e38\u8b66\u62a5&#034;&#034;&#034;<br \/>\n        alert &#061; {<br \/>\n            &#039;timestamp&#039;: time.time(),<br \/>\n            &#039;key&#039;: key,<br \/>\n            &#039;type&#039;: anomaly_type,<br \/>\n            &#039;details&#039;: details,<br \/>\n            &#039;severity&#039;: &#039;high&#039;<br \/>\n        }<\/p>\n<p>        print(f&#034;[Rate Limit Anomaly] {json.dumps(alert)}&#034;)<\/p>\n<p>        with open(&#039;rate_limit_anomalies.log&#039;, &#039;a&#039;) as f:<br \/>\n            f.write(json.dumps(alert) &#043; &#039;\\\\n&#039;)<\/p>\n<p>class AdaptiveRateAdjuster:<br \/>\n    &#034;&#034;&#034;\u81ea\u9002\u5e94\u901f\u7387\u8c03\u6574\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.request_history &#061; deque(maxlen&#061;1000)<br \/>\n        self.adjustment_history &#061; []<\/p>\n<p>    def record_request(self, request, identifiers: Dict):<br \/>\n        &#034;&#034;&#034;\u8bb0\u5f55\u8bf7\u6c42&#034;&#034;&#034;<br \/>\n        request_record &#061; {<br \/>\n            &#039;timestamp&#039;: time.time(),<br \/>\n            &#039;path&#039;: request.path,<br \/>\n            &#039;method&#039;: request.method,<br \/>\n            &#039;identifiers&#039;: identifiers,<br \/>\n            &#039;response_time&#039;: None,  # \u5c06\u5728\u54cd\u5e94\u65f6\u586b\u5145<br \/>\n            &#039;status_code&#039;: None<br \/>\n        }<\/p>\n<p>        self.request_history.append(request_record)<\/p>\n<p>        # \u5b9a\u671f\u5206\u6790\u5e76\u8c03\u6574<br \/>\n        if len(self.request_history) % 100 &#061;&#061; 0:<br \/>\n            self.analyze_and_adjust()<\/p>\n<p>    def analyze_and_adjust(self):<br \/>\n        &#034;&#034;&#034;\u5206\u6790\u5e76\u8c03\u6574\u901f\u7387\u9650\u5236&#034;&#034;&#034;<br \/>\n        # \u5206\u6790\u8bf7\u6c42\u6a21\u5f0f<br \/>\n        recent_requests &#061; list(self.request_history)[-100:]  # \u6700\u8fd1100\u4e2a\u8bf7\u6c42<\/p>\n<p>        if not recent_requests:<br \/>\n            return<\/p>\n<p>        # \u8ba1\u7b97\u8bf7\u6c42\u901f\u7387<br \/>\n        time_window &#061; recent_requests[-1][&#039;timestamp&#039;] &#8211; recent_requests[0][&#039;timestamp&#039;]<br \/>\n        request_rate &#061; len(recent_requests) \/ time_window if time_window &gt; 0 else 0<\/p>\n<p>        # \u8ba1\u7b97\u9519\u8bef\u7387<br \/>\n        error_requests &#061; [r for r in recent_requests if r.get(&#039;status_code&#039;, 200) &gt;&#061; 400]<br \/>\n        error_rate &#061; len(error_requests) \/ len(recent_requests) if recent_requests else 0<\/p>\n<p>        # \u6839\u636e\u5206\u6790\u7ed3\u679c\u8c03\u6574<br \/>\n        if error_rate &gt; 0.1:  # \u9519\u8bef\u7387\u8d85\u8fc710%<br \/>\n            # \u964d\u4f4e\u9650\u5236&#xff0c;\u53ef\u80fd\u662f\u670d\u52a1\u5668\u538b\u529b\u5927<br \/>\n            self.adjust_limits(&#039;decrease&#039;, {<br \/>\n                &#039;reason&#039;: &#039;high_error_rate&#039;,<br \/>\n                &#039;error_rate&#039;: error_rate,<br \/>\n                &#039;request_rate&#039;: request_rate<br \/>\n            })<br \/>\n        elif request_rate &gt; 100 and error_rate &lt; 0.01:  # \u9ad8\u8bf7\u6c42\u7387&#xff0c;\u4f4e\u9519\u8bef\u7387<br \/>\n            # \u53ef\u4ee5\u8003\u8651\u589e\u52a0\u9650\u5236<br \/>\n            self.adjust_limits(&#039;increase&#039;, {<br \/>\n                &#039;reason&#039;: &#039;high_throughput_low_errors&#039;,<br \/>\n                &#039;request_rate&#039;: request_rate,<br \/>\n                &#039;error_rate&#039;: error_rate<br \/>\n            })<\/p>\n<p>    def adjust_limits(self, direction: str, context: Dict):<br \/>\n        &#034;&#034;&#034;\u8c03\u6574\u9650\u5236&#034;&#034;&#034;<br \/>\n        adjustment &#061; {<br \/>\n            &#039;timestamp&#039;: time.time(),<br \/>\n            &#039;direction&#039;: direction,<br \/>\n            &#039;context&#039;: context<br \/>\n        }<\/p>\n<p>        self.adjustment_history.append(adjustment)<\/p>\n<p>        print(f&#034;[Rate Limit Adjustment] {json.dumps(adjustment)}&#034;)<\/p>\n<h3>22.4 \u5ba2\u6237\u7aef\u5904\u7406\u7b56\u7565<\/h3>\n<h4>22.4.1 \u667a\u80fd\u91cd\u8bd5\u4e0e\u9000\u907f\u7b56\u7565<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5ba2\u6237\u7aef\u901f\u7387\u9650\u5236\u5904\u7406\u7b56\u7565<br \/>\nclass RateLimitAwareClient {<br \/>\n  constructor(config &#061; {}) {<br \/>\n    this.config &#061; {<br \/>\n      baseDelay: 1000,<br \/>\n      maxDelay: 60000,<br \/>\n      backoffFactor: 2,<br \/>\n      jitter: 0.1,<br \/>\n      maxRetries: 5,<br \/>\n      respectRetryAfter: true,<br \/>\n      quotaMonitoring: true,<br \/>\n      adaptiveBackoff: true,<br \/>\n      &#8230;config<br \/>\n    };<\/p>\n<p>    this.rateLimitInfo &#061; new Map();<br \/>\n    this.retryQueue &#061; [];<br \/>\n    this.quotaHistory &#061; [];<br \/>\n    this.circuitBreakers &#061; new Map();<\/p>\n<p>    \/\/ \u521d\u59cb\u5316\u76d1\u63a7<br \/>\n    this.initQuotaMonitoring();<br \/>\n  }<\/p>\n<p>  async request(endpoint, options &#061; {}) {<br \/>\n    const requestId &#061; this.generateRequestId();<br \/>\n    const startTime &#061; Date.now();<\/p>\n<p>    \/\/ \u68c0\u67e5\u901f\u7387\u9650\u5236\u72b6\u6001<br \/>\n    const rateLimitCheck &#061; this.checkRateLimitState(endpoint, options);<br \/>\n    if (!rateLimitCheck.allowed) {<br \/>\n      \/\/ \u901f\u7387\u9650\u5236\u5df2\u89e6\u53d1&#xff0c;\u7b49\u5f85\u6216\u62d2\u7edd<br \/>\n      if (options.ignoreRateLimit) {<br \/>\n        return this.executeRequest(requestId, endpoint, options);<br \/>\n      } else {<br \/>\n        return this.handleRateLimitedRequest(requestId, endpoint, options, rateLimitCheck);<br \/>\n      }<br \/>\n    }<\/p>\n<p>    \/\/ \u6267\u884c\u8bf7\u6c42<br \/>\n    try {<br \/>\n      const response &#061; await this.executeRequest(requestId, endpoint, options);<\/p>\n<p>      \/\/ \u66f4\u65b0\u901f\u7387\u9650\u5236\u4fe1\u606f<br \/>\n      this.updateRateLimitInfo(endpoint, response);<\/p>\n<p>      return response;<\/p>\n<p>    } catch (error) {<br \/>\n      \/\/ \u5904\u7406\u9519\u8bef<br \/>\n      if (error.name &#061;&#061;&#061; &#039;RateLimitError&#039;) {<br \/>\n        return this.handleRateLimitError(requestId, endpoint, options, error);<br \/>\n      }<\/p>\n<p>      throw error;<br \/>\n    }<br \/>\n  }<\/p>\n<p>  checkRateLimitState(endpoint, options) {<br \/>\n    const endpointKey &#061; this.getEndpointKey(endpoint, options);<br \/>\n    const info &#061; this.rateLimitInfo.get(endpointKey);<\/p>\n<p>    if (!info) {<br \/>\n      return { allowed: true };<br \/>\n    }<\/p>\n<p>    \/\/ \u68c0\u67e5\u5269\u4f59\u914d\u989d<br \/>\n    if (info.remaining &lt;&#061; 0) {<br \/>\n      const now &#061; Date.now();<br \/>\n      const resetTime &#061; info.resetTime * 1000; \/\/ \u8f6c\u6362\u4e3a\u6beb\u79d2<\/p>\n<p>      if (now &lt; resetTime) {<br \/>\n        \/\/ \u4ecd\u5728\u9650\u5236\u671f\u5185<br \/>\n        const retryAfter &#061; Math.ceil((resetTime &#8211; now) \/ 1000);<\/p>\n<p>        return {<br \/>\n          allowed: false,<br \/>\n          reason: &#039;quota_exhausted&#039;,<br \/>\n          retryAfter,<br \/>\n          resetTime<br \/>\n        };<br \/>\n      } else {<br \/>\n        \/\/ \u9650\u5236\u5df2\u8fc7\u671f<br \/>\n        return { allowed: true };<br \/>\n      }<br \/>\n    }<\/p>\n<p>    \/\/ \u68c0\u67e5\u7535\u8def\u65ad\u8def\u5668<br \/>\n    const circuitKey &#061; this.getCircuitKey(endpoint, options);<br \/>\n    if (!this.isCircuitClosed(circuitKey)) {<br \/>\n      return {<br \/>\n        allowed: false,<br \/>\n        reason: &#039;circuit_open&#039;,<br \/>\n        circuitKey<br \/>\n      };<br \/>\n    }<\/p>\n<p>    return { allowed: true };<br \/>\n  }<\/p>\n<p>  async executeRequest(requestId, endpoint, options) {<br \/>\n    const controller &#061; new AbortController();<br \/>\n    const timeoutId &#061; setTimeout(() &#061;&gt; controller.abort(), options.timeout || 30000);<\/p>\n<p>    try {<br \/>\n      const response &#061; await fetch(endpoint, {<br \/>\n        &#8230;options,<br \/>\n        signal: controller.signal,<br \/>\n        headers: {<br \/>\n          &#8230;options.headers,<br \/>\n          &#039;X-Request-ID&#039;: requestId,<br \/>\n          &#039;X-Client-Version&#039;: &#039;1.0.0&#039;<br \/>\n        }<br \/>\n      });<\/p>\n<p>      clearTimeout(timeoutId);<\/p>\n<p>      \/\/ \u68c0\u67e5\u901f\u7387\u9650\u5236\u5934\u90e8<br \/>\n      this.extractRateLimitHeaders(endpoint, response);<\/p>\n<p>      \/\/ \u68c0\u67e5\u72b6\u6001\u7801<br \/>\n      if (response.status &#061;&#061;&#061; 429) {<br \/>\n        throw new RateLimitError(&#039;Rate limit exceeded&#039;, response);<br \/>\n      }<\/p>\n<p>      return response;<\/p>\n<p>    } catch (error) {<br \/>\n      clearTimeout(timeoutId);<br \/>\n      throw error;<br \/>\n    }<br \/>\n  }<\/p>\n<p>  extractRateLimitHeaders(endpoint, response) {<br \/>\n    const endpointKey &#061; this.getEndpointKey(endpoint);<\/p>\n<p>    const limit &#061; response.headers.get(&#039;X-RateLimit-Limit&#039;);<br \/>\n    const remaining &#061; response.headers.get(&#039;X-RateLimit-Remaining&#039;);<br \/>\n    const reset &#061; response.headers.get(&#039;X-RateLimit-Reset&#039;);<br \/>\n    const retryAfter &#061; response.headers.get(&#039;Retry-After&#039;);<br \/>\n    const rule &#061; response.headers.get(&#039;X-RateLimit-Rule&#039;);<\/p>\n<p>    if (limit !&#061;&#061; null &amp;&amp; remaining !&#061;&#061; null &amp;&amp; reset !&#061;&#061; null) {<br \/>\n      this.rateLimitInfo.set(endpointKey, {<br \/>\n        limit: parseInt(limit, 10),<br \/>\n        remaining: parseInt(remaining, 10),<br \/>\n        resetTime: parseInt(reset, 10),<br \/>\n        rule: rule,<br \/>\n        lastUpdated: Date.now()<br \/>\n      });<\/p>\n<p>      \/\/ \u8bb0\u5f55\u914d\u989d\u5386\u53f2<br \/>\n      this.recordQuotaHistory(endpointKey, {<br \/>\n        limit: parseInt(limit, 10),<br \/>\n        remaining: parseInt(remaining, 10),<br \/>\n        timestamp: Date.now()<br \/>\n      });<br \/>\n    }<\/p>\n<p>    \/\/ \u5904\u7406Retry-After\u5934\u90e8<br \/>\n    if (retryAfter !&#061;&#061; null &amp;&amp; this.config.respectRetryAfter) {<br \/>\n      const retryAfterSeconds &#061; parseInt(retryAfter, 10);<br \/>\n      this.scheduleRetryDelay(endpointKey, retryAfterSeconds * 1000);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  async handleRateLimitedRequest(requestId, endpoint, options, rateLimitCheck) {<br \/>\n    const endpointKey &#061; this.getEndpointKey(endpoint, options);<\/p>\n<p>    \/\/ \u6839\u636e\u539f\u56e0\u91c7\u53d6\u4e0d\u540c\u7b56\u7565<br \/>\n    switch (rateLimitCheck.reason) {<br \/>\n      case &#039;quota_exhausted&#039;:<br \/>\n        return this.handleQuotaExhausted(requestId, endpoint, options, rateLimitCheck);<\/p>\n<p>      case &#039;circuit_open&#039;:<br \/>\n        return this.handleCircuitOpen(requestId, endpoint, options, rateLimitCheck);<\/p>\n<p>      default:<br \/>\n        throw new RateLimitError(&#096;Rate limited: ${rateLimitCheck.reason}&#096;);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  async handleQuotaExhausted(requestId, endpoint, options, rateLimitCheck) {<br \/>\n    const { retryAfter, resetTime } &#061; rateLimitCheck;<\/p>\n<p>    \/\/ \u8ba1\u7b97\u7b49\u5f85\u65f6\u95f4<br \/>\n    let waitTime &#061; retryAfter * 1000; \/\/ \u8f6c\u6362\u4e3a\u6beb\u79d2<\/p>\n<p>    if (this.config.adaptiveBackoff) {<br \/>\n      \/\/ \u81ea\u9002\u5e94\u8c03\u6574\u7b49\u5f85\u65f6\u95f4<br \/>\n      waitTime &#061; this.calculateAdaptiveWaitTime(endpoint, waitTime);<br \/>\n    }<\/p>\n<p>    \/\/ \u6dfb\u52a0\u6296\u52a8<br \/>\n    waitTime &#061; this.addJitter(waitTime);<\/p>\n<p>    \/\/ \u8bb0\u5f55\u7b49\u5f85<br \/>\n    this.logRateLimitWait(endpoint, waitTime, &#039;quota_exhausted&#039;);<\/p>\n<p>    \/\/ \u7b49\u5f85\u540e\u91cd\u8bd5<br \/>\n    await this.sleep(waitTime);<\/p>\n<p>    \/\/ \u91cd\u8bd5\u8bf7\u6c42<br \/>\n    return this.request(endpoint, {<br \/>\n      &#8230;options,<br \/>\n      retryCount: (options.retryCount || 0) &#043; 1<br \/>\n    });<br \/>\n  }<\/p>\n<p>  async handleRateLimitError(requestId, endpoint, options, error) {<br \/>\n    const retryCount &#061; options.retryCount || 0;<\/p>\n<p>    if (retryCount &gt;&#061; this.config.maxRetries) {<br \/>\n      throw new MaxRetriesError(&#039;Maximum retries exceeded&#039;, error);<br \/>\n    }<\/p>\n<p>    \/\/ \u4ece\u54cd\u5e94\u4e2d\u63d0\u53d6Retry-After<br \/>\n    let retryAfter &#061; 60; \/\/ \u9ed8\u8ba460\u79d2<\/p>\n<p>    if (error.response) {<br \/>\n      const retryAfterHeader &#061; error.response.headers.get(&#039;Retry-After&#039;);<br \/>\n      if (retryAfterHeader) {<br \/>\n        retryAfter &#061; parseInt(retryAfterHeader, 10);<br \/>\n      }<br \/>\n    }<\/p>\n<p>    \/\/ \u8ba1\u7b97\u9000\u907f\u5ef6\u8fdf<br \/>\n    const delay &#061; this.calculateBackoffDelay(retryCount, retryAfter);<\/p>\n<p>    \/\/ \u8bb0\u5f55\u91cd\u8bd5<br \/>\n    this.logRetry(endpoint, retryCount, delay, &#039;rate_limit&#039;);<\/p>\n<p>    \/\/ \u7b49\u5f85\u540e\u91cd\u8bd5<br \/>\n    await this.sleep(delay);<\/p>\n<p>    return this.request(endpoint, {<br \/>\n      &#8230;options,<br \/>\n      retryCount: retryCount &#043; 1<br \/>\n    });<br \/>\n  }<\/p>\n<p>  calculateBackoffDelay(retryCount, retryAfter &#061; 0) {<br \/>\n    \/\/ \u6307\u6570\u9000\u907f<br \/>\n    let delay &#061; this.config.baseDelay * Math.pow(this.config.backoffFactor, retryCount);<\/p>\n<p>    \/\/ \u8003\u8651Retry-After\u5934\u90e8<br \/>\n    if (retryAfter &gt; 0) {<br \/>\n      delay &#061; Math.max(delay, retryAfter * 1000);<br \/>\n    }<\/p>\n<p>    \/\/ \u9650\u5236\u6700\u5927\u5ef6\u8fdf<br \/>\n    delay &#061; Math.min(delay, this.config.maxDelay);<\/p>\n<p>    \/\/ \u6dfb\u52a0\u6296\u52a8<br \/>\n    delay &#061; this.addJitter(delay);<\/p>\n<p>    return delay;<br \/>\n  }<\/p>\n<p>  calculateAdaptiveWaitTime(endpoint, baseWaitTime) {<br \/>\n    const endpointKey &#061; this.getEndpointKey(endpoint);<br \/>\n    const history &#061; this.getQuotaHistory(endpointKey);<\/p>\n<p>    if (history.length &lt; 5) {<br \/>\n      return baseWaitTime;<br \/>\n    }<\/p>\n<p>    \/\/ \u5206\u6790\u5386\u53f2\u914d\u989d\u4f7f\u7528\u6a21\u5f0f<br \/>\n    const recentUsage &#061; history.slice(-10);<br \/>\n    const avgUsageRate &#061; this.calculateUsageRate(recentUsage);<\/p>\n<p>    \/\/ \u6839\u636e\u4f7f\u7528\u7387\u8c03\u6574\u7b49\u5f85\u65f6\u95f4<br \/>\n    if (avgUsageRate &gt; 0.8) {<br \/>\n      \/\/ \u9ad8\u4f7f\u7528\u7387&#xff0c;\u589e\u52a0\u7b49\u5f85\u65f6\u95f4<br \/>\n      return baseWaitTime * 1.5;<br \/>\n    } else if (avgUsageRate &lt; 0.3) {<br \/>\n      \/\/ \u4f4e\u4f7f\u7528\u7387&#xff0c;\u51cf\u5c11\u7b49\u5f85\u65f6\u95f4<br \/>\n      return baseWaitTime * 0.7;<br \/>\n    }<\/p>\n<p>    return baseWaitTime;<br \/>\n  }<\/p>\n<p>  calculateUsageRate(quotaHistory) {<br \/>\n    if (quotaHistory.length &lt; 2) {<br \/>\n      return 0;<br \/>\n    }<\/p>\n<p>    let totalUsage &#061; 0;<br \/>\n    let totalTime &#061; 0;<\/p>\n<p>    for (let i &#061; 1; i &lt; quotaHistory.length; i&#043;&#043;) {<br \/>\n      const prev &#061; quotaHistory[i &#8211; 1];<br \/>\n      const curr &#061; quotaHistory[i];<\/p>\n<p>      const usage &#061; prev.remaining &#8211; curr.remaining;<br \/>\n      const timeDiff &#061; curr.timestamp &#8211; prev.timestamp;<\/p>\n<p>      if (timeDiff &gt; 0 &amp;&amp; prev.limit &gt; 0) {<br \/>\n        totalUsage &#043;&#061; usage;<br \/>\n        totalTime &#043;&#061; timeDiff;<br \/>\n      }<br \/>\n    }<\/p>\n<p>    if (totalTime &#061;&#061;&#061; 0) {<br \/>\n      return 0;<br \/>\n    }<\/p>\n<p>    const avgUsagePerMs &#061; totalUsage \/ totalTime;<br \/>\n    const limitPerMs &#061; quotaHistory[0].limit \/ (24 * 60 * 60 * 1000); \/\/ \u6bcf\u5929\u7684\u9650\u5236<\/p>\n<p>    return avgUsagePerMs \/ limitPerMs;<br \/>\n  }<\/p>\n<p>  addJitter(delay) {<br \/>\n    const jitter &#061; this.config.jitter;<br \/>\n    const jitterAmount &#061; delay * jitter;<br \/>\n    const randomJitter &#061; (Math.random() * 2 &#8211; 1) * jitterAmount;<\/p>\n<p>    return Math.max(0, delay &#043; randomJitter);<br \/>\n  }<\/p>\n<p>  \/\/ \u7535\u8def\u65ad\u8def\u5668<br \/>\n  isCircuitClosed(circuitKey) {<br \/>\n    if (!this.circuitBreakers.has(circuitKey)) {<br \/>\n      return true;<br \/>\n    }<\/p>\n<p>    const circuit &#061; this.circuitBreakers.get(circuitKey);<\/p>\n<p>    if (circuit.state &#061;&#061;&#061; &#039;open&#039;) {<br \/>\n      \/\/ \u68c0\u67e5\u662f\u5426\u5e94\u8be5\u8fdb\u5165\u534a\u5f00\u72b6\u6001<br \/>\n      if (Date.now() &#8211; circuit.openedAt &gt; 60000) { \/\/ 60\u79d2\u540e<br \/>\n        circuit.state &#061; &#039;half_open&#039;;<br \/>\n        circuit.halfOpenAttempts &#061; 0;<br \/>\n        return true;<br \/>\n      }<br \/>\n      return false;<br \/>\n    }<\/p>\n<p>    if (circuit.state &#061;&#061;&#061; &#039;half_open&#039;) {<br \/>\n      if (circuit.halfOpenAttempts &gt;&#061; 3) {<br \/>\n        return false;<br \/>\n      }<br \/>\n      circuit.halfOpenAttempts&#043;&#043;;<br \/>\n      return true;<br \/>\n    }<\/p>\n<p>    return true; \/\/ closed<br \/>\n  }<\/p>\n<p>  updateCircuitBreaker(circuitKey, success) {<br \/>\n    if (!this.circuitBreakers.has(circuitKey)) {<br \/>\n      this.circuitBreakers.set(circuitKey, {<br \/>\n        state: &#039;closed&#039;,<br \/>\n        failureCount: 0,<br \/>\n        successCount: 0<br \/>\n      });<br \/>\n    }<\/p>\n<p>    const circuit &#061; this.circuitBreakers.get(circuitKey);<\/p>\n<p>    if (success) {<br \/>\n      circuit.successCount&#043;&#043;;<br \/>\n      circuit.failureCount &#061; Math.max(0, circuit.failureCount &#8211; 1);<\/p>\n<p>      if (circuit.state &#061;&#061;&#061; &#039;half_open&#039; &amp;&amp; circuit.successCount &gt;&#061; 3) {<br \/>\n        circuit.state &#061; &#039;closed&#039;;<br \/>\n        circuit.failureCount &#061; 0;<br \/>\n        circuit.successCount &#061; 0;<br \/>\n      }<br \/>\n    } else {<br \/>\n      circuit.failureCount&#043;&#043;;<\/p>\n<p>      if (circuit.failureCount &gt;&#061; 5 &amp;&amp; circuit.state &#061;&#061;&#061; &#039;closed&#039;) {<br \/>\n        circuit.state &#061; &#039;open&#039;;<br \/>\n        circuit.openedAt &#061; Date.now();<br \/>\n      }<br \/>\n    }<br \/>\n  }<\/p>\n<p>  \/\/ \u914d\u989d\u76d1\u63a7<br \/>\n  initQuotaMonitoring() {<br \/>\n    if (this.config.quotaMonitoring) {<br \/>\n      \/\/ \u5b9a\u671f\u5237\u65b0\u914d\u989d\u4fe1\u606f<br \/>\n      setInterval(() &#061;&gt; this.refreshQuotaInfo(), 30000);<\/p>\n<p>      \/\/ \u76d1\u63a7\u914d\u989d\u4f7f\u7528\u7387<br \/>\n      setInterval(() &#061;&gt; this.monitorQuotaUsage(), 60000);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  refreshQuotaInfo() {<br \/>\n    \/\/ \u6e05\u7406\u8fc7\u671f\u7684\u914d\u989d\u4fe1\u606f<br \/>\n    const now &#061; Date.now();<\/p>\n<p>    for (const [key, info] of this.rateLimitInfo.entries()) {<br \/>\n      const resetTime &#061; info.resetTime * 1000;<\/p>\n<p>      if (now &gt; resetTime &#043; 60000) { \/\/ \u91cd\u7f6e\u65f6\u95f4\u540e1\u5206\u949f<br \/>\n        this.rateLimitInfo.delete(key);<br \/>\n      }<br \/>\n    }<br \/>\n  }<\/p>\n<p>  monitorQuotaUsage() {<br \/>\n    const warnings &#061; [];<\/p>\n<p>    for (const [endpointKey, info] of this.rateLimitInfo.entries()) {<br \/>\n      const usageRate &#061; info.remaining \/ info.limit;<\/p>\n<p>      if (usageRate &lt; 0.1) { \/\/ \u5269\u4f59\u4e0d\u8db310%<br \/>\n        warnings.push({<br \/>\n          endpointKey,<br \/>\n          remaining: info.remaining,<br \/>\n          limit: info.limit,<br \/>\n          resetTime: new Date(info.resetTime * 1000).toISOString()<br \/>\n        });<br \/>\n      }<br \/>\n    }<\/p>\n<p>    if (warnings.length &gt; 0) {<br \/>\n      this.emitQuotaWarning(warnings);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  recordQuotaHistory(endpointKey, quota) {<br \/>\n    this.quotaHistory.push({<br \/>\n      endpointKey,<br \/>\n      &#8230;quota<br \/>\n    });<\/p>\n<p>    \/\/ \u4fdd\u6301\u5386\u53f2\u8bb0\u5f55\u5927\u5c0f<br \/>\n    if (this.quotaHistory.length &gt; 1000) {<br \/>\n      this.quotaHistory &#061; this.quotaHistory.slice(-500);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  getQuotaHistory(endpointKey, limit &#061; 50) {<br \/>\n    return this.quotaHistory<br \/>\n      .filter(q &#061;&gt; q.endpointKey &#061;&#061;&#061; endpointKey)<br \/>\n      .slice(-limit);<br \/>\n  }<\/p>\n<p>  \/\/ \u5de5\u5177\u65b9\u6cd5<br \/>\n  generateRequestId() {<br \/>\n    return &#096;req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}&#096;;<br \/>\n  }<\/p>\n<p>  getEndpointKey(endpoint, options &#061; {}) {<br \/>\n    const url &#061; new URL(endpoint, window.location.origin);<br \/>\n    const method &#061; options.method || &#039;GET&#039;;<\/p>\n<p>    return &#096;${method}:${url.pathname}&#096;;<br \/>\n  }<\/p>\n<p>  getCircuitKey(endpoint, options &#061; {}) {<br \/>\n    const endpointKey &#061; this.getEndpointKey(endpoint, options);<br \/>\n    return &#096;circuit:${endpointKey}&#096;;<br \/>\n  }<\/p>\n<p>  sleep(ms) {<br \/>\n    return new Promise(resolve &#061;&gt; setTimeout(resolve, ms));<br \/>\n  }<\/p>\n<p>  logRateLimitWait(endpoint, waitTime, reason) {<br \/>\n    console.log(&#096;Rate limit wait for ${endpoint}: ${waitTime}ms (${reason})&#096;);<br \/>\n  }<\/p>\n<p>  logRetry(endpoint, retryCount, delay, reason) {<br \/>\n    console.log(&#096;Retry ${retryCount &#043; 1} for ${endpoint} in ${delay}ms (${reason})&#096;);<br \/>\n  }<\/p>\n<p>  emitQuotaWarning(warnings) {<br \/>\n    \/\/ \u5728\u5b9e\u9645\u5e94\u7528\u4e2d&#xff0c;\u8fd9\u91cc\u4f1a\u89e6\u53d1\u4e8b\u4ef6\u6216\u663e\u793a\u901a\u77e5<br \/>\n    console.warn(&#039;Quota warnings:&#039;, warnings);<br \/>\n  }<\/p>\n<p>  getRateLimitInfo() {<br \/>\n    return Array.from(this.rateLimitInfo.entries()).map(([key, info]) &#061;&gt; ({<br \/>\n      endpoint: key,<br \/>\n      &#8230;info,<br \/>\n      lastUpdated: new Date(info.lastUpdated).toISOString()<br \/>\n    }));<br \/>\n  }<br \/>\n}<\/p>\n<p>class RateLimitError extends Error {<br \/>\n  constructor(message, response) {<br \/>\n    super(message);<br \/>\n    this.name &#061; &#039;RateLimitError&#039;;<br \/>\n    this.response &#061; response;<br \/>\n  }<br \/>\n}<\/p>\n<p>class MaxRetriesError extends Error {<br \/>\n  constructor(message, originalError) {<br \/>\n    super(message);<br \/>\n    this.name &#061; &#039;MaxRetriesError&#039;;<br \/>\n    this.originalError &#061; originalError;<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\nconst client &#061; new RateLimitAwareClient({<br \/>\n  baseDelay: 1000,<br \/>\n  maxRetries: 3,<br \/>\n  adaptiveBackoff: true,<br \/>\n  quotaMonitoring: true<br \/>\n});<\/p>\n<p>\/\/ \u53d1\u8d77\u8bf7\u6c42<br \/>\nasync function fetchData() {<br \/>\n  try {<br \/>\n    const response &#061; await client.request(&#039;\/api\/data&#039;, {<br \/>\n      headers: {<br \/>\n        &#039;Authorization&#039;: &#096;Bearer ${token}&#096;<br \/>\n      }<br \/>\n    });<\/p>\n<p>    return await response.json();<\/p>\n<p>  } catch (error) {<br \/>\n    if (error.name &#061;&#061;&#061; &#039;RateLimitError&#039;) {<br \/>\n      \/\/ \u663e\u793a\u7528\u6237\u53cb\u597d\u7684\u9519\u8bef\u6d88\u606f<br \/>\n      showRateLimitError(error);<br \/>\n      return null;<br \/>\n    }<\/p>\n<p>    throw error;<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ \u663e\u793a\u901f\u7387\u9650\u5236\u9519\u8bef\u7684UI\u7ec4\u4ef6<br \/>\nfunction showRateLimitError(error) {<br \/>\n  const container &#061; document.getElementById(&#039;error-container&#039;);<\/p>\n<p>  let retryAfter &#061; 60;<br \/>\n  if (error.response) {<br \/>\n    const retryAfterHeader &#061; error.response.headers.get(&#039;Retry-After&#039;);<br \/>\n    if (retryAfterHeader) {<br \/>\n      retryAfter &#061; parseInt(retryAfterHeader, 10);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  const html &#061; &#096;<br \/>\n    &lt;div class&#061;&#034;rate-limit-error&#034;&gt;<br \/>\n      &lt;h3&gt;\u26a0\ufe0f \u8bf7\u6c42\u8fc7\u4e8e\u9891\u7e41&lt;\/h3&gt;<br \/>\n      &lt;p&gt;\u60a8\u53d1\u9001\u4e86\u592a\u591a\u8bf7\u6c42&#xff0c;\u8bf7\u7a0d\u540e\u518d\u8bd5\u3002&lt;\/p&gt;<\/p>\n<p>      &lt;div class&#061;&#034;retry-info&#034;&gt;<br \/>\n        &lt;p&gt;\u5efa\u8bae\u5728 &lt;strong&gt;${retryAfter}&lt;\/strong&gt; \u79d2\u540e\u91cd\u8bd5&lt;\/p&gt;<br \/>\n        &lt;div class&#061;&#034;countdown&#034; data-seconds&#061;&#034;${retryAfter}&#034;&gt;<br \/>\n          \u5012\u8ba1\u65f6: &lt;span class&#061;&#034;seconds&#034;&gt;${retryAfter}&lt;\/span&gt; \u79d2<br \/>\n        &lt;\/div&gt;<br \/>\n      &lt;\/div&gt;<\/p>\n<p>      &lt;div class&#061;&#034;actions&#034;&gt;<br \/>\n        &lt;button \u03bfnclick&#061;&#034;retryNow()&#034; class&#061;&#034;retry-btn&#034;&gt;<br \/>\n          \u7acb\u5373\u91cd\u8bd5<br \/>\n        &lt;\/button&gt;<br \/>\n        &lt;button \u03bfnclick&#061;&#034;dismissError()&#034; class&#061;&#034;dismiss-btn&#034;&gt;<br \/>\n          \u53d6\u6d88<br \/>\n        &lt;\/button&gt;<br \/>\n      &lt;\/div&gt;<\/p>\n<p>      &lt;div class&#061;&#034;tips&#034;&gt;<br \/>\n        &lt;h4&gt;\u5982\u4f55\u907f\u514d\u6b64\u95ee\u9898&#xff1a;&lt;\/h4&gt;<br \/>\n        &lt;ul&gt;<br \/>\n          &lt;li&gt;\u907f\u514d\u9891\u7e41\u5237\u65b0\u9875\u9762&lt;\/li&gt;<br \/>\n          &lt;li&gt;\u4f7f\u7528\u5206\u9875\u52a0\u8f7d\u66f4\u591a\u6570\u636e&lt;\/li&gt;<br \/>\n          &lt;li&gt;\u5408\u7406\u4f7f\u7528\u7f13\u5b58&lt;\/li&gt;<br \/>\n        &lt;\/ul&gt;<br \/>\n      &lt;\/div&gt;<br \/>\n    &lt;\/div&gt;<br \/>\n  &#096;;<\/p>\n<p>  container.innerHTML &#061; html;<\/p>\n<p>  \/\/ \u542f\u52a8\u5012\u8ba1\u65f6<br \/>\n  startCountdown(retryAfter);<br \/>\n}<\/p>\n<h4>22.4.2 \u914d\u989d\u4f7f\u7528\u53ef\u89c6\u5316\u4eea\u8868\u677f<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u901f\u7387\u9650\u5236\u914d\u989d\u53ef\u89c6\u5316\u4eea\u8868\u677f<br \/>\nimport React, { useState, useEffect, useCallback } from &#039;react&#039;;<br \/>\nimport {<br \/>\n  LineChart, Line, BarChart, Bar, PieChart, Pie, Cell,<br \/>\n  XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer,<br \/>\n  AreaChart, Area, RadarChart, Radar, PolarGrid, PolarAngleAxis, PolarRadiusAxis<br \/>\n} from &#039;recharts&#039;;<br \/>\nimport { AlertTriangle, Clock, Zap, Battery, RefreshCw, Shield } from &#039;lucide-react&#039;;<\/p>\n<p>function RateLimitDashboard() {<br \/>\n  const [timeRange, setTimeRange] &#061; useState(&#039;1h&#039;);<br \/>\n  const [quotaData, setQuotaData] &#061; useState([]);<br \/>\n  const [violationHistory, setViolationHistory] &#061; useState([]);<br \/>\n  const [endpointStats, setEndpointStats] &#061; useState({});<br \/>\n  const [realTimeUpdates, setRealTimeUpdates] &#061; useState([]);<\/p>\n<p>  \/\/ \u83b7\u53d6\u914d\u989d\u6570\u636e<br \/>\n  useEffect(() &#061;&gt; {<br \/>\n    const fetchQuotaData &#061; async () &#061;&gt; {<br \/>\n      try {<br \/>\n        const response &#061; await fetch(&#096;\/api\/rate-limit\/quotas?range&#061;${timeRange}&#096;);<br \/>\n        const data &#061; await response.json();<br \/>\n        setQuotaData(data.quotas || []);<br \/>\n        setViolationHistory(data.violations || []);<br \/>\n        setEndpointStats(data.endpointStats || {});<br \/>\n      } catch (error) {<br \/>\n        console.error(&#039;Failed to fetch quota data:&#039;, error);<br \/>\n      }<br \/>\n    };<\/p>\n<p>    fetchQuotaData();<br \/>\n    const interval &#061; setInterval(fetchQuotaData, 30000); \/\/ \u6bcf30\u79d2\u66f4\u65b0<\/p>\n<p>    return () &#061;&gt; clearInterval(interval);<br \/>\n  }, [timeRange]);<\/p>\n<p>  \/\/ WebSocket\u8fde\u63a5\u63a5\u6536\u5b9e\u65f6\u66f4\u65b0<br \/>\n  useEffect(() &#061;&gt; {<br \/>\n    const ws &#061; new WebSocket(&#039;wss:\/\/api.example.com\/rate-limit\/updates&#039;);<\/p>\n<p>    ws.onmessage &#061; (event) &#061;&gt; {<br \/>\n      const update &#061; JSON.parse(event.data);<br \/>\n      setRealTimeUpdates(prev &#061;&gt; [update, &#8230;prev.slice(0, 49)]);<br \/>\n    };<\/p>\n<p>    return () &#061;&gt; ws.close();<br \/>\n  }, []);<\/p>\n<p>  \/\/ \u5904\u7406\u91cd\u7f6e\u914d\u989d<br \/>\n  const handleResetQuota &#061; useCallback(async (endpoint, rule) &#061;&gt; {<br \/>\n    try {<br \/>\n      await fetch(&#039;\/api\/rate-limit\/reset&#039;, {<br \/>\n        method: &#039;POST&#039;,<br \/>\n        headers: { &#039;Content-Type&#039;: &#039;application\/json&#039; },<br \/>\n        body: JSON.stringify({ endpoint, rule })<br \/>\n      });<\/p>\n<p>      alert(&#039;\u914d\u989d\u5df2\u91cd\u7f6e&#039;);<br \/>\n    } catch (error) {<br \/>\n      alert(&#039;\u91cd\u7f6e\u5931\u8d25: &#039; &#043; error.message);<br \/>\n    }<br \/>\n  }, []);<\/p>\n<p>  \/\/ \u5904\u7406\u8c03\u6574\u9650\u5236<br \/>\n  const handleAdjustLimit &#061; useCallback(async (endpoint, rule, newLimit) &#061;&gt; {<br \/>\n    try {<br \/>\n      await fetch(&#039;\/api\/rate-limit\/adjust&#039;, {<br \/>\n        method: &#039;POST&#039;,<br \/>\n        headers: { &#039;Content-Type&#039;: &#039;application\/json&#039; },<br \/>\n        body: JSON.stringify({ endpoint, rule, newLimit })<br \/>\n      });<\/p>\n<p>      alert(&#039;\u9650\u5236\u5df2\u8c03\u6574&#039;);<br \/>\n    } catch (error) {<br \/>\n      alert(&#039;\u8c03\u6574\u5931\u8d25: &#039; &#043; error.message);<br \/>\n    }<br \/>\n  }, []);<\/p>\n<p>  \/\/ \u751f\u6210\u56fe\u8868\u6570\u636e<br \/>\n  const generateUsageChartData &#061; () &#061;&gt; {<br \/>\n    if (!quotaData.length) return [];<\/p>\n<p>    \/\/ \u6309\u65f6\u95f4\u5206\u7ec4<br \/>\n    const timeGroups &#061; {};<\/p>\n<p>    quotaData.forEach(quota &#061;&gt; {<br \/>\n      const date &#061; new Date(quota.timestamp);<br \/>\n      const hour &#061; &#096;${date.getHours()}:00&#096;;<\/p>\n<p>      if (!timeGroups[hour]) {<br \/>\n        timeGroups[hour] &#061; {<br \/>\n          time: hour,<br \/>\n          used: 0,<br \/>\n          total: 0,<br \/>\n          violations: 0<br \/>\n        };<br \/>\n      }<\/p>\n<p>      timeGroups[hour].used &#043;&#061; quota.used || 0;<br \/>\n      timeGroups[hour].total &#043;&#061; quota.limit || 0;<\/p>\n<p>      if (quota.remaining &#061;&#061;&#061; 0) {<br \/>\n        timeGroups[hour].violations&#043;&#043;;<br \/>\n      }<br \/>\n    });<\/p>\n<p>    return Object.values(timeGroups).sort((a, b) &#061;&gt; a.time.localeCompare(b.time));<br \/>\n  };<\/p>\n<p>  const generateEndpointDistributionData &#061; () &#061;&gt; {<br \/>\n    const endpointData &#061; [];<\/p>\n<p>    Object.entries(endpointStats).forEach(([endpoint, stats]) &#061;&gt; {<br \/>\n      endpointData.push({<br \/>\n        name: endpoint.length &gt; 20 ? endpoint.substring(0, 20) &#043; &#039;&#8230;&#039; : endpoint,<br \/>\n        violations: stats.violations || 0,<br \/>\n        requests: stats.requests || 0,<br \/>\n        usageRate: stats.usageRate || 0<br \/>\n      });<br \/>\n    });<\/p>\n<p>    return endpointData.sort((a, b) &#061;&gt; b.violations &#8211; a.violations).slice(0, 10);<br \/>\n  };<\/p>\n<p>  const generateRuleDistributionData &#061; () &#061;&gt; {<br \/>\n    const ruleData &#061; [];<br \/>\n    const ruleMap &#061; {};<\/p>\n<p>    quotaData.forEach(quota &#061;&gt; {<br \/>\n      const rule &#061; quota.rule || &#039;default&#039;;<\/p>\n<p>      if (!ruleMap[rule]) {<br \/>\n        ruleMap[rule] &#061; {<br \/>\n          name: rule,<br \/>\n          violations: 0,<br \/>\n          total: 0<br \/>\n        };<br \/>\n      }<\/p>\n<p>      ruleMap[rule].violations &#043;&#061; quota.remaining &#061;&#061;&#061; 0 ? 1 : 0;<br \/>\n      ruleMap[rule].total&#043;&#043;;<br \/>\n    });<\/p>\n<p>    Object.values(ruleMap).forEach(rule &#061;&gt; {<br \/>\n      ruleData.push({<br \/>\n        name: rule.name,<br \/>\n        value: rule.violations,<br \/>\n        percentage: rule.total &gt; 0 ? (rule.violations \/ rule.total * 100).toFixed(1) : 0<br \/>\n      });<br \/>\n    });<\/p>\n<p>    return ruleData.sort((a, b) &#061;&gt; b.value &#8211; a.value).slice(0, 8);<br \/>\n  };<\/p>\n<p>  \/\/ \u989c\u8272\u914d\u7f6e<br \/>\n  const COLORS &#061; [&#039;#0088FE&#039;, &#039;#00C49F&#039;, &#039;#FFBB28&#039;, &#039;#FF8042&#039;, &#039;#8884D8&#039;, &#039;#82CA9D&#039;, &#039;#FF6B6B&#039;, &#039;#FFD166&#039;];<\/p>\n<p>  return (<br \/>\n    &lt;div className&#061;&#034;dashboard rate-limit-dashboard&#034;&gt;<br \/>\n      {\/* \u4eea\u8868\u677f\u5934\u90e8 *\/}<br \/>\n      &lt;div className&#061;&#034;dashboard-header&#034;&gt;<br \/>\n        &lt;div className&#061;&#034;header-left&#034;&gt;<br \/>\n          &lt;Shield size&#061;{32} className&#061;&#034;header-icon&#034; \/&gt;<br \/>\n          &lt;h1&gt;\u901f\u7387\u9650\u5236\u76d1\u63a7&lt;\/h1&gt;<br \/>\n          &lt;span className&#061;&#034;time-range-label&#034;&gt;<br \/>\n            {timeRange &#061;&#061;&#061; &#039;1h&#039; ? &#039;1\u5c0f\u65f6&#039; :<br \/>\n             timeRange &#061;&#061;&#061; &#039;24h&#039; ? &#039;24\u5c0f\u65f6&#039; :<br \/>\n             timeRange &#061;&#061;&#061; &#039;7d&#039; ? &#039;7\u5929&#039; : &#039;30\u5929&#039;}<br \/>\n          &lt;\/span&gt;<br \/>\n        &lt;\/div&gt;<\/p>\n<p>        &lt;div className&#061;&#034;header-right&#034;&gt;<br \/>\n          &lt;div className&#061;&#034;time-range-selector&#034;&gt;<br \/>\n            {[&#039;1h&#039;, &#039;24h&#039;, &#039;7d&#039;, &#039;30d&#039;].map(range &#061;&gt; (<br \/>\n              &lt;button<br \/>\n                key&#061;{range}<br \/>\n                className&#061;{&#096;time-range-btn ${timeRange &#061;&#061;&#061; range ? &#039;active&#039; : &#039;&#039;}&#096;}<br \/>\n                onClick&#061;{() &#061;&gt; setTimeRange(range)}<br \/>\n              &gt;<br \/>\n                {range}<br \/>\n              &lt;\/button&gt;<br \/>\n            ))}<br \/>\n          &lt;\/div&gt;<\/p>\n<p>          &lt;button className&#061;&#034;refresh-btn&#034; onClick&#061;{() &#061;&gt; window.location.reload()}&gt;<br \/>\n            &lt;RefreshCw size&#061;{16} \/&gt; \u5237\u65b0<br \/>\n          &lt;\/button&gt;<br \/>\n        &lt;\/div&gt;<br \/>\n      &lt;\/div&gt;<\/p>\n<p>      {\/* \u5173\u952e\u6307\u6807\u5361\u7247 *\/}<br \/>\n      &lt;div className&#061;&#034;key-metrics-cards&#034;&gt;<br \/>\n        &lt;div className&#061;&#034;metric-card total-requests&#034;&gt;<br \/>\n          &lt;div className&#061;&#034;metric-icon&#034;&gt;<br \/>\n            &lt;Zap size&#061;{24} \/&gt;<br \/>\n          &lt;\/div&gt;<br \/>\n          &lt;div className&#061;&#034;metric-content&#034;&gt;<br \/>\n            &lt;h3&gt;\u603b\u8bf7\u6c42\u6570&lt;\/h3&gt;<br \/>\n            &lt;p className&#061;&#034;metric-value&#034;&gt;<br \/>\n              {endpointStats.totalRequests?.toLocaleString() || &#039;0&#039;}<br \/>\n            &lt;\/p&gt;<br \/>\n            &lt;p className&#061;&#034;metric-trend&#034;&gt;<br \/>\n              \u6210\u529f\u7387: {endpointStats.successRate ? (endpointStats.successRate * 100).toFixed(1) &#043; &#039;%&#039; : &#039;N\/A&#039;}<br \/>\n            &lt;\/p&gt;<br \/>\n          &lt;\/div&gt;<br \/>\n        &lt;\/div&gt;<\/p>\n<p>        &lt;div className&#061;&#034;metric-card violations&#034;&gt;<br \/>\n          &lt;div className&#061;&#034;metric-icon&#034;&gt;<br \/>\n            &lt;AlertTriangle size&#061;{24} \/&gt;<br \/>\n          &lt;\/div&gt;<br \/>\n          &lt;div className&#061;&#034;metric-content&#034;&gt;<br \/>\n            &lt;h3&gt;\u8fdd\u89c4\u6b21\u6570&lt;\/h3&gt;<br \/>\n            &lt;p className&#061;&#034;metric-value&#034;&gt;<br \/>\n              {violationHistory.length.toLocaleString()}<br \/>\n            &lt;\/p&gt;<br \/>\n            &lt;p className&#061;&#034;metric-trend&#034;&gt;<br \/>\n              \u8fdd\u89c4\u7387: {quotaData.length &gt; 0 ?<br \/>\n                ((violationHistory.length \/ quotaData.length) * 100).toFixed(1) &#043; &#039;%&#039; : &#039;0%&#039;}<br \/>\n            &lt;\/p&gt;<br \/>\n          &lt;\/div&gt;<br \/>\n        &lt;\/div&gt;<\/p>\n<p>        &lt;div className&#061;&#034;metric-card quota-usage&#034;&gt;<br \/>\n          &lt;div className&#061;&#034;metric-icon&#034;&gt;<br \/>\n            &lt;Battery size&#061;{24} \/&gt;<br \/>\n          &lt;\/div&gt;<br \/>\n          &lt;div className&#061;&#034;metric-content&#034;&gt;<br \/>\n            &lt;h3&gt;\u914d\u989d\u4f7f\u7528\u7387&lt;\/h3&gt;<br \/>\n            &lt;p className&#061;&#034;metric-value&#034;&gt;<br \/>\n              {endpointStats.avgUsageRate ?<br \/>\n                (endpointStats.avgUsageRate * 100).toFixed(1) &#043; &#039;%&#039; : &#039;0%&#039;}<br \/>\n            &lt;\/p&gt;<br \/>\n            &lt;p className&#061;&#034;metric-trend&#034;&gt;<br \/>\n              \u5cf0\u503c: {endpointStats.peakUsageRate ?<br \/>\n                (endpointStats.peakUsageRate * 100).toFixed(1) &#043; &#039;%&#039; : &#039;N\/A&#039;}<br \/>\n            &lt;\/p&gt;<br \/>\n          &lt;\/div&gt;<br \/>\n        &lt;\/div&gt;<\/p>\n<p>        &lt;div className&#061;&#034;metric-card avg-wait-time&#034;&gt;<br \/>\n          &lt;div className&#061;&#034;metric-icon&#034;&gt;<br \/>\n            &lt;Clock size&#061;{24} \/&gt;<br \/>\n          &lt;\/div&gt;<br \/>\n          &lt;div className&#061;&#034;metric-content&#034;&gt;<br \/>\n            &lt;h3&gt;\u5e73\u5747\u7b49\u5f85\u65f6\u95f4&lt;\/h3&gt;<br \/>\n            &lt;p className&#061;&#034;metric-value&#034;&gt;<br \/>\n              {endpointStats.avgWaitTime ?<br \/>\n                endpointStats.avgWaitTime.toFixed(1) &#043; &#039;s&#039; : &#039;0s&#039;}<br \/>\n            &lt;\/p&gt;<br \/>\n            &lt;p className&#061;&#034;metric-trend&#034;&gt;<br \/>\n              \u6700\u957f: {endpointStats.maxWaitTime ?<br \/>\n                endpointStats.maxWaitTime.toFixed(1) &#043; &#039;s&#039; : &#039;N\/A&#039;}<br \/>\n            &lt;\/p&gt;<br \/>\n          &lt;\/div&gt;<br \/>\n        &lt;\/div&gt;<br \/>\n      &lt;\/div&gt;<\/p>\n<p>      {\/* \u56fe\u8868\u533a\u57df *\/}<br \/>\n      &lt;div className&#061;&#034;charts-section&#034;&gt;<br \/>\n        &lt;div className&#061;&#034;chart-row&#034;&gt;<br \/>\n          &lt;div className&#061;&#034;chart-container large&#034;&gt;<br \/>\n            &lt;h3&gt;\u914d\u989d\u4f7f\u7528\u8d8b\u52bf&lt;\/h3&gt;<br \/>\n            &lt;ResponsiveContainer width&#061;&#034;100%&#034; height&#061;{300}&gt;<br \/>\n              &lt;AreaChart data&#061;{generateUsageChartData()}&gt;<br \/>\n                &lt;CartesianGrid strokeDasharray&#061;&#034;3 3&#034; \/&gt;<br \/>\n                &lt;XAxis dataKey&#061;&#034;time&#034; \/&gt;<br \/>\n                &lt;YAxis \/&gt;<br \/>\n                &lt;Tooltip \/&gt;<br \/>\n                &lt;Legend \/&gt;<br \/>\n                &lt;Area<br \/>\n                  type&#061;&#034;monotone&#034;<br \/>\n                  dataKey&#061;&#034;used&#034;<br \/>\n                  stackId&#061;&#034;1&#034;<br \/>\n                  stroke&#061;&#034;#8884d8&#034;<br \/>\n                  fill&#061;&#034;#8884d8&#034;<br \/>\n                  fillOpacity&#061;{0.3}<br \/>\n                  name&#061;&#034;\u5df2\u4f7f\u7528&#034;<br \/>\n                \/&gt;<br \/>\n                &lt;Area<br \/>\n                  type&#061;&#034;monotone&#034;<br \/>\n                  dataKey&#061;&#034;total&#034;<br \/>\n                  stackId&#061;&#034;1&#034;<br \/>\n                  stroke&#061;&#034;#82ca9d&#034;<br \/>\n                  fill&#061;&#034;#82ca9d&#034;<br \/>\n                  fillOpacity&#061;{0.3}<br \/>\n                  name&#061;&#034;\u603b\u914d\u989d&#034;<br \/>\n                \/&gt;<br \/>\n                &lt;Line<br \/>\n                  type&#061;&#034;monotone&#034;<br \/>\n                  dataKey&#061;&#034;violations&#034;<br \/>\n                  stroke&#061;&#034;#ff6b6b&#034;<br \/>\n                  strokeWidth&#061;{2}<br \/>\n                  dot&#061;{{ r: 4 }}<br \/>\n                  name&#061;&#034;\u8fdd\u89c4\u6b21\u6570&#034;<br \/>\n                \/&gt;<br \/>\n              &lt;\/AreaChart&gt;<br \/>\n            &lt;\/ResponsiveContainer&gt;<br \/>\n          &lt;\/div&gt;<\/p>\n<p>          &lt;div className&#061;&#034;chart-container&#034;&gt;<br \/>\n            &lt;h3&gt;\u89c4\u5219\u8fdd\u89c4\u5206\u5e03&lt;\/h3&gt;<br \/>\n            &lt;ResponsiveContainer width&#061;&#034;100%&#034; height&#061;{300}&gt;<br \/>\n              &lt;PieChart&gt;<br \/>\n                &lt;Pie<br \/>\n                  data&#061;{generateRuleDistributionData()}<br \/>\n                  cx&#061;&#034;50%&#034;<br \/>\n                  cy&#061;&#034;50%&#034;<br \/>\n                  labelLine&#061;{false}<br \/>\n                  label&#061;{({ name, percentage }) &#061;&gt; &#096;${name}: ${percentage}%&#096;}<br \/>\n                  outerRadius&#061;{80}<br \/>\n                  fill&#061;&#034;#8884d8&#034;<br \/>\n                  dataKey&#061;&#034;value&#034;<br \/>\n                &gt;<br \/>\n                  {generateRuleDistributionData().map((entry, index) &#061;&gt; (<br \/>\n                    &lt;Cell key&#061;{&#096;cell-${index}&#096;} fill&#061;{COLORS[index % COLORS.length]} \/&gt;<br \/>\n                  ))}<br \/>\n                &lt;\/Pie&gt;<br \/>\n                &lt;Tooltip formatter&#061;{(value, name, props) &#061;&gt; [<br \/>\n                  &#096;${value}\u6b21 (${props.payload.percentage}%)&#096;,<br \/>\n                  &#039;\u8fdd\u89c4\u6b21\u6570&#039;<br \/>\n                ]} \/&gt;<br \/>\n              &lt;\/PieChart&gt;<br \/>\n            &lt;\/ResponsiveContainer&gt;<br \/>\n          &lt;\/div&gt;<br \/>\n        &lt;\/div&gt;<\/p>\n<p>        &lt;div className&#061;&#034;chart-row&#034;&gt;<br \/>\n          &lt;div className&#061;&#034;chart-container large&#034;&gt;<br \/>\n            &lt;h3&gt;\u7aef\u70b9\u8fdd\u89c4\u6392\u540d&lt;\/h3&gt;<br \/>\n            &lt;ResponsiveContainer width&#061;&#034;100%&#034; height&#061;{300}&gt;<br \/>\n              &lt;BarChart data&#061;{generateEndpointDistributionData()}&gt;<br \/>\n                &lt;CartesianGrid strokeDasharray&#061;&#034;3 3&#034; \/&gt;<br \/>\n                &lt;XAxis dataKey&#061;&#034;name&#034; angle&#061;{-45} textAnchor&#061;&#034;end&#034; height&#061;{80} \/&gt;<br \/>\n                &lt;YAxis \/&gt;<br \/>\n                &lt;Tooltip \/&gt;<br \/>\n                &lt;Legend \/&gt;<br \/>\n                &lt;Bar dataKey&#061;&#034;violations&#034; fill&#061;&#034;#ff6b6b&#034; name&#061;&#034;\u8fdd\u89c4\u6b21\u6570&#034; \/&gt;<br \/>\n                &lt;Bar dataKey&#061;&#034;requests&#034; fill&#061;&#034;#8884d8&#034; name&#061;&#034;\u603b\u8bf7\u6c42\u6570&#034; \/&gt;<br \/>\n              &lt;\/BarChart&gt;<br \/>\n            &lt;\/ResponsiveContainer&gt;<br \/>\n          &lt;\/div&gt;<\/p>\n<p>          &lt;div className&#061;&#034;chart-container&#034;&gt;<br \/>\n            &lt;h3&gt;\u914d\u989d\u4f7f\u7528\u7387\u96f7\u8fbe\u56fe&lt;\/h3&gt;<br \/>\n            &lt;ResponsiveContainer width&#061;&#034;100%&#034; height&#061;{300}&gt;<br \/>\n              &lt;RadarChart data&#061;{generateRadarData()}&gt;<br \/>\n                &lt;PolarGrid \/&gt;<br \/>\n                &lt;PolarAngleAxis dataKey&#061;&#034;subject&#034; \/&gt;<br \/>\n                &lt;PolarRadiusAxis angle&#061;{30} domain&#061;{[0, 100]} \/&gt;<br \/>\n                &lt;Radar<br \/>\n                  name&#061;&#034;\u4f7f\u7528\u7387&#034;<br \/>\n                  dataKey&#061;&#034;usage&#034;<br \/>\n                  stroke&#061;&#034;#8884d8&#034;<br \/>\n                  fill&#061;&#034;#8884d8&#034;<br \/>\n                  fillOpacity&#061;{0.3}<br \/>\n                \/&gt;<br \/>\n                &lt;Radar<br \/>\n                  name&#061;&#034;\u76ee\u6807&#034;<br \/>\n                  dataKey&#061;&#034;target&#034;<br \/>\n                  stroke&#061;&#034;#82ca9d&#034;<br \/>\n                  fill&#061;&#034;#82ca9d&#034;<br \/>\n                  fillOpacity&#061;{0.3}<br \/>\n                \/&gt;<br \/>\n                &lt;Legend \/&gt;<br \/>\n                &lt;Tooltip \/&gt;<br \/>\n              &lt;\/RadarChart&gt;<br \/>\n            &lt;\/ResponsiveContainer&gt;<br \/>\n          &lt;\/div&gt;<br \/>\n        &lt;\/div&gt;<br \/>\n      &lt;\/div&gt;<\/p>\n<p>      {\/* \u5b9e\u65f6\u8fdd\u89c4\u4e8b\u4ef6 *\/}<br \/>\n      &lt;div className&#061;&#034;realtime-events-section&#034;&gt;<br \/>\n        &lt;h3&gt;\u5b9e\u65f6\u8fdd\u89c4\u4e8b\u4ef6&lt;\/h3&gt;<br \/>\n        &lt;div className&#061;&#034;events-table-container&#034;&gt;<br \/>\n          &lt;table className&#061;&#034;events-table&#034;&gt;<br \/>\n            &lt;thead&gt;<br \/>\n              &lt;tr&gt;<br \/>\n                &lt;th&gt;\u65f6\u95f4&lt;\/th&gt;<br \/>\n                &lt;th&gt;\u7aef\u70b9&lt;\/th&gt;<br \/>\n                &lt;th&gt;\u89c4\u5219&lt;\/th&gt;<br \/>\n                &lt;th&gt;\u5ba2\u6237\u7aefIP&lt;\/th&gt;<br \/>\n                &lt;th&gt;\u5269\u4f59\u914d\u989d&lt;\/th&gt;<br \/>\n                &lt;th&gt;\u91cd\u8bd5\u7b49\u5f85&lt;\/th&gt;<br \/>\n                &lt;th&gt;\u64cd\u4f5c&lt;\/th&gt;<br \/>\n              &lt;\/tr&gt;<br \/>\n            &lt;\/thead&gt;<br \/>\n            &lt;tbody&gt;<br \/>\n              {realTimeUpdates.map((event, index) &#061;&gt; (<br \/>\n                &lt;tr key&#061;{index} className&#061;&#034;event-row&#034;&gt;<br \/>\n                  &lt;td&gt;<br \/>\n                    {new Date(event.timestamp).toLocaleTimeString()}<br \/>\n                  &lt;\/td&gt;<br \/>\n                  &lt;td className&#061;&#034;endpoint-cell&#034;&gt;<br \/>\n                    &lt;code title&#061;{event.endpoint}&gt;<br \/>\n                      {event.endpoint?.length &gt; 30 ?<br \/>\n                        event.endpoint.substring(0, 30) &#043; &#039;&#8230;&#039; :<br \/>\n                        event.endpoint}<br \/>\n                    &lt;\/code&gt;<br \/>\n                  &lt;\/td&gt;<br \/>\n                  &lt;td&gt;<br \/>\n                    &lt;span className&#061;&#034;rule-badge&#034;&gt;<br \/>\n                      {event.rule || &#039;default&#039;}<br \/>\n                    &lt;\/span&gt;<br \/>\n                  &lt;\/td&gt;<br \/>\n                  &lt;td&gt;<br \/>\n                    &lt;code&gt;{event.client_ip}&lt;\/code&gt;<br \/>\n                  &lt;\/td&gt;<br \/>\n                  &lt;td&gt;<br \/>\n                    &lt;span className&#061;{&#096;quota-badge ${event.remaining &#061;&#061;&#061; 0 ? &#039;exhausted&#039; : &#039;available&#039;}&#096;}&gt;<br \/>\n                      {event.remaining}\/{event.limit}<br \/>\n                    &lt;\/span&gt;<br \/>\n                  &lt;\/td&gt;<br \/>\n                  &lt;td&gt;<br \/>\n                    &lt;span className&#061;&#034;wait-time&#034;&gt;<br \/>\n                      {event.retry_after || 0}s<br \/>\n                    &lt;\/span&gt;<br \/>\n                  &lt;\/td&gt;<br \/>\n                  &lt;td&gt;<br \/>\n                    &lt;button<br \/>\n                      className&#061;&#034;action-btn view-details&#034;<br \/>\n                      onClick&#061;{() &#061;&gt; viewEventDetails(event)}<br \/>\n                    &gt;<br \/>\n                      \u8be6\u60c5<br \/>\n                    &lt;\/button&gt;<br \/>\n                    &lt;button<br \/>\n                      className&#061;&#034;action-btn reset-quota&#034;<br \/>\n                      onClick&#061;{() &#061;&gt; handleResetQuota(event.endpoint, event.rule)}<br \/>\n                    &gt;<br \/>\n                      \u91cd\u7f6e\u914d\u989d<br \/>\n                    &lt;\/button&gt;<br \/>\n                  &lt;\/td&gt;<br \/>\n                &lt;\/tr&gt;<br \/>\n              ))}<br \/>\n            &lt;\/tbody&gt;<br \/>\n          &lt;\/table&gt;<br \/>\n        &lt;\/div&gt;<br \/>\n      &lt;\/div&gt;<\/p>\n<p>      {\/* \u914d\u989d\u7ba1\u7406 *\/}<br \/>\n      &lt;div className&#061;&#034;quota-management-section&#034;&gt;<br \/>\n        &lt;h3&gt;\u914d\u989d\u7ba1\u7406&lt;\/h3&gt;<br \/>\n        &lt;div className&#061;&#034;quota-list&#034;&gt;<br \/>\n          {quotaData.slice(0, 10).map((quota, index) &#061;&gt; (<br \/>\n            &lt;div key&#061;{index} className&#061;&#034;quota-item&#034;&gt;<br \/>\n              &lt;div className&#061;&#034;quota-header&#034;&gt;<br \/>\n                &lt;span className&#061;&#034;quota-endpoint&#034;&gt;{quota.endpoint}&lt;\/span&gt;<br \/>\n                &lt;span className&#061;&#034;quota-rule&#034;&gt;{quota.rule}&lt;\/span&gt;<br \/>\n              &lt;\/div&gt;<\/p>\n<p>              &lt;div className&#061;&#034;quota-progress&#034;&gt;<br \/>\n                &lt;div className&#061;&#034;progress-bar&#034;&gt;<br \/>\n                  &lt;div<br \/>\n                    className&#061;&#034;progress-fill&#034;<br \/>\n                    style&#061;{{ width: &#096;${(quota.used \/ quota.limit) * 100}%&#096; }}<br \/>\n                  &gt;&lt;\/div&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n                &lt;div className&#061;&#034;quota-numbers&#034;&gt;<br \/>\n                  &lt;span&gt;{quota.used} \/ {quota.limit}&lt;\/span&gt;<br \/>\n                  &lt;span&gt;{((quota.used \/ quota.limit) * 100).toFixed(1)}%&lt;\/span&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n              &lt;\/div&gt;<\/p>\n<p>              &lt;div className&#061;&#034;quota-actions&#034;&gt;<br \/>\n                &lt;button<br \/>\n                  className&#061;&#034;btn btn-sm btn-primary&#034;<br \/>\n                  onClick&#061;{() &#061;&gt; handleAdjustLimit(quota.endpoint, quota.rule, quota.limit * 1.1)}<br \/>\n                &gt;<br \/>\n                  \u589e\u52a010%<br \/>\n                &lt;\/button&gt;<br \/>\n                &lt;button<br \/>\n                  className&#061;&#034;btn btn-sm btn-secondary&#034;<br \/>\n                  onClick&#061;{() &#061;&gt; handleAdjustLimit(quota.endpoint, quota.rule, quota.limit * 0.9)}<br \/>\n                &gt;<br \/>\n                  \u51cf\u5c1110%<br \/>\n                &lt;\/button&gt;<br \/>\n                &lt;button<br \/>\n                  className&#061;&#034;btn btn-sm btn-tertiary&#034;<br \/>\n                  onClick&#061;{() &#061;&gt; handleResetQuota(quota.endpoint, quota.rule)}<br \/>\n                &gt;<br \/>\n                  \u7acb\u5373\u91cd\u7f6e<br \/>\n                &lt;\/button&gt;<br \/>\n              &lt;\/div&gt;<br \/>\n            &lt;\/div&gt;<br \/>\n          ))}<br \/>\n        &lt;\/div&gt;<br \/>\n      &lt;\/div&gt;<\/p>\n<p>      {\/* \u914d\u7f6e\u5efa\u8bae *\/}<br \/>\n      &lt;div className&#061;&#034;recommendations-section&#034;&gt;<br \/>\n        &lt;h3&gt;\u914d\u7f6e\u5efa\u8bae&lt;\/h3&gt;<br \/>\n        &lt;div className&#061;&#034;recommendations-list&#034;&gt;<br \/>\n          {generateRecommendations().map((rec, index) &#061;&gt; (<br \/>\n            &lt;div key&#061;{index} className&#061;&#034;recommendation-card&#034;&gt;<br \/>\n              &lt;div className&#061;&#034;recommendation-icon&#034;&gt;&#x1f4a1;&lt;\/div&gt;<br \/>\n              &lt;div className&#061;&#034;recommendation-content&#034;&gt;<br \/>\n                &lt;h4&gt;{rec.title}&lt;\/h4&gt;<br \/>\n                &lt;p&gt;{rec.description}&lt;\/p&gt;<br \/>\n                &lt;div className&#061;&#034;recommendation-actions&#034;&gt;<br \/>\n                  &lt;button<br \/>\n                    className&#061;&#034;btn btn-sm btn-primary&#034;<br \/>\n                    onClick&#061;{() &#061;&gt; applyRecommendation(rec)}<br \/>\n                  &gt;<br \/>\n                    \u5e94\u7528\u5efa\u8bae<br \/>\n                  &lt;\/button&gt;<br \/>\n                  &lt;button<br \/>\n                    className&#061;&#034;btn btn-sm btn-secondary&#034;<br \/>\n                    onClick&#061;{() &#061;&gt; dismissRecommendation(rec.id)}<br \/>\n                  &gt;<br \/>\n                    \u5ffd\u7565<br \/>\n                  &lt;\/button&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n              &lt;\/div&gt;<br \/>\n            &lt;\/div&gt;<br \/>\n          ))}<br \/>\n        &lt;\/div&gt;<br \/>\n      &lt;\/div&gt;<br \/>\n    &lt;\/div&gt;<br \/>\n  );<\/p>\n<p>  \/\/ \u8f85\u52a9\u51fd\u6570<br \/>\n  function generateRadarData() {<br \/>\n    return [<br \/>\n      { subject: &#039;\u8ba4\u8bc1\u7aef\u70b9&#039;, usage: 85, target: 70 },<br \/>\n      { subject: &#039;\u6570\u636e\u67e5\u8be2&#039;, usage: 60, target: 80 },<br \/>\n      { subject: &#039;\u6587\u4ef6\u4e0a\u4f20&#039;, usage: 95, target: 50 },<br \/>\n      { subject: &#039;\u7ba1\u7406\u63a5\u53e3&#039;, usage: 40, target: 30 },<br \/>\n      { subject: &#039;\u516c\u5171API&#039;, usage: 75, target: 90 },<br \/>\n      { subject: &#039;Webhook&#039;, usage: 20, target: 40 }<br \/>\n    ];<br \/>\n  }<\/p>\n<p>  function generateRecommendations() {<br \/>\n    const recommendations &#061; [];<\/p>\n<p>    \/\/ \u57fa\u4e8e\u6570\u636e\u5206\u6790\u751f\u6210\u5efa\u8bae<br \/>\n    const highViolationEndpoints &#061; generateEndpointDistributionData()<br \/>\n      .filter(e &#061;&gt; e.violations &gt; 10);<\/p>\n<p>    if (highViolationEndpoints.length &gt; 0) {<br \/>\n      highViolationEndpoints.forEach(endpoint &#061;&gt; {<br \/>\n        recommendations.push({<br \/>\n          id: &#096;increase-${endpoint.name}&#096;,<br \/>\n          title: &#096;\u589e\u52a0 ${endpoint.name} \u7684\u914d\u989d\u9650\u5236&#096;,<br \/>\n          description: &#096;\u8be5\u7aef\u70b9\u6709 ${endpoint.violations} \u6b21\u8fdd\u89c4&#xff0c;\u5efa\u8bae\u589e\u52a0\u914d\u989d\u9650\u5236\u4ee5\u63d0\u9ad8\u53ef\u7528\u6027\u3002&#096;,<br \/>\n          type: &#039;increase_limit&#039;,<br \/>\n          endpoint: endpoint.name,<br \/>\n          currentLimit: endpoint.requests,<br \/>\n          suggestedLimit: Math.ceil(endpoint.requests * 1.5)<br \/>\n        });<br \/>\n      });<br \/>\n    }<\/p>\n<p>    \/\/ \u68c0\u67e5\u4f7f\u7528\u7387\u4f4e\u7684\u7aef\u70b9<br \/>\n    const lowUsageEndpoints &#061; generateEndpointDistributionData()<br \/>\n      .filter(e &#061;&gt; e.usageRate &lt; 0.3 &amp;&amp; e.requests &gt; 100);<\/p>\n<p>    if (lowUsageEndpoints.length &gt; 0) {<br \/>\n      lowUsageEndpoints.forEach(endpoint &#061;&gt; {<br \/>\n        recommendations.push({<br \/>\n          id: &#096;decrease-${endpoint.name}&#096;,<br \/>\n          title: &#096;\u4f18\u5316 ${endpoint.name} \u7684\u914d\u989d\u914d\u7f6e&#096;,<br \/>\n          description: &#096;\u8be5\u7aef\u70b9\u4f7f\u7528\u7387\u8f83\u4f4e&#xff08;${(endpoint.usageRate * 100).toFixed(1)}%&#xff09;&#xff0c;\u53ef\u4ee5\u8003\u8651\u51cf\u5c11\u914d\u989d\u4ee5\u91ca\u653e\u8d44\u6e90\u3002&#096;,<br \/>\n          type: &#039;decrease_limit&#039;,<br \/>\n          endpoint: endpoint.name,<br \/>\n          currentLimit: endpoint.requests,<br \/>\n          suggestedLimit: Math.ceil(endpoint.requests * 0.7)<br \/>\n        });<br \/>\n      });<br \/>\n    }<\/p>\n<p>    \/\/ \u901a\u7528\u5efa\u8bae<br \/>\n    recommendations.push({<br \/>\n      id: &#039;implement-adaptive&#039;,<br \/>\n      title: &#039;\u5b9e\u65bd\u81ea\u9002\u5e94\u901f\u7387\u9650\u5236&#039;,<br \/>\n      description: &#039;\u6839\u636e\u5386\u53f2\u4f7f\u7528\u6a21\u5f0f\u52a8\u6001\u8c03\u6574\u9650\u5236&#xff0c;\u63d0\u9ad8\u7cfb\u7edf\u6548\u7387\u548c\u7528\u6237\u4f53\u9a8c\u3002&#039;,<br \/>\n      type: &#039;feature&#039;,<br \/>\n      priority: &#039;high&#039;<br \/>\n    });<\/p>\n<p>    recommendations.push({<br \/>\n      id: &#039;add-client-education&#039;,<br \/>\n      title: &#039;\u6dfb\u52a0\u5ba2\u6237\u7aef\u914d\u989d\u6559\u80b2&#039;,<br \/>\n      description: &#039;\u5728API\u54cd\u5e94\u4e2d\u6dfb\u52a0\u914d\u989d\u4f7f\u7528\u4fe1\u606f&#xff0c;\u5e2e\u52a9\u5ba2\u6237\u7aef\u66f4\u597d\u5730\u7ba1\u7406\u8bf7\u6c42\u3002&#039;,<br \/>\n      type: &#039;improvement&#039;,<br \/>\n      priority: &#039;medium&#039;<br \/>\n    });<\/p>\n<p>    return recommendations.slice(0, 5);<br \/>\n  }<\/p>\n<p>  function viewEventDetails(event) {<br \/>\n    console.log(&#039;Event details:&#039;, event);<br \/>\n    alert(&#096;\u4e8b\u4ef6\u8be6\u60c5:\\\\n${JSON.stringify(event, null, 2)}&#096;);<br \/>\n  }<\/p>\n<p>  function applyRecommendation(recommendation) {<br \/>\n    console.log(&#039;Applying recommendation:&#039;, recommendation);<\/p>\n<p>    if (recommendation.type &#061;&#061;&#061; &#039;increase_limit&#039; || recommendation.type &#061;&#061;&#061; &#039;decrease_limit&#039;) {<br \/>\n      handleAdjustLimit(<br \/>\n        recommendation.endpoint,<br \/>\n        &#039;custom_rule&#039;,<br \/>\n        recommendation.suggestedLimit<br \/>\n      );<br \/>\n    }<\/p>\n<p>    alert(&#096;\u5df2\u5e94\u7528\u5efa\u8bae: ${recommendation.title}&#096;);<br \/>\n  }<\/p>\n<p>  function dismissRecommendation(id) {<br \/>\n    console.log(&#039;Dismissing recommendation:&#039;, id);<br \/>\n    \/\/ \u5728\u5b9e\u9645\u5e94\u7528\u4e2d&#xff0c;\u8fd9\u91cc\u4f1a\u6807\u8bb0\u5efa\u8bae\u4e3a\u5df2\u5ffd\u7565<br \/>\n  }<br \/>\n}<\/p>\n<h2>\u7b2c23\u7ae0&#xff1a;\u5176\u4ed64xx\u72b6\u6001\u7801\u8be6\u89e3&#xff08;402\u3001406-418\u3001421-428\u3001431\u3001451&#xff09;<\/h2>\n<h3>\u5f15\u8a00<\/h3>\n<p>HTTP\u72b6\u6001\u7801\u662fWeb\u901a\u4fe1\u7684\u57fa\u7840\u8bed\u8a00&#xff0c;\u5176\u4e2d4xx\u7cfb\u5217\u4e13\u95e8\u6307\u793a\u5ba2\u6237\u7aef\u9519\u8bef\u3002\u867d\u7136404\u3001403\u548c400\u5e7f\u4e3a\u4eba\u77e5&#xff0c;\u4f46HTTP\u534f\u8bae\u8fd8\u5b9a\u4e49\u4e86\u8bb8\u591a\u4e13\u95e8\u5316\u76844xx\u72b6\u6001\u7801&#xff0c;\u4e3a\u7279\u5b9a\u9519\u8bef\u573a\u666f\u63d0\u4f9b\u7cbe\u786e\u8bed\u4e49\u3002\u8fd9\u4e9b&#034;\u8fb9\u7f18\u6848\u4f8b&#034;\u72b6\u6001\u7801\u5728\u73b0\u4ee3API\u8bbe\u8ba1\u3001\u5fae\u670d\u52a1\u67b6\u6784\u548cRESTful\u670d\u52a1\u4e2d\u626e\u6f14\u7740\u8d8a\u6765\u8d8a\u91cd\u8981\u7684\u89d2\u8272\u3002\u672c\u7ae0\u5c06\u6df1\u5165\u63a2\u8ba823\u4e2a\u4e0d\u5e38\u7528\u4f46\u91cd\u8981\u76844xx\u72b6\u6001\u7801&#xff0c;\u6db5\u76d6\u4ece\u652f\u4ed8\u7cfb\u7edf\u5230\u6cd5\u5f8b\u5408\u89c4\u7684\u5e7f\u6cdb\u573a\u666f\u3002<\/p>\n<h3>23.1 402 Payment Required&#xff08;\u9700\u8981\u4ed8\u6b3e&#xff09;<\/h3>\n<h4>23.1.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>402\u72b6\u6001\u7801\u5728HTTP\/1.1\u89c4\u8303\u4e2d\u5b9a\u4e49\u4e3a&#034;\u4fdd\u7559\u7528\u4e8e\u672a\u6765\u4f7f\u7528&#034;&#xff0c;\u4f46\u5b9e\u9645\u4e0a\u5df2\u88ab\u5e7f\u6cdb\u63a5\u53d7\u4e3a\u8868\u793a\u9700\u8981\u4ed8\u6b3e\u624d\u80fd\u8bbf\u95ee\u8d44\u6e90\u7684\u72b6\u6001\u3002\u867d\u7136\u4ece\u672a\u88ab\u6b63\u5f0f\u6807\u51c6\u5316&#xff0c;\u4f46\u5b83\u5df2\u6210\u4e3a\u652f\u4ed8\u7f51\u5173\u3001\u8ba2\u9605\u670d\u52a1\u548c\u5fae\u4ea4\u6613API\u7684\u4e8b\u5b9e\u6807\u51c6\u3002<\/p>\n<h4>23.1.2 \u4f7f\u7528\u573a\u666f<\/h4>\n<li>\n<p>\u8ba2\u9605\u5185\u5bb9\u8bbf\u95ee&#xff1a;\u7528\u6237\u5c1d\u8bd5\u8bbf\u95ee\u9700\u8981\u8ba2\u9605\u7684\u5185\u5bb9<\/p>\n<\/li>\n<li>\n<p>API\u8c03\u7528\u8ba1\u8d39&#xff1a;\u8d85\u8fc7\u514d\u8d39\u989d\u5ea6\u7684API\u8bf7\u6c42<\/p>\n<\/li>\n<li>\n<p>\u6570\u5b57\u5546\u54c1\u8d2d\u4e70&#xff1a;\u5c1d\u8bd5\u4e0b\u8f7d\u672a\u8d2d\u4e70\u7684\u7535\u5b50\u4e66\u3001\u8f6f\u4ef6\u7b49<\/p>\n<\/li>\n<li>\n<p>\u670d\u52a1\u989d\u5ea6\u8017\u5c3d&#xff1a;\u4e91\u670d\u52a1\u3001SaaS\u5e73\u53f0\u7684\u7528\u91cf\u8d85\u9650<\/p>\n<\/li>\n<h4>23.1.3 \u5b9e\u73b0\u793a\u4f8b<\/h4>\n<p>http<\/p>\n<p>HTTP\/1.1 402 Payment Required<br \/>\nContent-Type: application\/json<br \/>\nX-Payment-URL: https:\/\/api.example.com\/payment\/order_123<br \/>\nRetry-After: 3600<\/p>\n<p>{<br \/>\n  &#034;error&#034;: &#034;payment_required&#034;,<br \/>\n  &#034;message&#034;: &#034;You have exceeded your free API call limit&#034;,<br \/>\n  &#034;upgrade_url&#034;: &#034;https:\/\/api.example.com\/plans&#034;,<br \/>\n  &#034;current_usage&#034;: {<br \/>\n    &#034;used&#034;: 1000,<br \/>\n    &#034;limit&#034;: 100<br \/>\n  }<br \/>\n}<\/p>\n<h4>23.1.4 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u59cb\u7ec8\u63d0\u4f9b\u6e05\u6670\u7684\u9519\u8bef\u4fe1\u606f\u548c\u89e3\u51b3\u6307\u5f15<\/p>\n<\/li>\n<li>\n<p>\u5305\u542b\u652f\u4ed8URL\u6216\u5347\u7ea7\u8def\u5f84<\/p>\n<\/li>\n<li>\n<p>\u5bf9\u4e8eAPI\u9650\u6d41&#xff0c;\u4f7f\u7528429\u72b6\u6001\u7801\u53ef\u80fd\u66f4\u5408\u9002<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u652f\u6301\u591a\u79cd\u652f\u4ed8\u534f\u8bae&#xff08;\u5982Web Monetization API&#xff09;<\/p>\n<\/li>\n<\/ul>\n<h3>23.2 406 Not Acceptable&#xff08;\u4e0d\u53ef\u63a5\u53d7&#xff09;<\/h3>\n<h4>23.2.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>406\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u65e0\u6cd5\u751f\u6210\u5ba2\u6237\u7aefAccept\u5934\u6307\u5b9a\u7684\u54cd\u5e94\u5185\u5bb9\u3002\u5f53\u670d\u52a1\u5668\u4e0d\u652f\u6301\u5ba2\u6237\u7aef\u8bf7\u6c42\u7684\u5a92\u4f53\u7c7b\u578b\u65f6\u8fd4\u56de\u6b64\u72b6\u6001\u7801\u3002<\/p>\n<h4>23.2.2 \u5185\u5bb9\u534f\u5546\u673a\u5236<\/h4>\n<p>HTTP\u5185\u5bb9\u534f\u5546\u6d89\u53ca\u591a\u4e2a\u8bf7\u6c42\u5934&#xff1a;<\/p>\n<ul>\n<li>\n<p>Accept&#xff1a;\u5ba2\u6237\u7aef\u63a5\u53d7\u7684\u5a92\u4f53\u7c7b\u578b<\/p>\n<\/li>\n<li>\n<p>Accept-Charset&#xff1a;\u5b57\u7b26\u96c6\u504f\u597d<\/p>\n<\/li>\n<li>\n<p>Accept-Encoding&#xff1a;\u5185\u5bb9\u7f16\u7801\u504f\u597d<\/p>\n<\/li>\n<li>\n<p>Accept-Language&#xff1a;\u8bed\u8a00\u504f\u597d<\/p>\n<\/li>\n<\/ul>\n<h4>23.2.3 \u5b9e\u73b0\u6a21\u5f0f<\/h4>\n<p>python<\/p>\n<p># Flask\u793a\u4f8b&#xff1a;\u5904\u7406\u5185\u5bb9\u534f\u5546<br \/>\n&#064;app.route(&#039;\/resource&#039;)<br \/>\ndef get_resource():<br \/>\n    accepted_types &#061; request.accept_mimetypes<br \/>\n    if &#039;application\/json&#039; in accepted_types:<br \/>\n        return jsonify(data)<br \/>\n    elif &#039;application\/xml&#039; in accepted_types:<br \/>\n        return xml_response(data)<br \/>\n    else:<br \/>\n        # \u8fd4\u56de406\u5e76\u63d0\u4f9b\u652f\u6301\u7684\u683c\u5f0f<br \/>\n        response &#061; jsonify({<br \/>\n            &#039;error&#039;: &#039;Unsupported media type requested&#039;,<br \/>\n            &#039;supported_types&#039;: [<br \/>\n                &#039;application\/json&#039;,<br \/>\n                &#039;application\/xml&#039;,<br \/>\n                &#039;text\/html&#039;<br \/>\n            ]<br \/>\n        })<br \/>\n        response.status_code &#061; 406<br \/>\n        response.headers[&#039;Content-Type&#039;] &#061; &#039;application\/json&#039;<br \/>\n        return response<\/p>\n<h4>23.2.4 \u53d8\u4f53\u9009\u62e9\u5934<\/h4>\n<p>HTTP\/1.1\u5f15\u5165\u4e86Vary\u5934&#xff0c;\u6307\u793a\u7f13\u5b58\u670d\u52a1\u5668\u6839\u636e\u54ea\u4e9b\u8bf7\u6c42\u5934\u751f\u6210\u4e0d\u540c\u54cd\u5e94&#xff1a;<\/p>\n<p>text<\/p>\n<p>Vary: Accept, Accept-Language, User-Agent<\/p>\n<h4>23.2.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u5b9e\u73b0\u4f18\u96c5\u964d\u7ea7&#xff08;\u5982JSON\u4f5c\u4e3a\u9ed8\u8ba4\u683c\u5f0f&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u5728406\u54cd\u5e94\u4e2d\u5217\u51fa\u652f\u6301\u7684\u5a92\u4f53\u7c7b\u578b<\/p>\n<\/li>\n<li>\n<p>\u6b63\u786e\u8bbe\u7f6eVary\u5934\u4ee5\u652f\u6301\u7f13\u5b58<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u4f7f\u7528\u683c\u5f0f\u53c2\u6570\u4f5c\u4e3a\u540e\u5907\u65b9\u6848&#xff08;\u5982?format&#061;json&#xff09;<\/p>\n<\/li>\n<\/ul>\n<h3>23.3 407 Proxy Authentication Required&#xff08;\u9700\u8981\u4ee3\u7406\u8ba4\u8bc1&#xff09;<\/h3>\n<h4>23.3.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>407\u72b6\u6001\u7801\u8868\u793a\u5ba2\u6237\u7aef\u5fc5\u987b\u9996\u5148\u901a\u8fc7\u4ee3\u7406\u670d\u52a1\u5668\u7684\u8eab\u4efd\u9a8c\u8bc1\u3002\u4e0e401\u7c7b\u4f3c&#xff0c;\u4f46\u4e13\u95e8\u7528\u4e8e\u4ee3\u7406\u8ba4\u8bc1\u3002<\/p>\n<h4>23.3.2 \u4ee3\u7406\u8ba4\u8bc1\u65b9\u6848<\/h4>\n<li>\n<p>Basic&#xff1a;\u57fa\u672c\u8ba4\u8bc1&#xff08;Base64\u7f16\u7801&#xff09;<\/p>\n<\/li>\n<li>\n<p>Digest&#xff1a;\u6458\u8981\u8ba4\u8bc1&#xff08;\u66f4\u5b89\u5168&#xff09;<\/p>\n<\/li>\n<li>\n<p>Bearer&#xff1a;\u4ee4\u724c\u8ba4\u8bc1<\/p>\n<\/li>\n<li>\n<p>NTLM&#xff1a;Windows\u8ba4\u8bc1\u534f\u8bae<\/p>\n<\/li>\n<h4>23.3.3 \u5de5\u4f5c\u6d41\u7a0b<\/h4>\n<p>text<\/p>\n<p>\u5ba2\u6237\u7aef \u2192 \u4ee3\u7406: GET \/resource HTTP\/1.1<br \/>\n\u4ee3\u7406 \u2192 \u5ba2\u6237\u7aef: HTTP\/1.1 407 Proxy Authentication Required<br \/>\n                Proxy-Authenticate: Basic realm&#061;&#034;CorpProxy&#034;<br \/>\n\u5ba2\u6237\u7aef \u2192 \u4ee3\u7406: GET \/resource HTTP\/1.1<br \/>\n                Proxy-Authorization: Basic dXNlcjpwYXNz<br \/>\n\u4ee3\u7406 \u2192 \u540e\u7aef: \u8f6c\u53d1\u8bf7\u6c42<br \/>\n\u540e\u7aef \u2192 \u5ba2\u6237\u7aef: \u901a\u8fc7\u4ee3\u7406\u8fd4\u56de\u54cd\u5e94<\/p>\n<h4>23.3.4 \u5b89\u5168\u8003\u8651<\/h4>\n<ul>\n<li>\n<p>\u59cb\u7ec8\u4f7f\u7528HTTPS\u4f20\u8f93\u4ee3\u7406\u51ed\u8bc1<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u4f7f\u7528\u6458\u8981\u8ba4\u8bc1\u907f\u514d\u660e\u6587\u4f20\u8f93<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u51ed\u8bc1\u8f6e\u6362\u673a\u5236<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7\u4ee3\u7406\u8ba4\u8bc1\u5931\u8d25\u5c1d\u8bd5<\/p>\n<\/li>\n<\/ul>\n<h4>23.3.5 \u5b9e\u73b0\u793a\u4f8b<\/h4>\n<p>nginx<\/p>\n<p># Nginx\u4ee3\u7406\u8ba4\u8bc1\u914d\u7f6e<br \/>\nlocation \/ {<br \/>\n    auth_basic &#034;Proxy Authentication Required&#034;;<br \/>\n    auth_basic_user_file \/etc\/nginx\/.htpasswd;<br \/>\n    proxy_pass http:\/\/backend;<\/p>\n<p>    # \u8f6c\u53d1\u8ba4\u8bc1\u5934\u5230\u540e\u7aef&#xff08;\u5982\u679c\u9700\u8981&#xff09;<br \/>\n    proxy_set_header Authorization $http_authorization;<br \/>\n}<\/p>\n<h3>23.4 408 Request Timeout&#xff08;\u8bf7\u6c42\u8d85\u65f6&#xff09;<\/h3>\n<h4>23.4.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>408\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u5728\u7b49\u5f85\u8bf7\u6c42\u65f6\u8d85\u65f6\u3002\u670d\u52a1\u5668\u613f\u610f\u7ee7\u7eed\u7b49\u5f85&#xff0c;\u4f46\u5ba2\u6237\u7aef\u6ca1\u6709\u5728\u670d\u52a1\u5668\u51c6\u5907\u7b49\u5f85\u7684\u65f6\u95f4\u5185\u5b8c\u6210\u8bf7\u6c42\u53d1\u9001\u3002<\/p>\n<h4>23.4.2 \u8d85\u65f6\u539f\u56e0\u5206\u6790<\/h4>\n<li>\n<p>\u7f51\u7edc\u5ef6\u8fdf&#xff1a;\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u95f4\u7684\u9ad8\u5ef6\u8fdf<\/p>\n<\/li>\n<li>\n<p>\u8bf7\u6c42\u4f53\u8fc7\u5927&#xff1a;\u4e0a\u4f20\u5927\u6587\u4ef6\u65f6\u4f20\u8f93\u7f13\u6162<\/p>\n<\/li>\n<li>\n<p>\u5ba2\u6237\u7aef\u6545\u969c&#xff1a;\u5ba2\u6237\u7aef\u5d29\u6e83\u6216\u8fde\u63a5\u4e2d\u65ad<\/p>\n<\/li>\n<li>\n<p>\u7f51\u7edc\u4e2d\u65ad&#xff1a;\u4e2d\u95f4\u7f51\u7edc\u8bbe\u5907\u95ee\u9898<\/p>\n<\/li>\n<h4>23.4.3 \u670d\u52a1\u5668\u914d\u7f6e<\/h4>\n<p>nginx<\/p>\n<p># Nginx\u8d85\u65f6\u914d\u7f6e<br \/>\nhttp {<br \/>\n    # \u5ba2\u6237\u7aef\u8bfb\u53d6\u8d85\u65f6&#xff08;\u4e24\u4e2a\u8fde\u7eed\u64cd\u4f5c\u95f4\u9694&#xff09;<br \/>\n    client_header_timeout 60s;<\/p>\n<p>    # \u5ba2\u6237\u7aef\u53d1\u9001\u8bf7\u6c42\u4f53\u8d85\u65f6<br \/>\n    client_body_timeout 60s;<\/p>\n<p>    # \u4fdd\u6301\u8fde\u63a5\u8d85\u65f6<br \/>\n    keepalive_timeout 75s;<\/p>\n<p>    # \u53d1\u9001\u54cd\u5e94\u5230\u5ba2\u6237\u7aef\u8d85\u65f6<br \/>\n    send_timeout 60s;<br \/>\n}<\/p>\n<h4>23.4.4 \u91cd\u8bd5\u7b56\u7565<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u6307\u6570\u9000\u907f\u91cd\u8bd5\u7b56\u7565<br \/>\nasync function fetchWithRetry(url, options, maxRetries &#061; 3) {<br \/>\n    let lastError;<\/p>\n<p>    for (let i &#061; 0; i &lt; maxRetries; i&#043;&#043;) {<br \/>\n        try {<br \/>\n            const response &#061; await fetch(url, options);<br \/>\n            return response;<br \/>\n        } catch (error) {<br \/>\n            lastError &#061; error;<\/p>\n<p>            \/\/ \u68c0\u67e5\u662f\u5426\u4e3a\u8d85\u65f6\u9519\u8bef<br \/>\n            if (error.name &#061;&#061;&#061; &#039;TimeoutError&#039; || error.status &#061;&#061;&#061; 408) {<br \/>\n                \/\/ \u6307\u6570\u9000\u907f&#xff1a;1s, 2s, 4s&#8230;<br \/>\n                const delay &#061; Math.pow(2, i) * 1000;<br \/>\n                await new Promise(resolve &#061;&gt; setTimeout(resolve, delay));<br \/>\n                continue;<br \/>\n            }<\/p>\n<p>            \/\/ \u975e\u8d85\u65f6\u9519\u8bef&#xff0c;\u7acb\u5373\u629b\u51fa<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    throw lastError;<br \/>\n}<\/p>\n<h4>23.4.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u5b9e\u73b0\u5e42\u7b49\u8bf7\u6c42\u4ee5\u5b89\u5168\u91cd\u8bd5<\/p>\n<\/li>\n<li>\n<p>\u5ba2\u6237\u7aef\u4f7f\u7528\u6307\u6570\u9000\u907f\u7b97\u6cd5<\/p>\n<\/li>\n<li>\n<p>\u670d\u52a1\u5668\u8bbe\u7f6e\u5408\u7406\u7684\u8d85\u65f6\u9608\u503c<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7\u8d85\u65f6\u7387\u4ee5\u8bc6\u522b\u6027\u80fd\u95ee\u9898<\/p>\n<\/li>\n<\/ul>\n<h3>23.5 409 Conflict&#xff08;\u51b2\u7a81&#xff09;<\/h3>\n<h4>23.5.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>409\u72b6\u6001\u7801\u8868\u793a\u8bf7\u6c42\u4e0e\u670d\u52a1\u5668\u5f53\u524d\u72b6\u6001\u51b2\u7a81\u3002\u901a\u5e38\u7528\u4e8e\u5e76\u53d1\u4fee\u6539\u573a\u666f&#xff0c;\u5982\u7248\u672c\u51b2\u7a81\u6216\u8d44\u6e90\u9501\u5b9a\u3002<\/p>\n<h4>23.5.2 \u4f7f\u7528\u573a\u666f<\/h4>\n<li>\n<p>\u7248\u672c\u51b2\u7a81&#xff1a;\u57fa\u4e8e\u7248\u672c\u53f7\u7684\u4e50\u89c2\u9501<\/p>\n<\/li>\n<li>\n<p>\u552f\u4e00\u7ea6\u675f\u8fdd\u53cd&#xff1a;\u6570\u636e\u5e93\u552f\u4e00\u952e\u51b2\u7a81<\/p>\n<\/li>\n<li>\n<p>\u4e1a\u52a1\u89c4\u5219\u51b2\u7a81&#xff1a;\u8fdd\u53cd\u9886\u57df\u7279\u5b9a\u89c4\u5219<\/p>\n<\/li>\n<li>\n<p>\u8d44\u6e90\u72b6\u6001\u51b2\u7a81&#xff1a;\u5c1d\u8bd5\u4fee\u6539\u5df2\u5220\u9664\u7684\u8d44\u6e90<\/p>\n<\/li>\n<h4>23.5.3 \u4e50\u89c2\u9501\u5b9e\u73b0<\/h4>\n<p>java<\/p>\n<p>\/\/ Spring Boot\u793a\u4f8b&#xff1a;\u4f7f\u7528ETag\u5b9e\u73b0\u4e50\u89c2\u9501<br \/>\n&#064;PutMapping(&#034;\/products\/{id}&#034;)<br \/>\npublic ResponseEntity&lt;?&gt; updateProduct(<br \/>\n        &#064;PathVariable Long id,<br \/>\n        &#064;RequestBody Product product,<br \/>\n        &#064;RequestHeader(&#034;If-Match&#034;) String ifMatch) {<\/p>\n<p>    Product existing &#061; productRepository.findById(id)<br \/>\n        .orElseThrow(() -&gt; new ResourceNotFoundException());<\/p>\n<p>    \/\/ \u68c0\u67e5ETag\u662f\u5426\u5339\u914d<br \/>\n    String currentETag &#061; generateETag(existing);<br \/>\n    if (!currentETag.equals(ifMatch)) {<br \/>\n        return ResponseEntity.status(409)<br \/>\n            .header(&#034;ETag&#034;, currentETag)<br \/>\n            .body(Map.of(<br \/>\n                &#034;error&#034;, &#034;conflict&#034;,<br \/>\n                &#034;message&#034;, &#034;Resource has been modified by another user&#034;,<br \/>\n                &#034;current_version&#034;, currentETag<br \/>\n            ));<br \/>\n    }<\/p>\n<p>    \/\/ \u66f4\u65b0\u8d44\u6e90\u5e76\u751f\u6210\u65b0ETag<br \/>\n    Product updated &#061; productRepository.save(product);<br \/>\n    String newETag &#061; generateETag(updated);<\/p>\n<p>    return ResponseEntity.ok()<br \/>\n        .header(&#034;ETag&#034;, newETag)<br \/>\n        .body(updated);<br \/>\n}<\/p>\n<h4>23.5.4 \u51b2\u7a81\u89e3\u51b3\u7b56\u7565<\/h4>\n<li>\n<p>\u6700\u540e\u5199\u5165\u83b7\u80dc&#xff1a;\u7b80\u5355\u7684\u8986\u76d6\u7b56\u7565<\/p>\n<\/li>\n<li>\n<p>\u624b\u52a8\u5408\u5e76&#xff1a;\u63d0\u793a\u7528\u6237\u89e3\u51b3\u51b2\u7a81<\/p>\n<\/li>\n<li>\n<p>\u81ea\u52a8\u5408\u5e76&#xff1a;\u57fa\u4e8e\u89c4\u5219\u7684\u81ea\u52a8\u5408\u5e76<\/p>\n<\/li>\n<li>\n<p>\u64cd\u4f5c\u8f6c\u6362&#xff1a;\u5b9e\u65f6\u534f\u4f5c\u7cfb\u7edf\u4f7f\u7528<\/p>\n<\/li>\n<h4>23.5.5 \u54cd\u5e94\u683c\u5f0f\u5efa\u8bae<\/h4>\n<p>json<\/p>\n<p>{<br \/>\n  &#034;error&#034;: &#034;conflict&#034;,<br \/>\n  &#034;message&#034;: &#034;Resource update conflict&#034;,<br \/>\n  &#034;conflict_details&#034;: {<br \/>\n    &#034;field&#034;: &#034;email&#034;,<br \/>\n    &#034;reason&#034;: &#034;already_exists&#034;,<br \/>\n    &#034;existing_value&#034;: &#034;user&#064;example.com&#034;<br \/>\n  },<br \/>\n  &#034;resolution_options&#034;: [<br \/>\n    {<br \/>\n      &#034;action&#034;: &#034;overwrite&#034;,<br \/>\n      &#034;method&#034;: &#034;PUT&#034;,<br \/>\n      &#034;url&#034;: &#034;\/resource\/123?force&#061;true&#034;<br \/>\n    },<br \/>\n    {<br \/>\n      &#034;action&#034;: &#034;merge&#034;,<br \/>\n      &#034;method&#034;: &#034;PATCH&#034;,<br \/>\n      &#034;url&#034;: &#034;\/resource\/123&#034;<br \/>\n    }<br \/>\n  ]<br \/>\n}<\/p>\n<h3>23.6 410 Gone&#xff08;\u5df2\u5220\u9664&#xff09;<\/h3>\n<h4>23.6.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>410\u72b6\u6001\u7801\u8868\u793a\u8bf7\u6c42\u7684\u8d44\u6e90\u5728\u670d\u52a1\u5668\u4e0a\u5df2\u6c38\u4e45\u5220\u9664&#xff0c;\u4e14\u6ca1\u6709\u8f6c\u53d1\u5730\u5740\u3002\u4e0e404\u4e0d\u540c&#xff0c;410\u660e\u786e\u8868\u793a\u8d44\u6e90\u66fe\u7ecf\u5b58\u5728\u4f46\u88ab\u6709\u610f\u5220\u9664\u3002<\/p>\n<h4>23.6.2 \u4e0e404\u7684\u533a\u522b<\/h4>\n<table>\n<tr>\u7279\u6027404 Not Found410 Gone<\/tr>\n<tbody>\n<tr>\n<td>\u8d44\u6e90\u662f\u5426\u5b58\u5728\u8fc7<\/td>\n<td>\u672a\u77e5<\/td>\n<td>\u66fe\u7ecf\u5b58\u5728<\/td>\n<\/tr>\n<tr>\n<td>\u662f\u5426\u6c38\u4e45\u6027<\/td>\n<td>\u53ef\u80fd\u662f\u4e34\u65f6\u7684<\/td>\n<td>\u6c38\u4e45\u5220\u9664<\/td>\n<\/tr>\n<tr>\n<td>\u7f13\u5b58\u5efa\u8bae<\/td>\n<td>\u53ef\u77ed\u671f\u7f13\u5b58<\/td>\n<td>\u5e94\u957f\u671f\u7f13\u5b58<\/td>\n<\/tr>\n<tr>\n<td>\u5ba2\u6237\u7aef\u54cd\u5e94<\/td>\n<td>\u53ef\u91cd\u8bd5<\/td>\n<td>\u5e94\u5220\u9664\u5f15\u7528<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>23.6.3 \u4f7f\u7528\u573a\u666f<\/h4>\n<li>\n<p>\u7528\u6237\u5220\u9664\u5185\u5bb9&#xff1a;\u793e\u4ea4\u5a92\u4f53\u5e16\u5b50\u3001\u8bc4\u8bba<\/p>\n<\/li>\n<li>\n<p>\u5185\u5bb9\u4e0b\u67b6&#xff1a;\u5df2\u505c\u6b62\u7684\u4ea7\u54c1\u9875\u9762<\/p>\n<\/li>\n<li>\n<p>URL\u91cd\u6784&#xff1a;\u65e7URL\u4e0d\u518d\u53ef\u7528\u4e14\u65e0\u91cd\u5b9a\u5411<\/p>\n<\/li>\n<li>\n<p>\u5408\u89c4\u8981\u6c42&#xff1a;\u6cd5\u5f8b\u8981\u6c42\u5220\u9664\u7684\u5185\u5bb9<\/p>\n<\/li>\n<h4>23.6.4 SEO\u8003\u8651<\/h4>\n<p>html<\/p>\n<p>&lt;!&#8211; \u5bf9\u4e8eSEO\u53cb\u597d\u7684410\u9875\u9762 &#8211;&gt;<br \/>\n&lt;!DOCTYPE html&gt;<br \/>\n&lt;html lang&#061;&#034;en&#034;&gt;<br \/>\n&lt;head&gt;<br \/>\n    &lt;meta charset&#061;&#034;UTF-8&#034;&gt;<br \/>\n    &lt;meta name&#061;&#034;robots&#034; content&#061;&#034;noindex, follow&#034;&gt;<br \/>\n    &lt;title&gt;Content Removed&lt;\/title&gt;<br \/>\n&lt;\/head&gt;<br \/>\n&lt;body&gt;<br \/>\n    &lt;h1&gt;This content has been permanently removed&lt;\/h1&gt;<br \/>\n    &lt;p&gt;The resource you requested was intentionally removed and is no longer available.&lt;\/p&gt;<\/p>\n<p>    &lt;!&#8211; \u63d0\u4f9b\u76f8\u5173\u66ff\u4ee3\u5185\u5bb9 &#8211;&gt;<br \/>\n    &lt;div class&#061;&#034;suggestions&#034;&gt;<br \/>\n        &lt;h2&gt;You might be interested in:&lt;\/h2&gt;<br \/>\n        &lt;ul&gt;<br \/>\n            &lt;li&gt;&lt;a href&#061;&#034;\/new-location&#034;&gt;Updated version of this content&lt;\/a&gt;&lt;\/li&gt;<br \/>\n            &lt;li&gt;&lt;a href&#061;&#034;\/related-topic&#034;&gt;Related information&lt;\/a&gt;&lt;\/li&gt;<br \/>\n        &lt;\/ul&gt;<br \/>\n    &lt;\/div&gt;<\/p>\n<p>    &lt;!&#8211; \u7ed3\u6784\u5316\u6570\u636e\u5e2e\u52a9\u641c\u7d22\u5f15\u64ce\u7406\u89e3 &#8211;&gt;<br \/>\n    &lt;script type&#061;&#034;application\/ld&#043;json&#034;&gt;<br \/>\n    {<br \/>\n        &#034;&#064;context&#034;: &#034;https:\/\/schema.org&#034;,<br \/>\n        &#034;&#064;type&#034;: &#034;WebPage&#034;,<br \/>\n        &#034;name&#034;: &#034;Content Removed&#034;,<br \/>\n        &#034;description&#034;: &#034;This page indicates that previously available content has been permanently removed.&#034;,<br \/>\n        &#034;mainEntity&#034;: {<br \/>\n            &#034;&#064;type&#034;: &#034;Message&#034;,<br \/>\n            &#034;text&#034;: &#034;The requested resource was intentionally removed and is no longer available.&#034;<br \/>\n        }<br \/>\n    }<br \/>\n    &lt;\/script&gt;<br \/>\n&lt;\/body&gt;<br \/>\n&lt;\/html&gt;<\/p>\n<h4>23.6.5 \u5b9e\u73b0\u6a21\u5f0f<\/h4>\n<p>python<\/p>\n<p># Django\u89c6\u56fe\u793a\u4f8b<br \/>\nfrom django.http import HttpResponseGone<br \/>\nfrom django.views.generic import DetailView<\/p>\n<p>class DeletedResourceView(DetailView):<br \/>\n    def get(self, request, *args, **kwargs):<br \/>\n        # \u68c0\u67e5\u8d44\u6e90\u662f\u5426\u88ab\u6807\u8bb0\u4e3a\u5220\u9664<br \/>\n        resource &#061; self.get_object()<\/p>\n<p>        if resource.deleted_at:<br \/>\n            response &#061; HttpResponseGone()<br \/>\n            response[&#039;Link&#039;] &#061; &#039;&lt;\/related-resource&gt;; rel&#061;&#034;related&#034;&#039;<br \/>\n            response[&#039;X-Deleted-At&#039;] &#061; resource.deleted_at.isoformat()<br \/>\n            response[&#039;X-Deleted-Reason&#039;] &#061; resource.deletion_reason<br \/>\n            return response<\/p>\n<p>        return super().get(request, *args, **kwargs)<\/p>\n<h4>23.6.6 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u4e3a\u5df2\u5220\u9664\u8d44\u6e90\u4fdd\u7559\u5143\u6570\u636e\u4e00\u6bb5\u65f6\u95f4<\/p>\n<\/li>\n<li>\n<p>\u5728\u54cd\u5e94\u4e2d\u5305\u542b\u5220\u9664\u539f\u56e0\u548c\u65f6\u95f4\u6233<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u76f8\u5173\u8d44\u6e90\u7684\u94fe\u63a5<\/p>\n<\/li>\n<li>\n<p>\u66f4\u65b0\u5185\u90e8\u94fe\u63a5\u548c\u7ad9\u70b9\u5730\u56fe<\/p>\n<\/li>\n<\/ul>\n<h3>23.7 411 Length Required&#xff08;\u9700\u8981\u5185\u5bb9\u957f\u5ea6&#xff09;<\/h3>\n<h4>23.7.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>411\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u62d2\u7edd\u5904\u7406\u6ca1\u6709Content-Length\u5934\u7684\u8bf7\u6c42\u3002\u67d0\u4e9b\u670d\u52a1\u5668\u8981\u6c42\u63d0\u524d\u77e5\u9053\u8bf7\u6c42\u4f53\u5927\u5c0f\u4ee5\u8fdb\u884c\u5b89\u5168\u6216\u6548\u7387\u4f18\u5316\u3002<\/p>\n<h4>23.7.2 \u4f7f\u7528\u573a\u666f<\/h4>\n<li>\n<p>\u6587\u4ef6\u4e0a\u4f20&#xff1a;\u9700\u8981\u77e5\u9053\u4e0a\u4f20\u6587\u4ef6\u5927\u5c0f<\/p>\n<\/li>\n<li>\n<p>API\u9650\u6d41&#xff1a;\u57fa\u4e8e\u5185\u5bb9\u957f\u5ea6\u7684\u914d\u989d\u68c0\u67e5<\/p>\n<\/li>\n<li>\n<p>\u5b89\u5168\u626b\u63cf&#xff1a;\u63d0\u524d\u68c0\u6d4b\u8fc7\u5927\u8bf7\u6c42<\/p>\n<\/li>\n<li>\n<p>\u8d44\u6e90\u9884\u5206\u914d&#xff1a;\u63d0\u524d\u5206\u914d\u5b58\u50a8\u7a7a\u95f4<\/p>\n<\/li>\n<h4>23.7.3 \u5206\u5757\u4f20\u8f93\u7f16\u7801<\/h4>\n<p>\u5f53\u65e0\u6cd5\u63d0\u524d\u77e5\u9053\u5185\u5bb9\u5927\u5c0f\u65f6&#xff0c;\u53ef\u4f7f\u7528\u5206\u5757\u4f20\u8f93\u7f16\u7801&#xff1a;<\/p>\n<p>http<\/p>\n<p>POST \/upload HTTP\/1.1<br \/>\nHost: example.com<br \/>\nTransfer-Encoding: chunked<\/p>\n<p>7\\\\r\\\\n<br \/>\nMozilla\\\\r\\\\n<br \/>\n9\\\\r\\\\n<br \/>\nDeveloper\\\\r\\\\n<br \/>\n7\\\\r\\\\n<br \/>\nNetwork\\\\r\\\\n<br \/>\n0\\\\r\\\\n<br \/>\n\\\\r\\\\n<\/p>\n<h4>23.7.4 \u5ba2\u6237\u7aef\u5904\u7406<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u73b0\u4ee3JavaScript\u4e2d\u81ea\u52a8\u5904\u7406Content-Length<br \/>\nasync function uploadFile(file) {<br \/>\n    const formData &#061; new FormData();<br \/>\n    formData.append(&#039;file&#039;, file);<\/p>\n<p>    \/\/ Fetch API\u4f1a\u81ea\u52a8\u8ba1\u7b97\u548c\u8bbe\u7f6eContent-Length<br \/>\n    const response &#061; await fetch(&#039;\/upload&#039;, {<br \/>\n        method: &#039;POST&#039;,<br \/>\n        body: formData,<br \/>\n        \/\/ \u6ce8\u610f&#xff1a;FormData\u4f7f\u7528multipart\/form-data\u65f6<br \/>\n        \/\/ \u6d4f\u89c8\u5668\u4f1a\u81ea\u52a8\u5904\u7406&#xff0c;\u4e0d\u9700\u8981\u624b\u52a8\u8bbe\u7f6e<br \/>\n    });<\/p>\n<p>    return response.json();<br \/>\n}<\/p>\n<p>\/\/ \u624b\u52a8\u8bbe\u7f6eContent-Length\u7684\u793a\u4f8b<br \/>\nasync function sendJsonData(data) {<br \/>\n    const jsonString &#061; JSON.stringify(data);<\/p>\n<p>    const response &#061; await fetch(&#039;\/api\/data&#039;, {<br \/>\n        method: &#039;POST&#039;,<br \/>\n        headers: {<br \/>\n            &#039;Content-Type&#039;: &#039;application\/json&#039;,<br \/>\n            &#039;Content-Length&#039;: Buffer.byteLength(jsonString, &#039;utf8&#039;).toString()<br \/>\n        },<br \/>\n        body: jsonString<br \/>\n    });<\/p>\n<p>    return response.json();<br \/>\n}<\/p>\n<h4>23.7.5 \u670d\u52a1\u5668\u914d\u7f6e<\/h4>\n<p>nginx<\/p>\n<p># Nginx\u914d\u7f6e&#xff1a;\u9650\u5236\u8bf7\u6c42\u4f53\u5927\u5c0f\u5e76\u9700\u8981Content-Length<br \/>\nhttp {<br \/>\n    # \u542f\u7528\u8bf7\u6c42\u4f53\u5927\u5c0f\u68c0\u67e5<br \/>\n    client_max_body_size 10m;<\/p>\n<p>    # \u5bf9\u4e8e\u67d0\u4e9b\u7aef\u70b9\u8981\u6c42Content-Length<br \/>\n    location \/api\/upload {<br \/>\n        # \u5982\u679c\u8bf7\u6c42\u4f53\u8d85\u8fc71M&#xff0c;\u8981\u6c42Content-Length<br \/>\n        if ($request_body_length &gt; 1m) {<br \/>\n            # \u68c0\u67e5\u662f\u5426\u6709Content-Length\u5934<br \/>\n            if ($http_content_length &#061; &#034;&#034;) {<br \/>\n                return 411;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        # \u6b63\u5e38\u5904\u7406<br \/>\n        proxy_pass http:\/\/backend;<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.7.6 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u5bf9\u4e8e\u5c0f\u8bf7\u6c42\u4f53&#xff0c;\u670d\u52a1\u5668\u53ef\u66f4\u7075\u6d3b<\/p>\n<\/li>\n<li>\n<p>\u5927\u6587\u4ef6\u4e0a\u4f20\u5e94\u8981\u6c42Content-Length<\/p>\n<\/li>\n<li>\n<p>\u652f\u6301\u5206\u5757\u4f20\u8f93\u4f5c\u4e3a\u5907\u9009\u65b9\u6848<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u6e05\u6670\u7684\u9519\u8bef\u4fe1\u606f\u8bf4\u660e\u8981\u6c42<\/p>\n<\/li>\n<\/ul>\n<h3>23.8 412 Precondition Failed&#xff08;\u524d\u63d0\u6761\u4ef6\u5931\u8d25&#xff09;<\/h3>\n<h4>23.8.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>412\u72b6\u6001\u7801\u8868\u793a\u8bf7\u6c42\u4e2d\u63d0\u4f9b\u7684\u6761\u4ef6\u5934&#xff08;\u5982If-Match\u3001If-None-Match\u3001If-Modified-Since\u7b49&#xff09;\u8bc4\u4f30\u5931\u8d25\u3002<\/p>\n<h4>23.8.2 \u6761\u4ef6\u8bf7\u6c42\u5934<\/h4>\n<li>\n<p>If-Match&#xff1a;\u8981\u6c42ETag\u5339\u914d<\/p>\n<\/li>\n<li>\n<p>If-None-Match&#xff1a;\u8981\u6c42ETag\u4e0d\u5339\u914d<\/p>\n<\/li>\n<li>\n<p>If-Modified-Since&#xff1a;\u8981\u6c42\u8d44\u6e90\u5728\u6307\u5b9a\u65f6\u95f4\u540e\u88ab\u4fee\u6539<\/p>\n<\/li>\n<li>\n<p>If-Unmodified-Since&#xff1a;\u8981\u6c42\u8d44\u6e90\u5728\u6307\u5b9a\u65f6\u95f4\u540e\u672a\u4fee\u6539<\/p>\n<\/li>\n<li>\n<p>If-Range&#xff1a;\u7528\u4e8e\u65ad\u70b9\u7eed\u4f20<\/p>\n<\/li>\n<h4>23.8.3 \u4e50\u89c2\u9501\u5b9e\u73b0<\/h4>\n<p>python<\/p>\n<p># FastAPI\u793a\u4f8b&#xff1a;\u4f7f\u7528ETag\u5b9e\u73b0\u4e50\u89c2\u9501<br \/>\nfrom fastapi import FastAPI, Header, HTTPException<br \/>\nimport hashlib<br \/>\nimport json<\/p>\n<p>app &#061; FastAPI()<\/p>\n<p>def generate_etag(data: dict) -&gt; str:<br \/>\n    &#034;&#034;&#034;\u751f\u6210\u8d44\u6e90\u7684ETag&#034;&#034;&#034;<br \/>\n    data_str &#061; json.dumps(data, sort_keys&#061;True)<br \/>\n    return hashlib.md5(data_str.encode()).hexdigest()<\/p>\n<p>resources &#061; {<br \/>\n    &#034;1&#034;: {&#034;id&#034;: 1, &#034;name&#034;: &#034;Resource 1&#034;, &#034;version&#034;: 1}<br \/>\n}<\/p>\n<p>&#064;app.put(&#034;\/resources\/{resource_id}&#034;)<br \/>\nasync def update_resource(<br \/>\n    resource_id: str,<br \/>\n    updates: dict,<br \/>\n    if_match: str &#061; Header(None, alias&#061;&#034;If-Match&#034;)<br \/>\n):<br \/>\n    if resource_id not in resources:<br \/>\n        raise HTTPException(status_code&#061;404)<\/p>\n<p>    current &#061; resources[resource_id]<br \/>\n    current_etag &#061; generate_etag(current)<\/p>\n<p>    # \u68c0\u67e5ETag\u662f\u5426\u5339\u914d<br \/>\n    if if_match and if_match !&#061; current_etag:<br \/>\n        raise HTTPException(<br \/>\n            status_code&#061;412,<br \/>\n            detail&#061;{<br \/>\n                &#034;error&#034;: &#034;precondition_failed&#034;,<br \/>\n                &#034;message&#034;: &#034;Resource has been modified&#034;,<br \/>\n                &#034;current_etag&#034;: current_etag<br \/>\n            }<br \/>\n        )<\/p>\n<p>    # \u66f4\u65b0\u8d44\u6e90<br \/>\n    current.update(updates)<br \/>\n    current[&#034;version&#034;] &#043;&#061; 1<\/p>\n<p>    new_etag &#061; generate_etag(current)<\/p>\n<p>    return {<br \/>\n        &#034;data&#034;: current,<br \/>\n        &#034;etag&#034;: new_etag<br \/>\n    }<\/p>\n<h4>23.8.4 \u7f13\u5b58\u9a8c\u8bc1\u573a\u666f<\/h4>\n<p>http<\/p>\n<p># \u7f13\u5b58\u9a8c\u8bc1\u8bf7\u6c42<br \/>\nGET \/resource HTTP\/1.1<br \/>\nHost: api.example.com<br \/>\nIf-None-Match: &#034;abc123&#034;<\/p>\n<p># \u54cd\u5e94&#xff08;\u5982\u679c\u672a\u4fee\u6539&#xff09;<br \/>\nHTTP\/1.1 304 Not Modified<br \/>\nETag: &#034;abc123&#034;<br \/>\nCache-Control: max-age&#061;3600<\/p>\n<p># \u54cd\u5e94&#xff08;\u5982\u679c\u5df2\u4fee\u6539&#xff09;<br \/>\nHTTP\/1.1 200 OK<br \/>\nETag: &#034;def456&#034;<br \/>\nCache-Control: max-age&#061;3600<br \/>\nContent-Type: application\/json<\/p>\n<p>{&#034;data&#034;: &#034;updated&#034;}<\/p>\n<h4>23.8.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u4e3a\u53ef\u53d8\u8d44\u6e90\u5b9e\u73b0ETag\u652f\u6301<\/p>\n<\/li>\n<li>\n<p>\u5728\u54cd\u5e94\u4e2d\u59cb\u7ec8\u8fd4\u56deETag\u5934<\/p>\n<\/li>\n<li>\n<p>\u652f\u6301\u5f31ETag&#xff08;\u4ee5W\/\u5f00\u5934&#xff09;\u7528\u4e8e\u542f\u53d1\u5f0f\u6bd4\u8f83<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u6e05\u6670\u7684\u9519\u8bef\u4fe1\u606f\u8bf4\u660e\u524d\u63d0\u6761\u4ef6<\/p>\n<\/li>\n<\/ul>\n<h3>23.9 413 Payload Too Large&#xff08;\u8bf7\u6c42\u4f53\u8fc7\u5927&#xff09;<\/h3>\n<h4>23.9.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>413\u72b6\u6001\u7801\u8868\u793a\u8bf7\u6c42\u4f53\u8d85\u8fc7\u670d\u52a1\u5668\u80fd\u591f\u5904\u7406\u7684\u5927\u5c0f\u9650\u5236\u3002\u670d\u52a1\u5668\u53ef\u4ee5\u5173\u95ed\u8fde\u63a5\u6216\u8fd4\u56deRetry-After\u5934\u6307\u793a\u4f55\u65f6\u91cd\u8bd5\u3002<\/p>\n<h4>23.9.2 \u914d\u7f6e\u793a\u4f8b<\/h4>\n<p>nginx<\/p>\n<p># Nginx\u8bf7\u6c42\u4f53\u5927\u5c0f\u9650\u5236<br \/>\nhttp {<br \/>\n    # \u5168\u5c40\u8bf7\u6c42\u4f53\u5927\u5c0f\u9650\u5236<br \/>\n    client_max_body_size 10m;<\/p>\n<p>    # \u7279\u5b9a\u4f4d\u7f6e\u66f4\u5927\u9650\u5236<br \/>\n    location \/api\/upload {<br \/>\n        client_max_body_size 100m;<\/p>\n<p>        # \u5927\u6587\u4ef6\u4e0a\u4f20\u8d85\u65f6\u8bbe\u7f6e<br \/>\n        client_body_timeout 300s;<br \/>\n        client_header_timeout 300s;<\/p>\n<p>        # \u7f13\u51b2\u533a\u8bbe\u7f6e<br \/>\n        client_body_buffer_size 128k;<br \/>\n        client_body_temp_path \/tmp\/nginx_upload;<br \/>\n    }<\/p>\n<p>    # \u7279\u5b9a\u4f4d\u7f6e\u66f4\u5c0f\u9650\u5236<br \/>\n    location \/api\/json {<br \/>\n        client_max_body_size 1m;<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.9.3 \u9519\u8bef\u5904\u7406<\/h4>\n<p>http<\/p>\n<p>HTTP\/1.1 413 Payload Too Large<br \/>\nContent-Type: application\/problem&#043;json<br \/>\nRetry-After: 3600<\/p>\n<p>{<br \/>\n  &#034;type&#034;: &#034;https:\/\/example.com\/errors\/payload-too-large&#034;,<br \/>\n  &#034;title&#034;: &#034;Request payload too large&#034;,<br \/>\n  &#034;status&#034;: 413,<br \/>\n  &#034;detail&#034;: &#034;Maximum request body size is 10MB&#034;,<br \/>\n  &#034;instance&#034;: &#034;\/api\/upload&#034;,<br \/>\n  &#034;max_size&#034;: 10485760,<br \/>\n  &#034;current_size&#034;: 15728640,<br \/>\n  &#034;suggestions&#034;: [<br \/>\n    &#034;Compress your data before uploading&#034;,<br \/>\n    &#034;Split the data into smaller chunks&#034;,<br \/>\n    &#034;Use our resumable upload API at \/api\/resumable-upload&#034;<br \/>\n  ]<br \/>\n}<\/p>\n<h4>23.9.4 \u5927\u6587\u4ef6\u4e0a\u4f20\u7b56\u7565<\/h4>\n<li>\n<p>\u5206\u5757\u4e0a\u4f20&#xff1a;\u5c06\u5927\u6587\u4ef6\u5206\u6210\u5c0f\u5757<\/p>\n<\/li>\n<li>\n<p>\u65ad\u70b9\u7eed\u4f20&#xff1a;\u652f\u6301\u4ece\u4e2d\u65ad\u5904\u7ee7\u7eed<\/p>\n<\/li>\n<li>\n<p>\u6d41\u5f0f\u4e0a\u4f20&#xff1a;\u8fb9\u4e0a\u4f20\u8fb9\u5904\u7406<\/p>\n<\/li>\n<li>\n<p>\u5916\u90e8\u5b58\u50a8&#xff1a;\u4e0a\u4f20\u5230S3\u7b49\u5bf9\u8c61\u5b58\u50a8<\/p>\n<\/li>\n<h4>23.9.5 \u5b9e\u73b0\u5206\u5757\u4e0a\u4f20<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5ba2\u6237\u7aef\u5206\u5757\u4e0a\u4f20\u5b9e\u73b0<br \/>\nclass ChunkedUploader {<br \/>\n    constructor(file, chunkSize &#061; 5 * 1024 * 1024) { \/\/ 5MB<br \/>\n        this.file &#061; file;<br \/>\n        this.chunkSize &#061; chunkSize;<br \/>\n        this.totalChunks &#061; Math.ceil(file.size \/ chunkSize);<br \/>\n        this.uploadedChunks &#061; new Set();<br \/>\n        this.uploadId &#061; null;<br \/>\n    }<\/p>\n<p>    async startUpload() {<br \/>\n        \/\/ \u521d\u59cb\u5316\u4e0a\u4f20\u4f1a\u8bdd<br \/>\n        const response &#061; await fetch(&#039;\/api\/upload\/init&#039;, {<br \/>\n            method: &#039;POST&#039;,<br \/>\n            headers: {&#039;Content-Type&#039;: &#039;application\/json&#039;},<br \/>\n            body: JSON.stringify({<br \/>\n                filename: this.file.name,<br \/>\n                file_size: this.file.size,<br \/>\n                chunk_size: this.chunkSize,<br \/>\n                total_chunks: this.totalChunks<br \/>\n            })<br \/>\n        });<\/p>\n<p>        const { upload_id } &#061; await response.json();<br \/>\n        this.uploadId &#061; upload_id;<\/p>\n<p>        \/\/ \u4e0a\u4f20\u6240\u6709\u5206\u5757<br \/>\n        for (let chunkIndex &#061; 0; chunkIndex &lt; this.totalChunks; chunkIndex&#043;&#043;) {<br \/>\n            await this.uploadChunk(chunkIndex);<br \/>\n        }<\/p>\n<p>        \/\/ \u5b8c\u6210\u4e0a\u4f20<br \/>\n        await this.completeUpload();<br \/>\n    }<\/p>\n<p>    async uploadChunk(chunkIndex) {<br \/>\n        if (this.uploadedChunks.has(chunkIndex)) {<br \/>\n            return; \/\/ \u5df2\u4e0a\u4f20<br \/>\n        }<\/p>\n<p>        const start &#061; chunkIndex * this.chunkSize;<br \/>\n        const end &#061; Math.min(start &#043; this.chunkSize, this.file.size);<br \/>\n        const chunk &#061; this.file.slice(start, end);<\/p>\n<p>        const formData &#061; new FormData();<br \/>\n        formData.append(&#039;chunk&#039;, chunk);<br \/>\n        formData.append(&#039;chunk_index&#039;, chunkIndex);<br \/>\n        formData.append(&#039;upload_id&#039;, this.uploadId);<\/p>\n<p>        await fetch(&#039;\/api\/upload\/chunk&#039;, {<br \/>\n            method: &#039;POST&#039;,<br \/>\n            body: formData<br \/>\n        });<\/p>\n<p>        this.uploadedChunks.add(chunkIndex);<br \/>\n    }<\/p>\n<p>    async completeUpload() {<br \/>\n        await fetch(&#039;\/api\/upload\/complete&#039;, {<br \/>\n            method: &#039;POST&#039;,<br \/>\n            headers: {&#039;Content-Type&#039;: &#039;application\/json&#039;},<br \/>\n            body: JSON.stringify({<br \/>\n                upload_id: this.uploadId,<br \/>\n                uploaded_chunks: Array.from(this.uploadedChunks)<br \/>\n            })<br \/>\n        });<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.9.6 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u63d0\u4f9b\u6e05\u6670\u7684\u9519\u8bef\u4fe1\u606f&#xff0c;\u5305\u62ec\u6700\u5927\u5141\u8bb8\u5927\u5c0f<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u5206\u5757\u4e0a\u4f20\u652f\u6301\u5927\u6587\u4ef6<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u4f7f\u7528CDN\u6216\u5bf9\u8c61\u5b58\u50a8\u5904\u7406\u5927\u6587\u4ef6<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7\u5927\u6587\u4ef6\u4e0a\u4f20\u9891\u7387\u548c\u5927\u5c0f\u5206\u5e03<\/p>\n<\/li>\n<\/ul>\n<h3>23.10 414 URI Too Long&#xff08;URI\u8fc7\u957f&#xff09;<\/h3>\n<h4>23.10.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>414\u72b6\u6001\u7801\u8868\u793a\u5ba2\u6237\u7aef\u8bf7\u6c42\u7684URI\u957f\u5ea6\u8d85\u8fc7\u670d\u52a1\u5668\u80fd\u591f\u5904\u7406\u7684\u9650\u5236\u3002\u867d\u7136HTTP\u89c4\u8303\u6ca1\u6709\u6307\u5b9a\u6700\u5927URI\u957f\u5ea6&#xff0c;\u4f46\u670d\u52a1\u5668\u5b9e\u73b0\u901a\u5e38\u6709\u9650\u5236\u3002<\/p>\n<h4>23.10.2 \u5404\u670d\u52a1\u5668\u9650\u5236<\/h4>\n<table>\n<tr>\u670d\u52a1\u5668\/\u7ec4\u4ef6\u9ed8\u8ba4\u9650\u5236\u914d\u7f6e\u9879<\/tr>\n<tbody>\n<tr>\n<td>Nginx<\/td>\n<td>\u53d6\u51b3\u4e8e\u7cfb\u7edf&#xff0c;\u901a\u5e388KB<\/td>\n<td>large_client_header_buffers<\/td>\n<\/tr>\n<tr>\n<td>Apache<\/td>\n<td>8KB<\/td>\n<td>LimitRequestLine<\/td>\n<\/tr>\n<tr>\n<td>IIS<\/td>\n<td>16KB<\/td>\n<td>maxUrl\u00a0in web.config<\/td>\n<\/tr>\n<tr>\n<td>Node.js<\/td>\n<td>\u53d6\u51b3\u4e8e\u5b9e\u73b0<\/td>\n<td>\u53ef\u914d\u7f6e<\/td>\n<\/tr>\n<tr>\n<td>\u6d4f\u89c8\u5668<\/td>\n<td>\u7ea62000\u5b57\u7b26<\/td>\n<td>\u65e0\u6807\u51c6<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>23.10.3 \u5e38\u89c1\u539f\u56e0<\/h4>\n<li>\n<p>\u8fc7\u5ea6\u4f7f\u7528\u67e5\u8be2\u53c2\u6570&#xff1a;GET\u8bf7\u6c42\u643a\u5e26\u5927\u91cf\u53c2\u6570<\/p>\n<\/li>\n<li>\n<p>\u6df1\u5ea6\u5d4c\u5957\u8def\u5f84&#xff1a;RESTful API\u7684\u6df1\u5ea6\u8d44\u6e90\u5f15\u7528<\/p>\n<\/li>\n<li>\n<p>Base64\u7f16\u7801\u6570\u636e&#xff1a;\u5728URL\u4e2d\u5d4c\u5165\u6570\u636e<\/p>\n<\/li>\n<li>\n<p>\u9519\u8bef\u7684\u94fe\u63a5\u751f\u6210&#xff1a;\u65e0\u9650\u5faa\u73af\u751f\u6210\u53c2\u6570<\/p>\n<\/li>\n<h4>23.10.4 \u89e3\u51b3\u65b9\u6848<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5c06\u957fGET\u8bf7\u6c42\u8f6c\u6362\u4e3aPOST<br \/>\nfunction convertLongGetToPost(url) {<br \/>\n    const urlObj &#061; new URL(url);<\/p>\n<p>    \/\/ \u5982\u679cURL\u8d85\u8fc7\u5b89\u5168\u957f\u5ea6&#xff0c;\u8f6c\u6362\u4e3aPOST<br \/>\n    if (url.length &gt; 2000) {<br \/>\n        const params &#061; {};<br \/>\n        urlObj.searchParams.forEach((value, key) &#061;&gt; {<br \/>\n            params[key] &#061; value;<br \/>\n        });<\/p>\n<p>        return {<br \/>\n            method: &#039;POST&#039;,<br \/>\n            url: urlObj.pathname,<br \/>\n            body: JSON.stringify(params),<br \/>\n            headers: {<br \/>\n                &#039;Content-Type&#039;: &#039;application\/json&#039;,<br \/>\n                &#039;X-Original-Method&#039;: &#039;GET&#039;<br \/>\n            }<br \/>\n        };<br \/>\n    }<\/p>\n<p>    return { method: &#039;GET&#039;, url };<br \/>\n}<\/p>\n<p>\/\/ \u670d\u52a1\u5668\u7aef\u5904\u7406<br \/>\napp.post(&#039;\/api\/data&#039;, (req, res) &#061;&gt; {<br \/>\n    \/\/ \u68c0\u67e5\u662f\u5426\u4e3aGET\u8f6c\u6362\u7684POST\u8bf7\u6c42<br \/>\n    if (req.get(&#039;X-Original-Method&#039;) &#061;&#061;&#061; &#039;GET&#039;) {<br \/>\n        \/\/ \u6309\u7167GET\u8bed\u4e49\u5904\u7406&#xff0c;\u4f46\u4f7f\u7528POST\u4e3b\u4f53<br \/>\n        const params &#061; req.body;<br \/>\n        \/\/ \u5904\u7406\u903b\u8f91&#8230;<br \/>\n    }<br \/>\n});<\/p>\n<h4>23.10.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u5bf9\u4e8e\u590d\u6742\u67e5\u8be2&#xff0c;\u4f7f\u7528POST\u66ff\u4ee3GET<\/p>\n<\/li>\n<li>\n<p>\u907f\u514d\u5728URL\u4e2d\u5d4c\u5165\u5927\u5757\u6570\u636e<\/p>\n<\/li>\n<li>\n<p>\u4f7f\u7528\u5206\u9875\u548c\u8fc7\u6ee4\u51cf\u5c11\u6570\u636e\u91cf<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7URI\u957f\u5ea6\u5f02\u5e38<\/p>\n<\/li>\n<\/ul>\n<h3>23.11 415 Unsupported Media Type&#xff08;\u4e0d\u652f\u6301\u7684\u5a92\u4f53\u7c7b\u578b&#xff09;<\/h3>\n<h4>23.11.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>415\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u4e0d\u652f\u6301\u8bf7\u6c42\u7684\u5a92\u4f53\u7c7b\u578b\u3002\u901a\u5e38\u53d1\u751f\u5728\u8bf7\u6c42\u7684Content-Type\u4e0d\u88ab\u670d\u52a1\u5668\u63a5\u53d7\u65f6\u3002<\/p>\n<h4>23.11.2 \u5e38\u89c1\u5a92\u4f53\u7c7b\u578b<\/h4>\n<table>\n<tr>\u7c7b\u578b\u63cf\u8ff0\u4f7f\u7528\u573a\u666f<\/tr>\n<tbody>\n<tr>\n<td>application\/json<\/td>\n<td>JSON\u6570\u636e<\/td>\n<td>REST API<\/td>\n<\/tr>\n<tr>\n<td>application\/xml<\/td>\n<td>XML\u6570\u636e<\/td>\n<td>SOAP API<\/td>\n<\/tr>\n<tr>\n<td>multipart\/form-data<\/td>\n<td>\u8868\u5355\u6570\u636e<\/td>\n<td>\u6587\u4ef6\u4e0a\u4f20<\/td>\n<\/tr>\n<tr>\n<td>application\/x-www-form-urlencoded<\/td>\n<td>URL\u7f16\u7801\u8868\u5355<\/td>\n<td>\u7b80\u5355\u8868\u5355<\/td>\n<\/tr>\n<tr>\n<td>text\/plain<\/td>\n<td>\u7eaf\u6587\u672c<\/td>\n<td>\u7b80\u5355\u6570\u636e<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>23.11.3 \u5b9e\u73b0\u793a\u4f8b<\/h4>\n<p>python<\/p>\n<p># FastAPI\u5a92\u4f53\u7c7b\u578b\u9a8c\u8bc1<br \/>\nfrom fastapi import FastAPI, UploadFile, File, HTTPException<br \/>\nfrom fastapi.responses import JSONResponse<\/p>\n<p>app &#061; FastAPI()<\/p>\n<p># \u5141\u8bb8\u7684\u5a92\u4f53\u7c7b\u578b<br \/>\nALLOWED_MEDIA_TYPES &#061; {<br \/>\n    &#039;application\/json&#039;,<br \/>\n    &#039;application\/xml&#039;,<br \/>\n    &#039;text\/plain&#039;,<br \/>\n    &#039;multipart\/form-data&#039;<br \/>\n}<\/p>\n<p>&#064;app.middleware(&#034;http&#034;)<br \/>\nasync def check_media_type(request, call_next):<br \/>\n    # \u68c0\u67e5\u8bf7\u6c42\u65b9\u6cd5\u662f\u5426\u9700\u8981body<br \/>\n    if request.method in [&#039;POST&#039;, &#039;PUT&#039;, &#039;PATCH&#039;]:<br \/>\n        content_type &#061; request.headers.get(&#039;content-type&#039;, &#039;&#039;)<\/p>\n<p>        # \u63d0\u53d6\u4e3b\u5a92\u4f53\u7c7b\u578b<br \/>\n        main_type &#061; content_type.split(&#039;;&#039;)[0].strip() if content_type else &#039;&#039;<\/p>\n<p>        # \u5982\u679c\u63d0\u4f9b\u4e86Content-Type\u4f46\u4e0d\u88ab\u652f\u6301<br \/>\n        if content_type and main_type not in ALLOWED_MEDIA_TYPES:<br \/>\n            return JSONResponse(<br \/>\n                status_code&#061;415,<br \/>\n                content&#061;{<br \/>\n                    &#034;error&#034;: &#034;unsupported_media_type&#034;,<br \/>\n                    &#034;message&#034;: f&#034;Media type &#039;{main_type}&#039; is not supported&#034;,<br \/>\n                    &#034;supported_types&#034;: list(ALLOWED_MEDIA_TYPES),<br \/>\n                    &#034;received_type&#034;: content_type<br \/>\n                }<br \/>\n            )<\/p>\n<p>    response &#061; await call_next(request)<br \/>\n    return response<\/p>\n<p>&#064;app.post(&#034;\/api\/data&#034;)<br \/>\nasync def create_data(data: dict):<br \/>\n    return {&#034;id&#034;: 123, **data}<\/p>\n<h4>23.11.4 \u5185\u5bb9\u7c7b\u578b\u534f\u5546<\/h4>\n<p>http<\/p>\n<p># \u5ba2\u6237\u7aef\u8bf7\u6c42<br \/>\nPOST \/api\/resource HTTP\/1.1<br \/>\nHost: api.example.com<br \/>\nContent-Type: application\/json<br \/>\nAccept: application\/json, application\/xml;q&#061;0.9, text\/plain;q&#061;0.5<\/p>\n<p>{&#034;name&#034;: &#034;test&#034;}<\/p>\n<p># \u670d\u52a1\u5668\u54cd\u5e94<br \/>\nHTTP\/1.1 200 OK<br \/>\nContent-Type: application\/json<br \/>\nVary: Accept<\/p>\n<p>{&#034;id&#034;: 1, &#034;name&#034;: &#034;test&#034;}<\/p>\n<h4>23.11.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u5728415\u54cd\u5e94\u4e2d\u5217\u51fa\u652f\u6301\u7684\u5a92\u4f53\u7c7b\u578b<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u9ed8\u8ba4\u5a92\u4f53\u7c7b\u578b\u4f5c\u4e3a\u540e\u5907<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u4e25\u683c\u6a21\u5f0f\u7528\u4e8e\u751f\u4ea7&#xff0c;\u5bbd\u677e\u6a21\u5f0f\u7528\u4e8e\u5f00\u53d1<\/p>\n<\/li>\n<li>\n<p>\u8bb0\u5f55API\u671f\u671b\u7684\u5a92\u4f53\u7c7b\u578b<\/p>\n<\/li>\n<\/ul>\n<h3>23.12 416 Range Not Satisfiable&#xff08;\u8303\u56f4\u8bf7\u6c42\u65e0\u6cd5\u6ee1\u8db3&#xff09;<\/h3>\n<h4>23.12.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>416\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u65e0\u6cd5\u63d0\u4f9b\u8bf7\u6c42\u7684Range\u5934\u6307\u5b9a\u7684\u5b57\u8282\u8303\u56f4\u3002\u901a\u5e38\u53d1\u751f\u5728\u8bf7\u6c42\u7684\u8303\u56f4\u8d85\u51fa\u8d44\u6e90\u5927\u5c0f\u65f6\u3002<\/p>\n<h4>23.12.2 \u8303\u56f4\u8bf7\u6c42\u673a\u5236<\/h4>\n<p>http<\/p>\n<p># \u8bf7\u6c42\u7279\u5b9a\u8303\u56f4<br \/>\nGET \/large-file.pdf HTTP\/1.1<br \/>\nHost: example.com<br \/>\nRange: bytes&#061;0-999<\/p>\n<p># \u6210\u529f\u54cd\u5e94<br \/>\nHTTP\/1.1 206 Partial Content<br \/>\nContent-Type: application\/pdf<br \/>\nContent-Range: bytes 0-999\/5000<br \/>\nContent-Length: 1000<\/p>\n<p>[\u4e8c\u8fdb\u5236\u6570\u636e]<\/p>\n<p># \u65e0\u6548\u8303\u56f4\u8bf7\u6c42<br \/>\nGET \/large-file.pdf HTTP\/1.1<br \/>\nHost: example.com<br \/>\nRange: bytes&#061;5000-6000<\/p>\n<p># 416\u54cd\u5e94<br \/>\nHTTP\/1.1 416 Range Not Satisfiable<br \/>\nContent-Range: bytes *\/5000<\/p>\n<h4>23.12.3 \u65ad\u70b9\u7eed\u4f20\u5b9e\u73b0<\/h4>\n<p>python<\/p>\n<p># Flask\u65ad\u70b9\u7eed\u4f20\u5b9e\u73b0<br \/>\nfrom flask import Flask, request, send_file, Response<br \/>\nimport os<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p>&#064;app.route(&#039;\/download\/&lt;filename&gt;&#039;)<br \/>\ndef download_file(filename):<br \/>\n    filepath &#061; os.path.join(&#039;uploads&#039;, filename)<\/p>\n<p>    if not os.path.exists(filepath):<br \/>\n        return {&#039;error&#039;: &#039;file_not_found&#039;}, 404<\/p>\n<p>    file_size &#061; os.path.getsize(filepath)<\/p>\n<p>    # \u68c0\u67e5Range\u5934<br \/>\n    range_header &#061; request.headers.get(&#039;Range&#039;)<\/p>\n<p>    if range_header:<br \/>\n        # \u89e3\u6790\u8303\u56f4\u8bf7\u6c42<br \/>\n        try:<br \/>\n            byte1, byte2 &#061; 0, None<\/p>\n<p>            # \u683c\u5f0f&#xff1a;bytes&#061;start-end<br \/>\n            range_ &#061; range_header.replace(&#039;bytes&#061;&#039;, &#039;&#039;).split(&#039;-&#039;)<br \/>\n            byte1 &#061; int(range_[0]) if range_[0] else 0<br \/>\n            if range_[1]:<br \/>\n                byte2 &#061; int(range_[1])<\/p>\n<p>            if byte2 is None:<br \/>\n                byte2 &#061; file_size &#8211; 1<br \/>\n            elif byte2 &gt;&#061; file_size:<br \/>\n                # \u8303\u56f4\u8d85\u51fa\u6587\u4ef6\u5927\u5c0f<br \/>\n                return Response(<br \/>\n                    status&#061;416,<br \/>\n                    headers&#061;{<br \/>\n                        &#039;Content-Range&#039;: f&#039;bytes *\/{file_size}&#039;,<br \/>\n                        &#039;Accept-Ranges&#039;: &#039;bytes&#039;<br \/>\n                    }<br \/>\n                )<\/p>\n<p>            length &#061; byte2 &#8211; byte1 &#043; 1<\/p>\n<p>            # \u53d1\u9001\u90e8\u5206\u5185\u5bb9<br \/>\n            def generate():<br \/>\n                with open(filepath, &#039;rb&#039;) as f:<br \/>\n                    f.seek(byte1)<br \/>\n                    remaining &#061; length<br \/>\n                    chunk_size &#061; 8192<\/p>\n<p>                    while remaining &gt; 0:<br \/>\n                        chunk &#061; f.read(min(chunk_size, remaining))<br \/>\n                        if not chunk:<br \/>\n                            break<br \/>\n                        remaining -&#061; len(chunk)<br \/>\n                        yield chunk<\/p>\n<p>            response &#061; Response(generate(), 206)<br \/>\n            response.headers.add(&#039;Content-Range&#039;, f&#039;bytes {byte1}-{byte2}\/{file_size}&#039;)<br \/>\n            response.headers.add(&#039;Accept-Ranges&#039;, &#039;bytes&#039;)<br \/>\n            response.headers.add(&#039;Content-Length&#039;, str(length))<br \/>\n            response.headers.add(&#039;Content-Type&#039;, &#039;application\/octet-stream&#039;)<\/p>\n<p>            return response<\/p>\n<p>        except ValueError:<br \/>\n            # \u65e0\u6548\u7684Range\u5934\u683c\u5f0f<br \/>\n            pass<\/p>\n<p>    # \u5b8c\u6574\u6587\u4ef6\u4e0b\u8f7d<br \/>\n    return send_file(filepath, as_attachment&#061;True)<\/p>\n<h4>23.12.4 \u5ba2\u6237\u7aef\u5b9e\u73b0<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u652f\u6301\u65ad\u70b9\u7eed\u4f20\u7684\u4e0b\u8f7d\u5668<br \/>\nclass ResumableDownloader {<br \/>\n    constructor(url, filename) {<br \/>\n        this.url &#061; url;<br \/>\n        this.filename &#061; filename;<br \/>\n        this.downloadedBytes &#061; 0;<br \/>\n        this.totalBytes &#061; 0;<br \/>\n        this.isPaused &#061; false;<br \/>\n    }<\/p>\n<p>    async start() {<br \/>\n        try {<br \/>\n            \/\/ \u5c1d\u8bd5\u83b7\u53d6\u6587\u4ef6\u4fe1\u606f<br \/>\n            const headResponse &#061; await fetch(this.url, { method: &#039;HEAD&#039; });<\/p>\n<p>            if (!headResponse.ok) {<br \/>\n                throw new Error(&#096;Failed to get file info: ${headResponse.status}&#096;);<br \/>\n            }<\/p>\n<p>            this.totalBytes &#061; parseInt(headResponse.headers.get(&#039;Content-Length&#039;)) || 0;<br \/>\n            const acceptRanges &#061; headResponse.headers.get(&#039;Accept-Ranges&#039;) &#061;&#061;&#061; &#039;bytes&#039;;<\/p>\n<p>            \/\/ \u68c0\u67e5\u672c\u5730\u5df2\u4e0b\u8f7d\u90e8\u5206<br \/>\n            const downloaded &#061; await this.getDownloadedSize();<br \/>\n            this.downloadedBytes &#061; downloaded;<\/p>\n<p>            if (acceptRanges &amp;&amp; downloaded &gt; 0 &amp;&amp; downloaded &lt; this.totalBytes) {<br \/>\n                \/\/ \u65ad\u70b9\u7eed\u4f20<br \/>\n                await this.resumeDownload();<br \/>\n            } else {<br \/>\n                \/\/ \u5168\u65b0\u4e0b\u8f7d<br \/>\n                await this.startNewDownload();<br \/>\n            }<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Download failed:&#039;, error);<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    async resumeDownload() {<br \/>\n        const headers &#061; {<br \/>\n            &#039;Range&#039;: &#096;bytes&#061;${this.downloadedBytes}-&#096;<br \/>\n        };<\/p>\n<p>        const response &#061; await fetch(this.url, { headers });<\/p>\n<p>        if (response.status &#061;&#061;&#061; 416) {<br \/>\n            \/\/ \u8303\u56f4\u65e0\u6548&#xff0c;\u91cd\u65b0\u5f00\u59cb<br \/>\n            this.downloadedBytes &#061; 0;<br \/>\n            await this.startNewDownload();<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        if (response.status !&#061;&#061; 206) {<br \/>\n            throw new Error(&#096;Unexpected status: ${response.status}&#096;);<br \/>\n        }<\/p>\n<p>        await this.processResponse(response);<br \/>\n    }<\/p>\n<p>    async startNewDownload() {<br \/>\n        const response &#061; await fetch(this.url);<\/p>\n<p>        if (!response.ok) {<br \/>\n            throw new Error(&#096;Download failed: ${response.status}&#096;);<br \/>\n        }<\/p>\n<p>        this.downloadedBytes &#061; 0;<br \/>\n        await this.processResponse(response);<br \/>\n    }<\/p>\n<p>    async processResponse(response) {<br \/>\n        const reader &#061; response.body.getReader();<br \/>\n        const fileStream &#061; await this.getFileStream();<\/p>\n<p>        while (true) {<br \/>\n            if (this.isPaused) {<br \/>\n                await new Promise(resolve &#061;&gt; {<br \/>\n                    this.resumeCallback &#061; resolve;<br \/>\n                });<br \/>\n            }<\/p>\n<p>            const { done, value } &#061; await reader.read();<\/p>\n<p>            if (done) {<br \/>\n                fileStream.close();<br \/>\n                console.log(&#039;Download completed&#039;);<br \/>\n                return;<br \/>\n            }<\/p>\n<p>            await fileStream.write(value);<br \/>\n            this.downloadedBytes &#043;&#061; value.length;<\/p>\n<p>            \/\/ \u66f4\u65b0\u8fdb\u5ea6<br \/>\n            const progress &#061; this.totalBytes &gt; 0<br \/>\n                ? (this.downloadedBytes \/ this.totalBytes * 100).toFixed(2)<br \/>\n                : &#039;unknown&#039;;<\/p>\n<p>            console.log(&#096;Progress: ${progress}%&#096;);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    pause() {<br \/>\n        this.isPaused &#061; true;<br \/>\n    }<\/p>\n<p>    resume() {<br \/>\n        this.isPaused &#061; false;<br \/>\n        if (this.resumeCallback) {<br \/>\n            this.resumeCallback();<br \/>\n            this.resumeCallback &#061; null;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    async getDownloadedSize() {<br \/>\n        \/\/ \u4ece\u672c\u5730\u5b58\u50a8\u83b7\u53d6\u5df2\u4e0b\u8f7d\u5927\u5c0f<br \/>\n        \/\/ \u5b9e\u73b0\u53d6\u51b3\u4e8e\u5b58\u50a8\u65b9\u5f0f&#xff08;IndexedDB\u3001\u6587\u4ef6\u7cfb\u7edfAPI\u7b49&#xff09;<br \/>\n        return 0;<br \/>\n    }<\/p>\n<p>    async getFileStream() {<br \/>\n        \/\/ \u83b7\u53d6\u6587\u4ef6\u5199\u5165\u6d41<br \/>\n        \/\/ \u5b9e\u73b0\u53d6\u51b3\u4e8e\u5b58\u50a8\u65b9\u5f0f<br \/>\n        return {<br \/>\n            write: async () &#061;&gt; {},<br \/>\n            close: () &#061;&gt; {}<br \/>\n        };<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.12.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u59cb\u7ec8\u5728\u54cd\u5e94\u4e2d\u5305\u542bAccept-Ranges\u5934<\/p>\n<\/li>\n<li>\n<p>\u5bf9\u4e8e416\u54cd\u5e94&#xff0c;\u5305\u542bContent-Range\u5934\u663e\u793a\u6709\u6548\u8303\u56f4<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u5b8c\u6574\u7684\u5206\u5757\u4e0b\u8f7d\u652f\u6301<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u652f\u6301\u591a\u8303\u56f4\u8bf7\u6c42&#xff08;multipart\/byteranges&#xff09;<\/p>\n<\/li>\n<\/ul>\n<h3>23.13 417 Expectation Failed&#xff08;\u671f\u671b\u5931\u8d25&#xff09;<\/h3>\n<h4>23.13.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>417\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u65e0\u6cd5\u6ee1\u8db3\u8bf7\u6c42\u7684Expect\u5934\u4e2d\u7684\u671f\u671b\u3002\u6700\u5e38\u89c1\u7684\u573a\u666f\u662fExpect: 100-continue\u3002<\/p>\n<h4>23.13.2 100 Continue\u673a\u5236<\/h4>\n<p>http<\/p>\n<p># \u5ba2\u6237\u7aef\u8bf7\u6c42&#xff08;\u4f7f\u7528100-continue&#xff09;<br \/>\nPOST \/upload HTTP\/1.1<br \/>\nHost: example.com<br \/>\nContent-Type: application\/json<br \/>\nContent-Length: 1024<br \/>\nExpect: 100-continue<\/p>\n<p># \u670d\u52a1\u5668\u54cd\u5e94<br \/>\n# \u60c5\u51b51&#xff1a;\u63a5\u53d7\u8bf7\u6c42<br \/>\nHTTP\/1.1 100 Continue<\/p>\n<p># \u5ba2\u6237\u7aef\u53d1\u9001\u8bf7\u6c42\u4f53<br \/>\n{&#034;data&#034;: &#034;&#8230;&#034;}<\/p>\n<p># \u670d\u52a1\u5668\u6700\u7ec8\u54cd\u5e94<br \/>\nHTTP\/1.1 201 Created<\/p>\n<p># \u60c5\u51b52&#xff1a;\u62d2\u7edd\u8bf7\u6c42<br \/>\nHTTP\/1.1 417 Expectation Failed<br \/>\nContent-Type: application\/json<\/p>\n<p>{<br \/>\n  &#034;error&#034;: &#034;expectation_failed&#034;,<br \/>\n  &#034;message&#034;: &#034;Server cannot accept the request&#034;,<br \/>\n  &#034;reason&#034;: &#034;File size limit exceeded&#034;<br \/>\n}<\/p>\n<h4>23.13.3 \u4f7f\u7528\u573a\u666f<\/h4>\n<li>\n<p>\u5927\u6587\u4ef6\u4e0a\u4f20\u9a8c\u8bc1&#xff1a;\u68c0\u67e5\u6743\u9650\u548c\u914d\u989d\u540e\u518d\u63a5\u6536\u6570\u636e<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u65f6\u5904\u7406\u9a8c\u8bc1&#xff1a;\u786e\u4fdd\u670d\u52a1\u5668\u80fd\u5904\u7406\u8bf7\u6c42<\/p>\n<\/li>\n<li>\n<p>\u6761\u4ef6\u8bf7\u6c42&#xff1a;\u57fa\u4e8e\u7279\u5b9a\u6761\u4ef6\u63a5\u53d7\u8bf7\u6c42<\/p>\n<\/li>\n<li>\n<p>\u8d44\u6e90\u9884\u7559&#xff1a;\u68c0\u67e5\u8d44\u6e90\u53ef\u7528\u6027<\/p>\n<\/li>\n<h4>23.13.4 \u5b9e\u73b0\u793a\u4f8b<\/h4>\n<p>python<\/p>\n<p># FastAPI Expect\u5934\u5904\u7406<br \/>\nfrom fastapi import FastAPI, Request, HTTPException<br \/>\nfrom fastapi.responses import JSONResponse<\/p>\n<p>app &#061; FastAPI()<\/p>\n<p>&#064;app.middleware(&#034;http&#034;)<br \/>\nasync def handle_expect_header(request: Request, call_next):<br \/>\n    expect_header &#061; request.headers.get(&#034;expect&#034;, &#034;&#034;).lower()<\/p>\n<p>    if expect_header &#061;&#061; &#034;100-continue&#034;:<br \/>\n        # \u6267\u884c\u9884\u68c0\u67e5<br \/>\n        content_length &#061; request.headers.get(&#034;content-length&#034;)<\/p>\n<p>        if content_length and int(content_length) &gt; 10 * 1024 * 1024:  # 10MB<br \/>\n            # \u8bf7\u6c42\u4f53\u592a\u5927&#xff0c;\u62d2\u7edd<br \/>\n            return JSONResponse(<br \/>\n                status_code&#061;417,<br \/>\n                content&#061;{<br \/>\n                    &#034;error&#034;: &#034;expectation_failed&#034;,<br \/>\n                    &#034;message&#034;: &#034;File size exceeds limit&#034;,<br \/>\n                    &#034;max_size&#034;: 10 * 1024 * 1024,<br \/>\n                    &#034;requested_size&#034;: int(content_length)<br \/>\n                }<br \/>\n            )<\/p>\n<p>        # \u68c0\u67e5\u8ba4\u8bc1<br \/>\n        auth_header &#061; request.headers.get(&#034;authorization&#034;)<br \/>\n        if not auth_header or not validate_token(auth_header):<br \/>\n            return JSONResponse(<br \/>\n                status_code&#061;417,<br \/>\n                content&#061;{<br \/>\n                    &#034;error&#034;: &#034;expectation_failed&#034;,<br \/>\n                    &#034;message&#034;: &#034;Authentication required&#034;<br \/>\n                }<br \/>\n            )<\/p>\n<p>        # \u53d1\u9001100 Continue\u54cd\u5e94<br \/>\n        from starlette.responses import Response<br \/>\n        response &#061; Response(status_code&#061;100)<br \/>\n        await response(scope&#061;request.scope, receive&#061;request.receive, send&#061;request.send)<\/p>\n<p>    # \u7ee7\u7eed\u6b63\u5e38\u5904\u7406<br \/>\n    response &#061; await call_next(request)<br \/>\n    return response<\/p>\n<p>def validate_token(token: str) -&gt; bool:<br \/>\n    # \u5b9e\u73b0\u4ee4\u724c\u9a8c\u8bc1<br \/>\n    return token.startswith(&#034;Bearer &#034;)<\/p>\n<h4>23.13.5 \u5ba2\u6237\u7aef\u5b9e\u73b0<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u4f7f\u7528Expect: 100-continue\u7684\u5ba2\u6237\u7aef<br \/>\nasync function uploadWithContinue(file, url) {<br \/>\n    return new Promise((resolve, reject) &#061;&gt; {<br \/>\n        const xhr &#061; new XMLHttpRequest();<\/p>\n<p>        xhr.open(&#039;POST&#039;, url, true);<\/p>\n<p>        \/\/ \u8bbe\u7f6eExpect\u5934<br \/>\n        xhr.setRequestHeader(&#039;Expect&#039;, &#039;100-continue&#039;);<br \/>\n        xhr.setRequestHeader(&#039;Content-Type&#039;, &#039;application\/octet-stream&#039;);<\/p>\n<p>        \/\/ \u76d1\u542c100-continue\u54cd\u5e94<br \/>\n        xhr.onreadystatechange &#061; function() {<br \/>\n            if (xhr.readyState &#061;&#061;&#061; XMLHttpRequest.HEADERS_RECEIVED) {<br \/>\n                \/\/ \u68c0\u67e5\u72b6\u6001\u7801<br \/>\n                if (xhr.status &#061;&#061;&#061; 100) {<br \/>\n                    console.log(&#039;Server ready to receive data&#039;);<br \/>\n                } else if (xhr.status &#061;&#061;&#061; 417) {<br \/>\n                    reject(new Error(&#039;Server rejected the request&#039;));<br \/>\n                }<br \/>\n            }<br \/>\n        };<\/p>\n<p>        xhr.onload &#061; function() {<br \/>\n            if (xhr.status &gt;&#061; 200 &amp;&amp; xhr.status &lt; 300) {<br \/>\n                resolve(xhr.response);<br \/>\n            } else {<br \/>\n                reject(new Error(&#096;Upload failed: ${xhr.status}&#096;));<br \/>\n            }<br \/>\n        };<\/p>\n<p>        xhr.onerror &#061; function() {<br \/>\n            reject(new Error(&#039;Network error&#039;));<br \/>\n        };<\/p>\n<p>        xhr.send(file);<br \/>\n    });<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528Fetch API&#xff08;\u81ea\u52a8\u5904\u7406100-continue&#xff09;<br \/>\nasync function uploadWithFetch(file, url) {<br \/>\n    const response &#061; await fetch(url, {<br \/>\n        method: &#039;POST&#039;,<br \/>\n        headers: {<br \/>\n            &#039;Content-Type&#039;: &#039;application\/octet-stream&#039;,<br \/>\n            \/\/ Fetch API\u4f1a\u81ea\u52a8\u5904\u7406Expect\u5934<br \/>\n        },<br \/>\n        body: file<br \/>\n    });<\/p>\n<p>    if (!response.ok) {<br \/>\n        throw new Error(&#096;Upload failed: ${response.status}&#096;);<br \/>\n    }<\/p>\n<p>    return response.json();<br \/>\n}<\/p>\n<h4>23.13.6 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u5bf9\u4e8e\u5927\u6587\u4ef6\u4e0a\u4f20\u5b9e\u73b0100-continue\u652f\u6301<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u6e05\u6670\u7684\u62d2\u7edd\u539f\u56e0<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u8d85\u65f6\u5904\u7406&#xff08;\u5ba2\u6237\u7aef\u7b49\u5f85100\u54cd\u5e94\u7684\u8d85\u65f6&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7417\u9519\u8bef\u9891\u7387\u4ee5\u8bc6\u522b\u5ba2\u6237\u7aef\u95ee\u9898<\/p>\n<\/li>\n<\/ul>\n<h3>23.14 418 I&#039;m a Teapot&#xff08;\u6211\u662f\u8336\u58f6&#xff09;<\/h3>\n<h4>23.14.1 \u8d77\u6e90\u4e0e\u5b9a\u4e49<\/h4>\n<p>418\u72b6\u6001\u7801\u6700\u521d\u662f1998\u5e74\u611a\u4eba\u8282\u7684RFC 2324&#xff08;\u8d85\u6587\u672c\u5496\u5561\u58f6\u63a7\u5236\u534f\u8bae&#xff09;\u7684\u4e00\u90e8\u5206&#xff0c;\u7528\u4e8e\u8868\u793a\u670d\u52a1\u5668\u662f\u4e00\u4e2a\u8336\u58f6&#xff0c;\u65e0\u6cd5\u716e\u5496\u5561\u3002\u867d\u7136\u6700\u521d\u662f\u73a9\u7b11&#xff0c;\u4f46\u5df2\u88ab\u8bb8\u591a\u670d\u52a1\u5668\u5b9e\u73b0\u5e76\u7528\u4e8e\u8868\u793a&#034;\u8fd9\u4e2a\u7aef\u70b9\u6c38\u8fdc\u4e0d\u4f1a\u5de5\u4f5c&#034;\u3002<\/p>\n<h4>23.14.2 \u73b0\u4ee3\u7528\u9014<\/h4>\n<li>\n<p>API\u7aef\u70b9\u5360\u4f4d\u7b26&#xff1a;\u8868\u793a\u5c1a\u672a\u5b9e\u73b0\u7684\u7aef\u70b9<\/p>\n<\/li>\n<li>\n<p>\u5185\u90e8\u73a9\u7b11&#xff1a;\u5f00\u53d1\u56e2\u961f\u95f4\u7684\u5e7d\u9ed8<\/p>\n<\/li>\n<li>\n<p>\u5b89\u5168\u63aa\u65bd&#xff1a;\u6df7\u6dc6\u653b\u51fb\u8005<\/p>\n<\/li>\n<li>\n<p>\u590d\u6d3b\u8282\u5f69\u86cb&#xff1a;\u9690\u85cf\u529f\u80fd\u6216\u73a9\u7b11\u54cd\u5e94<\/p>\n<\/li>\n<h4>23.14.3 \u5b9e\u73b0\u793a\u4f8b<\/h4>\n<p>python<\/p>\n<p># Flask 418\u5b9e\u73b0<br \/>\nfrom flask import Flask, jsonify<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p>&#064;app.route(&#039;\/brew-coffee&#039;)<br \/>\ndef brew_coffee():<br \/>\n    &#034;&#034;&#034;HTCPCP\u7aef\u70b9 &#8211; \u603b\u662f\u8fd4\u56de418&#034;&#034;&#034;<br \/>\n    response &#061; jsonify({<br \/>\n        &#034;error&#034;: &#034;I&#039;m a teapot&#034;,<br \/>\n        &#034;message&#034;: &#034;The requested entity body is short and stout.&#034;,<br \/>\n        &#034;hint&#034;: &#034;Tip me over and pour me out.&#034;,<br \/>\n        &#034;rfc&#034;: &#034;https:\/\/tools.ietf.org\/html\/rfc2324&#034;,<br \/>\n        &#034;solution&#034;: &#034;Try making tea instead.&#034;<br \/>\n    })<br \/>\n    response.status_code &#061; 418<br \/>\n    response.headers[&#039;X-Teapot-Type&#039;] &#061; &#039;Robust Brown Betty&#039;<br \/>\n    response.headers[&#039;X-Brewing-Time&#039;] &#061; &#039;4 minutes&#039;<br \/>\n    return response<\/p>\n<p>&#064;app.route(&#039;\/make-tea&#039;)<br \/>\ndef make_tea():<br \/>\n    &#034;&#034;&#034;\u6b63\u786e\u7684HTCPCP\u7aef\u70b9&#034;&#034;&#034;<br \/>\n    return jsonify({<br \/>\n        &#034;status&#034;: &#034;brewing&#034;,<br \/>\n        &#034;type&#034;: &#034;Earl Grey&#034;,<br \/>\n        &#034;strength&#034;: &#034;medium&#034;,<br \/>\n        &#034;message&#034;: &#034;Your tea will be ready shortly.&#034;<br \/>\n    })<\/p>\n<h4>23.14.4 \u521b\u610f\u4f7f\u7528<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u521b\u610f418\u9875\u9762<br \/>\nfunction createTeapotResponse() {<br \/>\n    return {<br \/>\n        status: 418,<br \/>\n        headers: {<br \/>\n            &#039;Content-Type&#039;: &#039;application\/json&#039;,<br \/>\n            &#039;X-Teapot&#039;: &#039;true&#039;,<br \/>\n            &#039;X-Brew-Advice&#039;: &#039;Use freshly boiled water at 95\u00b0C&#039;,<br \/>\n            &#039;X-Tea-Type&#039;: &#039;Oolong&#039;<br \/>\n        },<br \/>\n        body: JSON.stringify({<br \/>\n            error: &#034;I&#039;m a teapot&#034;,<br \/>\n            message: &#034;This server is a teapot, not a coffee pot.&#034;,<br \/>\n            instructions: {<br \/>\n                step1: &#034;Fill me with fresh water&#034;,<br \/>\n                step2: &#034;Heat to 95\u00b0C (203\u00b0F)&#034;,<br \/>\n                step3: &#034;Add 1 teaspoon of tea leaves per cup&#034;,<br \/>\n                step4: &#034;Steep for 3-5 minutes&#034;,<br \/>\n                step5: &#034;Pour and enjoy&#034;<br \/>\n            },<br \/>\n            tea_suggestions: [<br \/>\n                {<br \/>\n                    name: &#034;Green Tea&#034;,<br \/>\n                    temperature: &#034;80\u00b0C&#034;,<br \/>\n                    time: &#034;2-3 minutes&#034;<br \/>\n                },<br \/>\n                {<br \/>\n                    name: &#034;Black Tea&#034;,<br \/>\n                    temperature: &#034;95\u00b0C&#034;,<br \/>\n                    time: &#034;3-5 minutes&#034;<br \/>\n                },<br \/>\n                {<br \/>\n                    name: &#034;Herbal Tea&#034;,<br \/>\n                    temperature: &#034;100\u00b0C&#034;,<br \/>\n                    time: &#034;5-7 minutes&#034;<br \/>\n                }<br \/>\n            ],<br \/>\n            fun_fact: &#034;The 418 status code was defined in RFC 2324 (Hyper Text Coffee Pot Control Protocol) as an April Fools&#039; joke in 1998.&#034;<br \/>\n        }, null, 2)<br \/>\n    };<br \/>\n}<\/p>\n<p>\/\/ Express\u4e2d\u95f4\u4ef6\u68c0\u6d4b418\u8bf7\u6c42<br \/>\napp.use((req, res, next) &#061;&gt; {<br \/>\n    \/\/ \u68c0\u67e5\u662f\u5426\u4e3a\u5496\u5561\u76f8\u5173\u8bf7\u6c42<br \/>\n    if (req.path.includes(&#039;coffee&#039;) ||<br \/>\n        req.get(&#039;X-Beverage&#039;) &#061;&#061;&#061; &#039;coffee&#039;) {<\/p>\n<p>        const teapotResponse &#061; createTeapotResponse();<\/p>\n<p>        res.status(teapotResponse.status);<br \/>\n        Object.entries(teapotResponse.headers)<br \/>\n            .forEach(([key, value]) &#061;&gt; res.set(key, value));<br \/>\n        res.send(teapotResponse.body);<br \/>\n        return;<br \/>\n    }<\/p>\n<p>    next();<br \/>\n});<\/p>\n<h4>23.14.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u8c28\u614e\u4f7f\u7528418&#xff0c;\u907f\u514d\u6df7\u6dc6\u771f\u6b63\u7684\u9519\u8bef<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u5728\u5f00\u53d1\u73af\u5883\u4f7f\u7528&#xff0c;\u751f\u4ea7\u73af\u5883\u7981\u7528<\/p>\n<\/li>\n<li>\n<p>\u53ef\u4ee5\u7528\u4e8eAPI\u7248\u672c\u63a7\u5236&#xff08;\u65e7\u7248\u672c\u8fd4\u56de418&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u786e\u4fdd\u4e0d\u4f1a\u5f71\u54cdSEO\u6216\u722c\u866b<\/p>\n<\/li>\n<\/ul>\n<h3>23.15 421 Misdirected Request&#xff08;\u9519\u8bef\u5b9a\u5411\u8bf7\u6c42&#xff09;<\/h3>\n<h4>23.15.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>421\u72b6\u6001\u7801\u5728HTTP\/2\u4e2d\u5f15\u5165&#xff0c;\u8868\u793a\u8bf7\u6c42\u88ab\u53d1\u9001\u5230\u65e0\u6cd5\u751f\u6210\u54cd\u5e94\u7684\u670d\u52a1\u5668\u3002\u901a\u5e38\u53d1\u751f\u5728\u8fde\u63a5\u91cd\u7528\u548c\u670d\u52a1\u5668\u540d\u79f0\u6307\u793a&#xff08;SNI&#xff09;\u4e0d\u5339\u914d\u65f6\u3002<\/p>\n<h4>23.15.2 HTTP\/2\u8fde\u63a5\u91cd\u7528<\/h4>\n<p>HTTP\/2\u5141\u8bb8\u5728\u5355\u4e2a\u8fde\u63a5\u4e0a\u591a\u8def\u590d\u7528\u591a\u4e2a\u8bf7\u6c42\u3002\u4f46\u5f53\u5ba2\u6237\u7aef\u5c1d\u8bd5\u5728\u8fde\u63a5\u4e0a\u53d1\u9001\u9488\u5bf9\u4e0d\u540c\u4e3b\u673a\u7684\u8bf7\u6c42\u65f6&#xff0c;\u670d\u52a1\u5668\u53ef\u80fd\u8fd4\u56de421\u3002<\/p>\n<h4>23.15.3 SNI&#xff08;\u670d\u52a1\u5668\u540d\u79f0\u6307\u793a&#xff09;<\/h4>\n<p>SNI\u662fTLS\u6269\u5c55&#xff0c;\u5141\u8bb8\u5ba2\u6237\u7aef\u5728\u63e1\u624b\u65f6\u6307\u5b9a\u8981\u8fde\u63a5\u7684\u4e3b\u673a\u540d\u3002\u8fd9\u5bf9\u4e8e\u865a\u62df\u4e3b\u673a\u6258\u7ba1\u81f3\u5173\u91cd\u8981\u3002<\/p>\n<h4>23.15.4 \u95ee\u9898\u573a\u666f<\/h4>\n<p>text<\/p>\n<p>\u5ba2\u6237\u7aef \u2192 \u670d\u52a1\u5668: TLS\u63e1\u624b&#xff08;SNI: api.example.com&#xff09;<br \/>\n\u5ba2\u6237\u7aef \u2192 \u670d\u52a1\u5668: \u8bf7\u6c421: GET \/users (Host: api.example.com)<br \/>\n\u670d\u52a1\u5668 \u2192 \u5ba2\u6237\u7aef: 200 OK<\/p>\n<p>\u5ba2\u6237\u7aef \u2192 \u670d\u52a1\u5668: \u8bf7\u6c422: GET \/orders (Host: shop.example.com)<br \/>\n\u670d\u52a1\u5668 \u2192 \u5ba2\u6237\u7aef: 421 Misdirected Request<br \/>\n                  (\u56e0\u4e3a\u8fde\u63a5\u662f\u4e3aapi.example.com\u5efa\u7acb\u7684)<\/p>\n<h4>23.15.5 \u89e3\u51b3\u65b9\u6848<\/h4>\n<p>nginx<\/p>\n<p># Nginx\u914d\u7f6e\u5904\u7406\u591a\u4e2a\u4e3b\u673a\u540d<br \/>\nserver {<br \/>\n    listen 443 ssl http2;<br \/>\n    server_name api.example.com;<br \/>\n    ssl_certificate \/certs\/api.example.com.crt;<br \/>\n    ssl_certificate_key \/certs\/api.example.com.key;<\/p>\n<p>    location \/ {<br \/>\n        proxy_pass http:\/\/api_backend;<br \/>\n    }<br \/>\n}<\/p>\n<p>server {<br \/>\n    listen 443 ssl http2;<br \/>\n    server_name shop.example.com;<br \/>\n    ssl_certificate \/certs\/shop.example.com.crt;<br \/>\n    ssl_certificate_key \/certs\/shop.example.com.key;<\/p>\n<p>    location \/ {<br \/>\n        proxy_pass http:\/\/shop_backend;<br \/>\n    }<br \/>\n}<\/p>\n<p># \u6216\u8005\u4f7f\u7528\u901a\u914d\u7b26\u8bc1\u4e66<br \/>\nserver {<br \/>\n    listen 443 ssl http2;<br \/>\n    server_name api.example.com shop.example.com;<br \/>\n    ssl_certificate \/certs\/wildcard.example.com.crt;<br \/>\n    ssl_certificate_key \/certs\/wildcard.example.com.key;<\/p>\n<p>    # \u57fa\u4e8eHost\u5934\u8def\u7531<br \/>\n    location \/ {<br \/>\n        if ($host &#061; &#034;api.example.com&#034;) {<br \/>\n            proxy_pass http:\/\/api_backend;<br \/>\n        }<br \/>\n        if ($host &#061; &#034;shop.example.com&#034;) {<br \/>\n            proxy_pass http:\/\/shop_backend;<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.15.6 \u5ba2\u6237\u7aef\u5904\u7406<\/h4>\n<p>javascript<\/p>\n<p>\/\/ HTTP\/2\u5ba2\u6237\u7aef\u8fde\u63a5\u7ba1\u7406<br \/>\nclass H2ConnectionManager {<br \/>\n    constructor() {<br \/>\n        this.connections &#061; new Map(); \/\/ host -&gt; connection<br \/>\n        this.pendingRequests &#061; new Map(); \/\/ host -&gt; [requests]<br \/>\n    }<\/p>\n<p>    async request(host, path, options &#061; {}) {<br \/>\n        \/\/ \u68c0\u67e5\u662f\u5426\u6709\u73b0\u6709\u8fde\u63a5<br \/>\n        let connection &#061; this.connections.get(host);<\/p>\n<p>        if (!connection) {<br \/>\n            \/\/ \u521b\u5efa\u65b0\u8fde\u63a5<br \/>\n            connection &#061; await this.createConnection(host);<br \/>\n            this.connections.set(host, connection);<\/p>\n<p>            \/\/ \u76d1\u542c\u8fde\u63a5\u9519\u8bef<br \/>\n            connection.on(&#039;error&#039;, (error) &#061;&gt; {<br \/>\n                console.error(&#096;Connection to ${host} failed:&#096;, error);<br \/>\n                this.connections.delete(host);<br \/>\n            });<\/p>\n<p>            \/\/ \u76d1\u542c421\u9519\u8bef<br \/>\n            connection.on(&#039;stream&#039;, (stream) &#061;&gt; {<br \/>\n                stream.on(&#039;response&#039;, (headers) &#061;&gt; {<br \/>\n                    if (headers[&#039;:status&#039;] &#061;&#061;&#061; &#039;421&#039;) {<br \/>\n                        console.log(&#096;Received 421 for ${host}, closing connection&#096;);<br \/>\n                        connection.close();<br \/>\n                        this.connections.delete(host);<\/p>\n<p>                        \/\/ \u91cd\u8bd5\u5f85\u5904\u7406\u8bf7\u6c42<br \/>\n                        this.retryPendingRequests(host);<br \/>\n                    }<br \/>\n                });<br \/>\n            });<br \/>\n        }<\/p>\n<p>        \/\/ \u53d1\u9001\u8bf7\u6c42<br \/>\n        return new Promise((resolve, reject) &#061;&gt; {<br \/>\n            const stream &#061; connection.request({<br \/>\n                &#039;:method&#039;: options.method || &#039;GET&#039;,<br \/>\n                &#039;:path&#039;: path,<br \/>\n                &#039;:authority&#039;: host,<br \/>\n                &#8230;options.headers<br \/>\n            });<\/p>\n<p>            stream.on(&#039;response&#039;, (headers) &#061;&gt; {<br \/>\n                const chunks &#061; [];<\/p>\n<p>                stream.on(&#039;data&#039;, (chunk) &#061;&gt; {<br \/>\n                    chunks.push(chunk);<br \/>\n                });<\/p>\n<p>                stream.on(&#039;end&#039;, () &#061;&gt; {<br \/>\n                    const response &#061; {<br \/>\n                        status: headers[&#039;:status&#039;],<br \/>\n                        headers,<br \/>\n                        body: Buffer.concat(chunks)<br \/>\n                    };<br \/>\n                    resolve(response);<br \/>\n                });<\/p>\n<p>                stream.on(&#039;error&#039;, reject);<br \/>\n            });<\/p>\n<p>            if (options.body) {<br \/>\n                stream.end(options.body);<br \/>\n            } else {<br \/>\n                stream.end();<br \/>\n            }<br \/>\n        });<br \/>\n    }<\/p>\n<p>    async createConnection(host) {<br \/>\n        \/\/ \u5b9e\u73b0HTTP\/2\u8fde\u63a5\u521b\u5efa<br \/>\n        \/\/ \u6ce8\u610f&#xff1a;\u9700\u8981\u9002\u5f53\u7684HTTP\/2\u5ba2\u6237\u7aef\u5e93<br \/>\n        throw new Error(&#039;Not implemented&#039;);<br \/>\n    }<\/p>\n<p>    retryPendingRequests(host) {<br \/>\n        const pending &#061; this.pendingRequests.get(host) || [];<br \/>\n        this.pendingRequests.delete(host);<\/p>\n<p>        pending.forEach(request &#061;&gt; {<br \/>\n            this.request(host, request.path, request.options)<br \/>\n                .then(request.resolve)<br \/>\n                .catch(request.reject);<br \/>\n        });<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.15.7 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u4e3a\u4e0d\u540c\u4e3b\u673a\u540d\u4f7f\u7528\u901a\u914d\u7b26\u6216\u591a\u57df\u540d\u8bc1\u4e66<\/p>\n<\/li>\n<li>\n<p>\u5ba2\u6237\u7aef\u6b63\u786e\u7ba1\u7406HTTP\/2\u8fde\u63a5\u6c60<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7421\u9519\u8bef\u4ee5\u8bc6\u522b\u914d\u7f6e\u95ee\u9898<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u4f7f\u7528HTTP\/1.1\u4f5c\u4e3a\u5907\u7528\u65b9\u6848<\/p>\n<\/li>\n<\/ul>\n<h3>23.16 422 Unprocessable Entity&#xff08;\u65e0\u6cd5\u5904\u7406\u7684\u5b9e\u4f53&#xff09;<\/h3>\n<h4>23.16.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>422\u72b6\u6001\u7801\u6765\u81eaWebDAV\u89c4\u8303&#xff0c;\u8868\u793a\u670d\u52a1\u5668\u7406\u89e3\u8bf7\u6c42\u5b9e\u4f53\u7684\u5185\u5bb9\u7c7b\u578b&#xff0c;\u4e14\u8bed\u6cd5\u6b63\u786e&#xff0c;\u4f46\u65e0\u6cd5\u5904\u7406\u5176\u4e2d\u5305\u542b\u7684\u6307\u4ee4\u3002\u5e38\u7528\u4e8e\u9a8c\u8bc1\u9519\u8bef\u3002<\/p>\n<h4>23.16.2 \u4e0e400\u7684\u533a\u522b<\/h4>\n<ul>\n<li>\n<p>400 Bad Request&#xff1a;\u8bf7\u6c42\u8bed\u6cd5\u9519\u8bef\u6216\u65e0\u6cd5\u89e3\u6790<\/p>\n<\/li>\n<li>\n<p>422 Unprocessable Entity&#xff1a;\u8bf7\u6c42\u8bed\u6cd5\u6b63\u786e&#xff0c;\u4f46\u8bed\u4e49\u9519\u8bef<\/p>\n<\/li>\n<\/ul>\n<h4>23.16.3 \u9a8c\u8bc1\u9519\u8bef\u793a\u4f8b<\/h4>\n<p>json<\/p>\n<p>\/\/ \u8bf7\u6c42<br \/>\nPOST \/api\/users HTTP\/1.1<br \/>\nContent-Type: application\/json<\/p>\n<p>{<br \/>\n  &#034;email&#034;: &#034;invalid-email&#034;,<br \/>\n  &#034;age&#034;: -5,<br \/>\n  &#034;password&#034;: &#034;123&#034;<br \/>\n}<\/p>\n<p>\/\/ \u54cd\u5e94<br \/>\nHTTP\/1.1 422 Unprocessable Entity<br \/>\nContent-Type: application\/problem&#043;json<\/p>\n<p>{<br \/>\n  &#034;type&#034;: &#034;https:\/\/example.com\/errors\/validation&#034;,<br \/>\n  &#034;title&#034;: &#034;Validation Failed&#034;,<br \/>\n  &#034;status&#034;: 422,<br \/>\n  &#034;detail&#034;: &#034;The request contains validation errors&#034;,<br \/>\n  &#034;instance&#034;: &#034;\/api\/users&#034;,<br \/>\n  &#034;errors&#034;: [<br \/>\n    {<br \/>\n      &#034;field&#034;: &#034;email&#034;,<br \/>\n      &#034;code&#034;: &#034;invalid_format&#034;,<br \/>\n      &#034;message&#034;: &#034;Must be a valid email address&#034;,<br \/>\n      &#034;value&#034;: &#034;invalid-email&#034;<br \/>\n    },<br \/>\n    {<br \/>\n      &#034;field&#034;: &#034;age&#034;,<br \/>\n      &#034;code&#034;: &#034;minimum&#034;,<br \/>\n      &#034;message&#034;: &#034;Must be greater than 0&#034;,<br \/>\n      &#034;value&#034;: -5,<br \/>\n      &#034;constraint&#034;: 0<br \/>\n    },<br \/>\n    {<br \/>\n      &#034;field&#034;: &#034;password&#034;,<br \/>\n      &#034;code&#034;: &#034;min_length&#034;,<br \/>\n      &#034;message&#034;: &#034;Must be at least 8 characters&#034;,<br \/>\n      &#034;value&#034;: &#034;123&#034;,<br \/>\n      &#034;constraint&#034;: 8<br \/>\n    }<br \/>\n  ]<br \/>\n}<\/p>\n<h4>23.16.4 \u5b9e\u73b0\u6a21\u5f0f<\/h4>\n<p>python<\/p>\n<p># FastAPI\u9a8c\u8bc1\u9519\u8bef\u5904\u7406<br \/>\nfrom fastapi import FastAPI, HTTPException, Request<br \/>\nfrom fastapi.responses import JSONResponse<br \/>\nfrom pydantic import BaseModel, EmailStr, validator<br \/>\nfrom typing import List<\/p>\n<p>app &#061; FastAPI()<\/p>\n<p>class UserCreate(BaseModel):<br \/>\n    email: EmailStr<br \/>\n    age: int<br \/>\n    password: str<\/p>\n<p>    &#064;validator(&#039;age&#039;)<br \/>\n    def age_must_be_positive(cls, v):<br \/>\n        if v &lt;&#061; 0:<br \/>\n            raise ValueError(&#039;age must be positive&#039;)<br \/>\n        return v<\/p>\n<p>    &#064;validator(&#039;password&#039;)<br \/>\n    def password_length(cls, v):<br \/>\n        if len(v) &lt; 8:<br \/>\n            raise ValueError(&#039;password must be at least 8 characters&#039;)<br \/>\n        return v<\/p>\n<p>&#064;app.exception_handler(ValueError)<br \/>\nasync def validation_exception_handler(request: Request, exc: ValueError):<br \/>\n    # \u5c06\u9a8c\u8bc1\u9519\u8bef\u8f6c\u6362\u4e3a422\u54cd\u5e94<br \/>\n    return JSONResponse(<br \/>\n        status_code&#061;422,<br \/>\n        content&#061;{<br \/>\n            &#034;detail&#034;: str(exc),<br \/>\n            &#034;errors&#034;: [<br \/>\n                {<br \/>\n                    &#034;loc&#034;: [&#034;body&#034;],<br \/>\n                    &#034;msg&#034;: str(exc),<br \/>\n                    &#034;type&#034;: &#034;value_error&#034;<br \/>\n                }<br \/>\n            ]<br \/>\n        }<br \/>\n    )<\/p>\n<p>&#064;app.post(&#034;\/users\/&#034;)<br \/>\nasync def create_user(user: UserCreate):<br \/>\n    # \u5982\u679c\u9a8c\u8bc1\u901a\u8fc7&#xff0c;\u521b\u5efa\u7528\u6237<br \/>\n    return {&#034;message&#034;: &#034;User created&#034;, &#034;user&#034;: user.dict()}<\/p>\n<p># \u66f4\u7cbe\u7ec6\u7684\u9519\u8bef\u5904\u7406<br \/>\nfrom pydantic import ValidationError<\/p>\n<p>&#064;app.exception_handler(ValidationError)<br \/>\nasync def pydantic_validation_handler(request: Request, exc: ValidationError):<br \/>\n    errors &#061; []<\/p>\n<p>    for error in exc.errors():<br \/>\n        errors.append({<br \/>\n            &#034;field&#034;: &#034;.&#034;.join(str(loc) for loc in error[&#034;loc&#034;]),<br \/>\n            &#034;code&#034;: error[&#034;type&#034;],<br \/>\n            &#034;message&#034;: error[&#034;msg&#034;],<br \/>\n            &#034;context&#034;: error.get(&#034;ctx&#034;)<br \/>\n        })<\/p>\n<p>    return JSONResponse(<br \/>\n        status_code&#061;422,<br \/>\n        content&#061;{<br \/>\n            &#034;error&#034;: &#034;validation_failed&#034;,<br \/>\n            &#034;message&#034;: &#034;The request contains validation errors&#034;,<br \/>\n            &#034;errors&#034;: errors<br \/>\n        }<br \/>\n    )<\/p>\n<h4>23.16.5 \u524d\u7aef\u96c6\u6210<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u524d\u7aef\u8868\u5355\u9a8c\u8bc1\u548c422\u9519\u8bef\u5904\u7406<br \/>\nclass FormValidator {<br \/>\n    constructor(formId) {<br \/>\n        this.form &#061; document.getElementById(formId);<br \/>\n        this.errorsContainer &#061; document.createElement(&#039;div&#039;);<br \/>\n        this.errorsContainer.className &#061; &#039;validation-errors&#039;;<br \/>\n        this.form.parentNode.insertBefore(this.errorsContainer, this.form);<\/p>\n<p>        this.form.addEventListener(&#039;submit&#039;, this.handleSubmit.bind(this));<br \/>\n    }<\/p>\n<p>    async handleSubmit(event) {<br \/>\n        event.preventDefault();<\/p>\n<p>        \/\/ \u6e05\u9664\u4e4b\u524d\u7684\u9519\u8bef<br \/>\n        this.clearErrors();<\/p>\n<p>        \/\/ \u6536\u96c6\u8868\u5355\u6570\u636e<br \/>\n        const formData &#061; new FormData(this.form);<br \/>\n        const data &#061; Object.fromEntries(formData.entries());<\/p>\n<p>        try {<br \/>\n            const response &#061; await fetch(this.form.action, {<br \/>\n                method: this.form.method,<br \/>\n                headers: {<br \/>\n                    &#039;Content-Type&#039;: &#039;application\/json&#039;<br \/>\n                },<br \/>\n                body: JSON.stringify(data)<br \/>\n            });<\/p>\n<p>            if (response.status &#061;&#061;&#061; 422) {<br \/>\n                \/\/ \u5904\u7406\u9a8c\u8bc1\u9519\u8bef<br \/>\n                const result &#061; await response.json();<br \/>\n                this.displayErrors(result.errors);<br \/>\n                return;<br \/>\n            }<\/p>\n<p>            if (!response.ok) {<br \/>\n                throw new Error(&#096;Request failed: ${response.status}&#096;);<br \/>\n            }<\/p>\n<p>            \/\/ \u6210\u529f\u5904\u7406<br \/>\n            const result &#061; await response.json();<br \/>\n            this.onSuccess(result);<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;Form submission error:&#039;, error);<br \/>\n            this.displayGenericError(error.message);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    displayErrors(errors) {<br \/>\n        this.errorsContainer.innerHTML &#061; &#039;&#039;;<\/p>\n<p>        \/\/ \u6309\u5b57\u6bb5\u5206\u7ec4\u9519\u8bef<br \/>\n        const fieldErrors &#061; {};<\/p>\n<p>        errors.forEach(error &#061;&gt; {<br \/>\n            if (!fieldErrors[error.field]) {<br \/>\n                fieldErrors[error.field] &#061; [];<br \/>\n            }<br \/>\n            fieldErrors[error.field].push(error.message);<br \/>\n        });<\/p>\n<p>        \/\/ \u663e\u793a\u9519\u8bef<br \/>\n        Object.entries(fieldErrors).forEach(([field, messages]) &#061;&gt; {<br \/>\n            const fieldElement &#061; this.form.querySelector(&#096;[name&#061;&#034;${field}&#034;]&#096;);<br \/>\n            if (fieldElement) {<br \/>\n                \/\/ \u6dfb\u52a0\u9519\u8bef\u7c7b<br \/>\n                fieldElement.classList.add(&#039;error&#039;);<\/p>\n<p>                \/\/ \u521b\u5efa\u9519\u8bef\u6d88\u606f<br \/>\n                const errorElement &#061; document.createElement(&#039;div&#039;);<br \/>\n                errorElement.className &#061; &#039;field-error&#039;;<br \/>\n                errorElement.textContent &#061; messages.join(&#039;, &#039;);<\/p>\n<p>                fieldElement.parentNode.appendChild(errorElement);<br \/>\n            }<\/p>\n<p>            \/\/ \u6dfb\u52a0\u5230\u9519\u8bef\u5bb9\u5668<br \/>\n            const errorSummary &#061; document.createElement(&#039;div&#039;);<br \/>\n            errorSummary.className &#061; &#039;error-summary&#039;;<br \/>\n            errorSummary.innerHTML &#061; &#096;<br \/>\n                &lt;strong&gt;${field}:&lt;\/strong&gt;<br \/>\n                &lt;ul&gt;<br \/>\n                    ${messages.map(msg &#061;&gt; &#096;&lt;li&gt;${msg}&lt;\/li&gt;&#096;).join(&#039;&#039;)}<br \/>\n                &lt;\/ul&gt;<br \/>\n            &#096;;<br \/>\n            this.errorsContainer.appendChild(errorSummary);<br \/>\n        });<\/p>\n<p>        \/\/ \u6eda\u52a8\u5230\u9519\u8bef<br \/>\n        this.errorsContainer.scrollIntoView({ behavior: &#039;smooth&#039; });<br \/>\n    }<\/p>\n<p>    clearErrors() {<br \/>\n        \/\/ \u6e05\u9664\u5b57\u6bb5\u9519\u8bef\u6837\u5f0f<br \/>\n        this.form.querySelectorAll(&#039;.error&#039;).forEach(el &#061;&gt; {<br \/>\n            el.classList.remove(&#039;error&#039;);<br \/>\n        });<\/p>\n<p>        \/\/ \u79fb\u9664\u9519\u8bef\u6d88\u606f<br \/>\n        this.form.querySelectorAll(&#039;.field-error&#039;).forEach(el &#061;&gt; {<br \/>\n            el.remove();<br \/>\n        });<\/p>\n<p>        \/\/ \u6e05\u7a7a\u9519\u8bef\u5bb9\u5668<br \/>\n        this.errorsContainer.innerHTML &#061; &#039;&#039;;<br \/>\n    }<\/p>\n<p>    displayGenericError(message) {<br \/>\n        this.errorsContainer.innerHTML &#061; &#096;<br \/>\n            &lt;div class&#061;&#034;alert alert-danger&#034;&gt;<br \/>\n                &lt;strong&gt;Error:&lt;\/strong&gt; ${message}<br \/>\n            &lt;\/div&gt;<br \/>\n        &#096;;<br \/>\n    }<\/p>\n<p>    onSuccess(result) {<br \/>\n        \/\/ \u6210\u529f\u56de\u8c03<br \/>\n        console.log(&#039;Form submitted successfully:&#039;, result);<br \/>\n        this.form.reset();<br \/>\n        this.clearErrors();<\/p>\n<p>        alert(&#039;Form submitted successfully!&#039;);<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.16.6 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u63d0\u4f9b\u8be6\u7ec6\u7684\u9a8c\u8bc1\u9519\u8bef\u4fe1\u606f<\/p>\n<\/li>\n<li>\n<p>\u4f7f\u7528\u6807\u51c6\u9519\u8bef\u683c\u5f0f&#xff08;\u5982RFC 7807&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u5ba2\u6237\u7aef\u9a8c\u8bc1\u4ee5\u51cf\u5c11422\u8bf7\u6c42<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u4f7f\u7528JSON Schema\u8fdb\u884c\u9a8c\u8bc1<\/p>\n<\/li>\n<\/ul>\n<h3>23.17 423 Locked&#xff08;\u5df2\u9501\u5b9a&#xff09;<\/h3>\n<h4>23.17.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>423\u72b6\u6001\u7801\u6765\u81eaWebDAV\u89c4\u8303&#xff0c;\u8868\u793a\u8bf7\u6c42\u7684\u8d44\u6e90\u88ab\u9501\u5b9a\u3002\u5ba2\u6237\u7aef\u5e94\u7b49\u5f85\u9501\u91ca\u653e\u6216\u8054\u7cfb\u9501\u6301\u6709\u8005\u3002<\/p>\n<h4>23.17.2 \u9501\u7c7b\u578b<\/h4>\n<li>\n<p>\u72ec\u5360\u9501&#xff1a;\u53ea\u6709\u4e00\u4e2a\u5ba2\u6237\u7aef\u53ef\u4fee\u6539\u8d44\u6e90<\/p>\n<\/li>\n<li>\n<p>\u5171\u4eab\u9501&#xff1a;\u591a\u4e2a\u5ba2\u6237\u7aef\u53ef\u8bfb\u53d6&#xff0c;\u4f46\u53ea\u6709\u4e00\u4e2a\u53ef\u5199\u5165<\/p>\n<\/li>\n<li>\n<p>\u6df1\u5ea6\u9501&#xff1a;\u9501\u4f4f\u8d44\u6e90\u53ca\u5176\u6240\u6709\u5b50\u8d44\u6e90<\/p>\n<\/li>\n<h4>23.17.3 \u9501\u673a\u5236<\/h4>\n<p>http<\/p>\n<p># \u9501\u5b9a\u8bf7\u6c42<br \/>\nLOCK \/document.txt HTTP\/1.1<br \/>\nHost: example.com<br \/>\nTimeout: Infinite, Second-3600<br \/>\nDepth: infinity<br \/>\nContent-Type: application\/xml<\/p>\n<p>&lt;?xml version&#061;&#034;1.0&#034; encoding&#061;&#034;utf-8&#034; ?&gt;<br \/>\n&lt;D:lockinfo xmlns:D&#061;&#034;DAV:&#034;&gt;<br \/>\n    &lt;D:lockscope&gt;&lt;D:exclusive\/&gt;&lt;\/D:lockscope&gt;<br \/>\n    &lt;D:locktype&gt;&lt;D:write\/&gt;&lt;\/D:locktype&gt;<br \/>\n    &lt;D:owner&gt;<br \/>\n        &lt;D:href&gt;mailto:user&#064;example.com&lt;\/D:href&gt;<br \/>\n    &lt;\/D:owner&gt;<br \/>\n&lt;\/D:lockinfo&gt;<\/p>\n<p># \u9501\u5b9a\u54cd\u5e94<br \/>\nHTTP\/1.1 200 OK<br \/>\nContent-Type: application\/xml<\/p>\n<p>&lt;?xml version&#061;&#034;1.0&#034; encoding&#061;&#034;utf-8&#034; ?&gt;<br \/>\n&lt;D:prop xmlns:D&#061;&#034;DAV:&#034;&gt;<br \/>\n    &lt;D:lockdiscovery&gt;<br \/>\n        &lt;D:activelock&gt;<br \/>\n            &lt;D:locktype&gt;&lt;D:write\/&gt;&lt;\/D:locktype&gt;<br \/>\n            &lt;D:lockscope&gt;&lt;D:exclusive\/&gt;&lt;\/D:lockscope&gt;<br \/>\n            &lt;D:depth&gt;infinity&lt;\/D:depth&gt;<br \/>\n            &lt;D:owner&gt;<br \/>\n                &lt;D:href&gt;mailto:user&#064;example.com&lt;\/D:href&gt;<br \/>\n            &lt;\/D:owner&gt;<br \/>\n            &lt;D:locktoken&gt;<br \/>\n                &lt;D:href&gt;urn:uuid:e71d4fae-5dec-11d0-a765-00a0c91e6bf6&lt;\/D:href&gt;<br \/>\n            &lt;\/D:locktoken&gt;<br \/>\n            &lt;D:timeout&gt;Second-3600&lt;\/D:timeout&gt;<br \/>\n        &lt;\/D:activelock&gt;<br \/>\n    &lt;\/D:lockdiscovery&gt;<br \/>\n&lt;\/D:prop&gt;<\/p>\n<p># \u5c1d\u8bd5\u4fee\u6539\u5df2\u9501\u5b9a\u7684\u8d44\u6e90<br \/>\nPUT \/document.txt HTTP\/1.1<br \/>\nHost: example.com<br \/>\nIf: &lt;urn:uuid:e71d4fae-5dec-11d0-a765-00a0c91e6bf6&gt;<br \/>\nContent-Type: text\/plain<\/p>\n<p>New content<\/p>\n<p># \u54cd\u5e94&#xff08;\u5982\u679c\u6ca1\u6709\u6b63\u786e\u7684\u9501\u4ee4\u724c&#xff09;<br \/>\nHTTP\/1.1 423 Locked<br \/>\nContent-Type: application\/xml<\/p>\n<p>&lt;?xml version&#061;&#034;1.0&#034; encoding&#061;&#034;utf-8&#034; ?&gt;<br \/>\n&lt;D:error xmlns:D&#061;&#034;DAV:&#034;&gt;<br \/>\n    &lt;D:lock-token-submitted&gt;<br \/>\n        &lt;D:href&gt;\/document.txt&lt;\/D:href&gt;<br \/>\n    &lt;\/D:lock-token-submitted&gt;<br \/>\n&lt;\/D:error&gt;<\/p>\n<h4>23.17.4 \u73b0\u4ee3API\u5b9e\u73b0<\/h4>\n<p>python<\/p>\n<p># Flask\u8d44\u6e90\u9501\u5b9e\u73b0<br \/>\nfrom flask import Flask, request, jsonify<br \/>\nimport threading<br \/>\nimport time<br \/>\nimport uuid<br \/>\nfrom datetime import datetime, timedelta<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p>class ResourceLock:<br \/>\n    def __init__(self):<br \/>\n        self.locks &#061; {}<br \/>\n        self.lock &#061; threading.Lock()<\/p>\n<p>    def acquire(self, resource_id, client_id, timeout&#061;300):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u8d44\u6e90\u9501&#034;&#034;&#034;<br \/>\n        with self.lock:<br \/>\n            current_time &#061; datetime.now()<\/p>\n<p>            # \u68c0\u67e5\u662f\u5426\u5df2\u9501\u5b9a<br \/>\n            if resource_id in self.locks:<br \/>\n                lock_info &#061; self.locks[resource_id]<\/p>\n<p>                # \u68c0\u67e5\u9501\u662f\u5426\u8fc7\u671f<br \/>\n                if lock_info[&#039;expires&#039;] &lt; current_time:<br \/>\n                    # \u9501\u5df2\u8fc7\u671f&#xff0c;\u53ef\u4ee5\u83b7\u53d6<br \/>\n                    pass<br \/>\n                elif lock_info[&#039;client_id&#039;] &#061;&#061; client_id:<br \/>\n                    # \u540c\u4e00\u5ba2\u6237\u7aef&#xff0c;\u66f4\u65b0\u8d85\u65f6<br \/>\n                    lock_info[&#039;expires&#039;] &#061; current_time &#043; timedelta(seconds&#061;timeout)<br \/>\n                    return True<br \/>\n                else:<br \/>\n                    # \u8d44\u6e90\u88ab\u5176\u4ed6\u5ba2\u6237\u7aef\u9501\u5b9a<br \/>\n                    return False<\/p>\n<p>            # \u83b7\u53d6\u65b0\u9501<br \/>\n            lock_token &#061; str(uuid.uuid4())<br \/>\n            self.locks[resource_id] &#061; {<br \/>\n                &#039;client_id&#039;: client_id,<br \/>\n                &#039;token&#039;: lock_token,<br \/>\n                &#039;created&#039;: current_time,<br \/>\n                &#039;expires&#039;: current_time &#043; timedelta(seconds&#061;timeout),<br \/>\n                &#039;timeout&#039;: timeout<br \/>\n            }<\/p>\n<p>            return lock_token<\/p>\n<p>    def release(self, resource_id, client_id&#061;None, token&#061;None):<br \/>\n        &#034;&#034;&#034;\u91ca\u653e\u8d44\u6e90\u9501&#034;&#034;&#034;<br \/>\n        with self.lock:<br \/>\n            if resource_id not in self.locks:<br \/>\n                return True<\/p>\n<p>            lock_info &#061; self.locks[resource_id]<\/p>\n<p>            # \u68c0\u67e5\u6743\u9650<br \/>\n            if client_id and lock_info[&#039;client_id&#039;] !&#061; client_id:<br \/>\n                return False<\/p>\n<p>            if token and lock_info[&#039;token&#039;] !&#061; token:<br \/>\n                return False<\/p>\n<p>            # \u91ca\u653e\u9501<br \/>\n            del self.locks[resource_id]<br \/>\n            return True<\/p>\n<p>    def check(self, resource_id, token&#061;None):<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u8d44\u6e90\u662f\u5426\u88ab\u9501\u5b9a&#034;&#034;&#034;<br \/>\n        with self.lock:<br \/>\n            if resource_id not in self.locks:<br \/>\n                return True<\/p>\n<p>            lock_info &#061; self.locks[resource_id]<\/p>\n<p>            # \u68c0\u67e5\u662f\u5426\u8fc7\u671f<br \/>\n            if lock_info[&#039;expires&#039;] &lt; datetime.now():<br \/>\n                del self.locks[resource_id]<br \/>\n                return True<\/p>\n<p>            # \u68c0\u67e5\u4ee4\u724c<br \/>\n            if token and lock_info[&#039;token&#039;] &#061;&#061; token:<br \/>\n                return True<\/p>\n<p>            return False<\/p>\n<p># \u5168\u5c40\u9501\u7ba1\u7406\u5668<br \/>\nlock_manager &#061; ResourceLock()<\/p>\n<p>&#064;app.route(&#039;\/api\/documents\/&lt;document_id&gt;\/lock&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef lock_document(document_id):<br \/>\n    &#034;&#034;&#034;\u9501\u5b9a\u6587\u6863&#034;&#034;&#034;<br \/>\n    client_id &#061; request.headers.get(&#039;X-Client-ID&#039;)<br \/>\n    if not client_id:<br \/>\n        return jsonify({&#039;error&#039;: &#039;client_id_required&#039;}), 400<\/p>\n<p>    timeout &#061; request.json.get(&#039;timeout&#039;, 300)<\/p>\n<p>    # \u5c1d\u8bd5\u83b7\u53d6\u9501<br \/>\n    result &#061; lock_manager.acquire(document_id, client_id, timeout)<\/p>\n<p>    if result is True:<br \/>\n        # \u5df2\u6301\u6709\u9501<br \/>\n        return jsonify({&#039;status&#039;: &#039;already_locked&#039;}), 200<br \/>\n    elif result is False:<br \/>\n        # \u88ab\u5176\u4ed6\u5ba2\u6237\u7aef\u9501\u5b9a<br \/>\n        lock_info &#061; get_lock_info(document_id)<br \/>\n        return jsonify({<br \/>\n            &#039;error&#039;: &#039;resource_locked&#039;,<br \/>\n            &#039;message&#039;: &#039;Document is locked by another client&#039;,<br \/>\n            &#039;locked_by&#039;: lock_info[&#039;client_id&#039;],<br \/>\n            &#039;expires_at&#039;: lock_info[&#039;expires&#039;].isoformat()<br \/>\n        }), 423<br \/>\n    else:<br \/>\n        # \u6210\u529f\u83b7\u53d6\u9501<br \/>\n        return jsonify({<br \/>\n            &#039;lock_token&#039;: result,<br \/>\n            &#039;timeout&#039;: timeout,<br \/>\n            &#039;expires_in&#039;: timeout<br \/>\n        }), 200<\/p>\n<p>&#064;app.route(&#039;\/api\/documents\/&lt;document_id&gt;&#039;, methods&#061;[&#039;PUT&#039;])<br \/>\ndef update_document(document_id):<br \/>\n    &#034;&#034;&#034;\u66f4\u65b0\u6587\u6863&#xff08;\u9700\u8981\u9501&#xff09;&#034;&#034;&#034;<br \/>\n    lock_token &#061; request.headers.get(&#039;X-Lock-Token&#039;)<\/p>\n<p>    # \u68c0\u67e5\u9501<br \/>\n    if not lock_manager.check(document_id, lock_token):<br \/>\n        return jsonify({<br \/>\n            &#039;error&#039;: &#039;resource_locked&#039;,<br \/>\n            &#039;message&#039;: &#039;Document is locked. Provide valid lock token.&#039;,<br \/>\n            &#039;required_header&#039;: &#039;X-Lock-Token&#039;<br \/>\n        }), 423<\/p>\n<p>    # \u66f4\u65b0\u6587\u6863\u903b\u8f91<br \/>\n    # &#8230;<\/p>\n<p>    return jsonify({&#039;status&#039;: &#039;updated&#039;}), 200<\/p>\n<p>def get_lock_info(resource_id):<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u9501\u4fe1\u606f&#xff08;\u7b80\u5316\u5b9e\u73b0&#xff09;&#034;&#034;&#034;<br \/>\n    # \u5b9e\u9645\u5b9e\u73b0\u9700\u8981\u7ebf\u7a0b\u5b89\u5168\u5730\u8bbf\u95eelock_manager<br \/>\n    return {&#039;client_id&#039;: &#039;unknown&#039;, &#039;expires&#039;: datetime.now()}<\/p>\n<h4>23.17.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u5b9e\u73b0\u9501\u8d85\u65f6\u673a\u5236\u9632\u6b62\u6b7b\u9501<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u9501\u4ee4\u724c\u4ee5\u4fbf\u5ba2\u6237\u7aef\u540e\u7eed\u64cd\u4f5c<\/p>\n<\/li>\n<li>\n<p>\u652f\u6301\u9501\u67e5\u8be2\u548c\u91ca\u653e\u64cd\u4f5c<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u5206\u5e03\u5f0f\u9501\u65b9\u6848&#xff08;\u5982Redis&#xff09;<\/p>\n<\/li>\n<\/ul>\n<h3>23.18 424 Failed Dependency&#xff08;\u4f9d\u8d56\u5931\u8d25&#xff09;<\/h3>\n<h4>23.18.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>424\u72b6\u6001\u7801\u6765\u81eaWebDAV\u89c4\u8303&#xff0c;\u8868\u793a\u8bf7\u6c42\u7684\u64cd\u4f5c\u4f9d\u8d56\u4e8e\u53e6\u4e00\u4e2a\u64cd\u4f5c&#xff0c;\u4e14\u90a3\u4e2a\u64cd\u4f5c\u5931\u8d25\u3002\u7528\u4e8e\u4e8b\u52a1\u6027\u64cd\u4f5c\u6216\u591a\u8d44\u6e90\u64cd\u4f5c\u3002<\/p>\n<h4>23.18.2 \u4f7f\u7528\u573a\u666f<\/h4>\n<li>\n<p>\u6279\u91cf\u64cd\u4f5c&#xff1a;\u90e8\u5206\u64cd\u4f5c\u5931\u8d25\u5bfc\u81f4\u6574\u4f53\u5931\u8d25<\/p>\n<\/li>\n<li>\n<p>\u4e8b\u52a1\u5904\u7406&#xff1a;\u6570\u636e\u5e93\u4e8b\u52a1\u56de\u6eda<\/p>\n<\/li>\n<li>\n<p>\u6587\u4ef6\u64cd\u4f5c&#xff1a;\u79fb\u52a8\u5305\u542b\u591a\u4e2a\u6587\u4ef6\u7684\u6587\u4ef6\u5939<\/p>\n<\/li>\n<li>\n<p>API\u7ec4\u5408&#xff1a;\u8c03\u7528\u591a\u4e2a\u5fae\u670d\u52a1<\/p>\n<\/li>\n<h4>23.18.3 \u5b9e\u73b0\u793a\u4f8b<\/h4>\n<p>python<\/p>\n<p># Django\u4e8b\u52a1\u6027\u64cd\u4f5c<br \/>\nfrom django.db import transaction<br \/>\nfrom django.http import JsonResponse<br \/>\nfrom django.views import View<br \/>\nimport logging<\/p>\n<p>logger &#061; logging.getLogger(__name__)<\/p>\n<p>class BatchCreateView(View):<br \/>\n    &#064;transaction.atomic<br \/>\n    def post(self, request):<br \/>\n        data &#061; request.json<br \/>\n        created_resources &#061; []<br \/>\n        errors &#061; []<\/p>\n<p>        # \u521b\u5efa\u4fdd\u5b58\u70b9<br \/>\n        sid &#061; transaction.savepoint()<\/p>\n<p>        try:<br \/>\n            for item in data[&#039;items&#039;]:<br \/>\n                try:<br \/>\n                    # \u521b\u5efa\u8d44\u6e90<br \/>\n                    resource &#061; self.create_resource(item)<br \/>\n                    created_resources.append(resource)<\/p>\n<p>                except ValidationError as e:<br \/>\n                    # \u5355\u4e2a\u8d44\u6e90\u521b\u5efa\u5931\u8d25<br \/>\n                    errors.append({<br \/>\n                        &#039;item&#039;: item,<br \/>\n                        &#039;error&#039;: str(e),<br \/>\n                        &#039;field&#039;: getattr(e, &#039;field&#039;, None)<br \/>\n                    })<br \/>\n                    # \u56de\u6eda\u5230\u4fdd\u5b58\u70b9<br \/>\n                    transaction.savepoint_rollback(sid)<br \/>\n                    raise BatchOperationError(f&#034;Failed to create item: {item}&#034;)<\/p>\n<p>            # \u6240\u6709\u64cd\u4f5c\u6210\u529f&#xff0c;\u63d0\u4ea4\u4e8b\u52a1<br \/>\n            transaction.savepoint_commit(sid)<\/p>\n<p>            return JsonResponse({<br \/>\n                &#039;status&#039;: &#039;success&#039;,<br \/>\n                &#039;created&#039;: len(created_resources),<br \/>\n                &#039;resources&#039;: created_resources<br \/>\n            })<\/p>\n<p>        except BatchOperationError as e:<br \/>\n            # \u8fd4\u56de424\u54cd\u5e94<br \/>\n            return JsonResponse({<br \/>\n                &#039;error&#039;: &#039;failed_dependency&#039;,<br \/>\n                &#039;message&#039;: &#039;Batch operation failed due to dependency errors&#039;,<br \/>\n                &#039;details&#039;: errors,<br \/>\n                &#039;successful_count&#039;: len(created_resources),<br \/>\n                &#039;failed_count&#039;: len(errors)<br \/>\n            }, status&#061;424)<\/p>\n<p>        except Exception as e:<br \/>\n            # \u5176\u4ed6\u9519\u8bef<br \/>\n            logger.error(f&#034;Batch operation failed: {e}&#034;)<br \/>\n            return JsonResponse({<br \/>\n                &#039;error&#039;: &#039;internal_error&#039;,<br \/>\n                &#039;message&#039;: &#039;Internal server error&#039;<br \/>\n            }, status&#061;500)<\/p>\n<p>    def create_resource(self, item):<br \/>\n        # \u5b9e\u73b0\u8d44\u6e90\u521b\u5efa\u903b\u8f91<br \/>\n        # \u53ef\u80fd\u629b\u51faValidationError<br \/>\n        pass<\/p>\n<p>class BatchOperationError(Exception):<br \/>\n    pass<\/p>\n<h4>23.18.4 \u5fae\u670d\u52a1\u573a\u666f<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5fae\u670d\u52a1\u7f16\u6392\u4e2d\u7684\u4f9d\u8d56\u5931\u8d25\u5904\u7406<br \/>\nclass Orchestrator {<br \/>\n    async processOrder(order) {<br \/>\n        const steps &#061; [<br \/>\n            this.validateOrder.bind(this, order),<br \/>\n            this.reserveInventory.bind(this, order),<br \/>\n            this.processPayment.bind(this, order),<br \/>\n            this.shipOrder.bind(this, order),<br \/>\n            this.sendConfirmation.bind(this, order)<br \/>\n        ];<\/p>\n<p>        const results &#061; [];<br \/>\n        const errors &#061; [];<\/p>\n<p>        for (let i &#061; 0; i &lt; steps.length; i&#043;&#043;) {<br \/>\n            try {<br \/>\n                const result &#061; await steps[i]();<br \/>\n                results.push({<br \/>\n                    step: i &#043; 1,<br \/>\n                    success: true,<br \/>\n                    result<br \/>\n                });<br \/>\n            } catch (error) {<br \/>\n                errors.push({<br \/>\n                    step: i &#043; 1,<br \/>\n                    success: false,<br \/>\n                    error: error.message,<br \/>\n                    dependency_failed: i &gt; 0 ? i : null<br \/>\n                });<\/p>\n<p>                \/\/ \u5982\u679c\u4f9d\u8d56\u6b65\u9aa4\u5931\u8d25&#xff0c;\u540e\u7eed\u6b65\u9aa4\u65e0\u6cd5\u6267\u884c<br \/>\n                if (i &gt; 0) {<br \/>\n                    \/\/ \u8865\u507f\u5df2\u5b8c\u6210\u7684\u6b65\u9aa4<br \/>\n                    await this.compensate(results.slice(0, i));<\/p>\n<p>                    return {<br \/>\n                        status: &#039;failed&#039;,<br \/>\n                        error_type: &#039;dependency_failed&#039;,<br \/>\n                        status_code: 424,<br \/>\n                        completed_steps: results.length,<br \/>\n                        failed_step: i &#043; 1,<br \/>\n                        errors,<br \/>\n                        compensation_status: &#039;executed&#039;<br \/>\n                    };<br \/>\n                }<\/p>\n<p>                \/\/ \u7b2c\u4e00\u6b65\u5931\u8d25&#xff0c;\u76f4\u63a5\u8fd4\u56de\u9519\u8bef<br \/>\n                return {<br \/>\n                    status: &#039;failed&#039;,<br \/>\n                    error_type: &#039;initial_failure&#039;,<br \/>\n                    status_code: 400,<br \/>\n                    failed_step: 1,<br \/>\n                    error: error.message<br \/>\n                };<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return {<br \/>\n            status: &#039;success&#039;,<br \/>\n            results<br \/>\n        };<br \/>\n    }<\/p>\n<p>    async compensate(completedSteps) {<br \/>\n        \/\/ \u6267\u884c\u8865\u507f\u64cd\u4f5c<br \/>\n        for (const step of completedSteps.reverse()) {<br \/>\n            try {<br \/>\n                await this.reverseStep(step);<br \/>\n            } catch (error) {<br \/>\n                console.error(&#096;Compensation failed for step ${step.step}:&#096;, error);<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    async reverseStep(step) {<br \/>\n        \/\/ \u5b9e\u73b0\u6b65\u9aa4\u56de\u6eda\u903b\u8f91<br \/>\n        switch (step.step) {<br \/>\n            case 2: \/\/ \u5e93\u5b58\u9884\u7559<br \/>\n                await this.releaseInventory(step.result.inventory_id);<br \/>\n                break;<br \/>\n            case 3: \/\/ \u652f\u4ed8\u5904\u7406<br \/>\n                await this.refundPayment(step.result.payment_id);<br \/>\n                break;<br \/>\n            \/\/ \u5176\u4ed6\u6b65\u9aa4\u7684\u56de\u6eda&#8230;<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.18.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u5b9e\u73b0\u539f\u5b50\u64cd\u4f5c\u6216\u4e8b\u52a1\u8865\u507f<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u8be6\u7ec6\u7684\u5931\u8d25\u4f9d\u8d56\u4fe1\u606f<\/p>\n<\/li>\n<li>\n<p>\u8bbe\u8ba1\u5e42\u7b49\u64cd\u4f5c\u4ee5\u4fbf\u91cd\u8bd5<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7\u4f9d\u8d56\u5931\u8d25\u7387\u4ee5\u8bc6\u522b\u7cfb\u7edf\u5f31\u70b9<\/p>\n<\/li>\n<\/ul>\n<h3>23.19 425 Too Early&#xff08;\u592a\u65e9&#xff09;<\/h3>\n<h4>23.19.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>425\u72b6\u6001\u7801\u6765\u81eaRFC 8470&#xff0c;\u8868\u793a\u670d\u52a1\u5668\u4e0d\u613f\u610f\u5904\u7406\u8bf7\u6c42&#xff0c;\u56e0\u4e3a\u5b83\u53ef\u80fd\u88ab\u91cd\u653e\u3002\u7528\u4e8e\u9632\u6b62\u91cd\u653e\u653b\u51fb&#xff0c;\u7279\u522b\u662f\u57280-RTT&#xff08;\u96f6\u5f80\u8fd4\u65f6\u95f4&#xff09;TLS\u8fde\u63a5\u4e2d\u3002<\/p>\n<h4>23.19.2 TLS 1.3\u548c0-RTT<\/h4>\n<p>TLS 1.3\u5f15\u5165\u4e860-RTT\u7279\u6027&#xff0c;\u5141\u8bb8\u5ba2\u6237\u7aef\u5728TLS\u63e1\u624b\u5b8c\u6210\u524d\u53d1\u9001\u6570\u636e\u3002\u8fd9\u5e26\u6765\u4e86\u91cd\u653e\u653b\u51fb\u7684\u98ce\u9669\u3002<\/p>\n<h4>23.19.3 \u91cd\u653e\u653b\u51fb\u9632\u62a4<\/h4>\n<p>python<\/p>\n<p># Flask 0-RTT\u8bf7\u6c42\u5904\u7406<br \/>\nfrom flask import Flask, request, jsonify<br \/>\nimport hashlib<br \/>\nimport time<br \/>\nfrom collections import deque<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p>class ReplayProtection:<br \/>\n    def __init__(self, window_size&#061;300):  # 5\u5206\u949f\u7a97\u53e3<br \/>\n        self.window_size &#061; window_size<br \/>\n        self.seen_requests &#061; deque(maxlen&#061;10000)<\/p>\n<p>    def is_replay(self, request_data, client_id):<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u662f\u5426\u4e3a\u91cd\u653e\u8bf7\u6c42&#034;&#034;&#034;<br \/>\n        # \u521b\u5efa\u8bf7\u6c42\u6307\u7eb9<br \/>\n        fingerprint &#061; self.create_fingerprint(request_data, client_id)<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u5df2\u89c1\u8fc7<br \/>\n        if fingerprint in self.seen_requests:<br \/>\n            return True<\/p>\n<p>        # \u6dfb\u52a0\u5230\u5df2\u89c1\u5217\u8868<br \/>\n        self.seen_requests.append(fingerprint)<\/p>\n<p>        # \u6e05\u7406\u8fc7\u671f\u6307\u7eb9&#xff08;\u7b80\u5316\u5b9e\u73b0&#xff09;<br \/>\n        if len(self.seen_requests) % 100 &#061;&#061; 0:<br \/>\n            self.cleanup()<\/p>\n<p>        return False<\/p>\n<p>    def create_fingerprint(self, request_data, client_id):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u8bf7\u6c42\u6307\u7eb9&#034;&#034;&#034;<br \/>\n        components &#061; [<br \/>\n            client_id,<br \/>\n            str(time.time() \/\/ self.window_size),  # \u65f6\u95f4\u7a97\u53e3<br \/>\n            request.method,<br \/>\n            request.path,<br \/>\n            hashlib.sha256(request.get_data()).hexdigest()[:16]<br \/>\n        ]<\/p>\n<p>        return hashlib.sha256(&#039;|&#039;.join(components).encode()).hexdigest()<\/p>\n<p>    def cleanup(self):<br \/>\n        &#034;&#034;&#034;\u6e05\u7406\u8fc7\u671f\u6307\u7eb9&#xff08;\u7b80\u5316&#xff09;&#034;&#034;&#034;<br \/>\n        # \u5b9e\u9645\u5b9e\u73b0\u9700\u8981\u57fa\u4e8e\u65f6\u95f4\u6233\u6e05\u7406<br \/>\n        pass<\/p>\n<p>replay_protection &#061; ReplayProtection()<\/p>\n<p>&#064;app.before_request<br \/>\ndef check_replay():<br \/>\n    # \u68c0\u67e5\u662f\u5426\u4e3a0-RTT\u8bf7\u6c42<br \/>\n    early_data &#061; request.headers.get(&#039;Early-Data&#039;) &#061;&#061; &#039;1&#039;<\/p>\n<p>    if early_data:<br \/>\n        # \u63d0\u53d6\u5ba2\u6237\u7aef\u6807\u8bc6<br \/>\n        client_id &#061; request.headers.get(&#039;X-Client-ID&#039;) or request.remote_addr<\/p>\n<p>        # \u68c0\u67e5\u91cd\u653e<br \/>\n        if replay_protection.is_replay(request, client_id):<br \/>\n            return jsonify({<br \/>\n                &#039;error&#039;: &#039;too_early&#039;,<br \/>\n                &#039;message&#039;: &#039;Request may be replayed&#039;,<br \/>\n                &#039;retry_after&#039;: 1,<br \/>\n                &#039;hint&#039;: &#039;Wait 1 second and retry without early data&#039;<br \/>\n            }), 425<\/p>\n<p>&#064;app.route(&#039;\/api\/order&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef create_order():<br \/>\n    # \u68c0\u67e5\u662f\u5426\u4e3a0-RTT\u8bf7\u6c42<br \/>\n    early_data &#061; request.headers.get(&#039;Early-Data&#039;) &#061;&#061; &#039;1&#039;<\/p>\n<p>    if early_data:<br \/>\n        # \u5bf9\u4e8e0-RTT\u8bf7\u6c42&#xff0c;\u53ea\u5141\u8bb8\u5e42\u7b49\u64cd\u4f5c<br \/>\n        # \u6216\u8005\u6267\u884c\u989d\u5916\u9a8c\u8bc1<br \/>\n        return jsonify({<br \/>\n            &#039;status&#039;: &#039;accepted_with_caution&#039;,<br \/>\n            &#039;message&#039;: &#039;Order accepted via 0-RTT, additional verification may be required&#039;,<br \/>\n            &#039;order_id&#039;: generate_order_id(),<br \/>\n            &#039;warning&#039;: &#039;This request was sent via 0-RTT TLS&#039;<br \/>\n        })<\/p>\n<p>    # \u6b63\u5e38\u8bf7\u6c42\u5904\u7406<br \/>\n    return jsonify({<br \/>\n        &#039;status&#039;: &#039;created&#039;,<br \/>\n        &#039;order_id&#039;: generate_order_id()<br \/>\n    })<\/p>\n<p>def generate_order_id():<br \/>\n    import uuid<br \/>\n    return str(uuid.uuid4())<\/p>\n<h4>23.19.4 \u5ba2\u6237\u7aef\u5b9e\u73b0<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u652f\u63010-RTT\u7684\u5ba2\u6237\u7aef<br \/>\nclass EarlyDataClient {<br \/>\n    constructor(baseURL) {<br \/>\n        this.baseURL &#061; baseURL;<br \/>\n        this.earlyDataSupported &#061; false;<br \/>\n        this.earlyDataAttempts &#061; new Map(); \/\/ requestId -&gt; timestamp<br \/>\n    }<\/p>\n<p>    async request(endpoint, options &#061; {}) {<br \/>\n        const requestId &#061; this.generateRequestId();<br \/>\n        const url &#061; &#096;${this.baseURL}${endpoint}&#096;;<\/p>\n<p>        \/\/ \u68c0\u67e5\u662f\u5426\u652f\u63010-RTT<br \/>\n        if (this.earlyDataSupported &amp;&amp; this.isIdempotent(options.method)) {<br \/>\n            \/\/ \u5c1d\u8bd50-RTT\u8bf7\u6c42<br \/>\n            try {<br \/>\n                const earlyOptions &#061; {<br \/>\n                    &#8230;options,<br \/>\n                    headers: {<br \/>\n                        &#8230;options.headers,<br \/>\n                        &#039;Early-Data&#039;: &#039;1&#039;,<br \/>\n                        &#039;X-Request-ID&#039;: requestId<br \/>\n                    }<br \/>\n                };<\/p>\n<p>                this.earlyDataAttempts.set(requestId, Date.now());<\/p>\n<p>                const response &#061; await fetch(url, earlyOptions);<\/p>\n<p>                if (response.status &#061;&#061;&#061; 425) {<br \/>\n                    \/\/ \u592a\u65e9&#xff0c;\u9700\u8981\u91cd\u8bd5<br \/>\n                    console.log(&#039;Received 425 Too Early, retrying without early data&#039;);<\/p>\n<p>                    \/\/ \u79fb\u9664Early-Data\u5934<br \/>\n                    const retryOptions &#061; { &#8230;options };<br \/>\n                    if (retryOptions.headers) {<br \/>\n                        delete retryOptions.headers[&#039;Early-Data&#039;];<br \/>\n                    }<\/p>\n<p>                    \/\/ \u7b49\u5f85\u5efa\u8bae\u7684\u65f6\u95f4<br \/>\n                    const retryAfter &#061; response.headers.get(&#039;Retry-After&#039;);<br \/>\n                    if (retryAfter) {<br \/>\n                        await this.delay(parseInt(retryAfter) * 1000);<br \/>\n                    }<\/p>\n<p>                    return this.requestWithoutEarlyData(url, retryOptions);<br \/>\n                }<\/p>\n<p>                \/\/ \u68c0\u67e5\u662f\u5426\u4e3a\u8c28\u614e\u63a5\u53d7\u7684\u54cd\u5e94<br \/>\n                if (response.status &#061;&#061;&#061; 200) {<br \/>\n                    const data &#061; await response.json();<br \/>\n                    if (data.warning &amp;&amp; data.warning.includes(&#039;0-RTT&#039;)) {<br \/>\n                        console.warn(&#039;Request accepted via 0-RTT with caution&#039;);<br \/>\n                    }<br \/>\n                }<\/p>\n<p>                return response;<\/p>\n<p>            } catch (error) {<br \/>\n                console.error(&#039;Early data request failed:&#039;, error);<br \/>\n                \/\/ \u56de\u9000\u5230\u6b63\u5e38\u8bf7\u6c42<br \/>\n                return this.requestWithoutEarlyData(url, options);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/\/ \u6b63\u5e38\u8bf7\u6c42<br \/>\n        return this.requestWithoutEarlyData(url, options);<br \/>\n    }<\/p>\n<p>    async requestWithoutEarlyData(url, options) {<br \/>\n        \/\/ \u79fb\u9664\u53ef\u80fd\u7684Early-Data\u5934<br \/>\n        const cleanOptions &#061; { &#8230;options };<br \/>\n        if (cleanOptions.headers) {<br \/>\n            delete cleanOptions.headers[&#039;Early-Data&#039;];<br \/>\n        }<\/p>\n<p>        return fetch(url, cleanOptions);<br \/>\n    }<\/p>\n<p>    isIdempotent(method) {<br \/>\n        \/\/ \u68c0\u67e5\u65b9\u6cd5\u662f\u5426\u5e42\u7b49<br \/>\n        const idempotentMethods &#061; [&#039;GET&#039;, &#039;HEAD&#039;, &#039;PUT&#039;, &#039;DELETE&#039;, &#039;OPTIONS&#039;, &#039;TRACE&#039;];<br \/>\n        return idempotentMethods.includes(method.toUpperCase());<br \/>\n    }<\/p>\n<p>    generateRequestId() {<br \/>\n        return &#096;${Date.now()}-${Math.random().toString(36).substr(2, 9)}&#096;;<br \/>\n    }<\/p>\n<p>    delay(ms) {<br \/>\n        return new Promise(resolve &#061;&gt; setTimeout(resolve, ms));<br \/>\n    }<\/p>\n<p>    async detectEarlyDataSupport() {<br \/>\n        \/\/ \u68c0\u6d4b\u670d\u52a1\u5668\u662f\u5426\u652f\u63010-RTT<br \/>\n        try {<br \/>\n            const response &#061; await fetch(&#096;${this.baseURL}\/.well-known\/support-early-data&#096;, {<br \/>\n                method: &#039;HEAD&#039;<br \/>\n            });<\/p>\n<p>            this.earlyDataSupported &#061; response.headers.get(&#039;Supports-Early-Data&#039;) &#061;&#061;&#061; &#039;1&#039;;<br \/>\n            return this.earlyDataSupported;<br \/>\n        } catch (error) {<br \/>\n            this.earlyDataSupported &#061; false;<br \/>\n            return false;<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.19.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u53ea\u5bf9\u5e42\u7b49\u64cd\u4f5c\u4f7f\u75280-RTT<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u91cd\u653e\u4fdd\u62a4\u673a\u5236<\/p>\n<\/li>\n<li>\n<p>\u4e3a0-RTT\u8bf7\u6c42\u63d0\u4f9b\u660e\u786e\u7684\u8b66\u544a<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7425\u9519\u8bef\u7387\u4ee5\u8c03\u6574\u91cd\u653e\u7a97\u53e3<\/p>\n<\/li>\n<\/ul>\n<h3>23.20 426 Upgrade Required&#xff08;\u9700\u8981\u5347\u7ea7&#xff09;<\/h3>\n<h4>23.20.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>426\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u62d2\u7edd\u4f7f\u7528\u5f53\u524d\u534f\u8bae\u5904\u7406\u8bf7\u6c42&#xff0c;\u4f46\u613f\u610f\u5728\u5ba2\u6237\u7aef\u5347\u7ea7\u5230\u4e0d\u540c\u534f\u8bae\u540e\u5904\u7406\u3002\u901a\u5e38\u7528\u4e8e\u534f\u8bae\u5347\u7ea7\u534f\u5546\u3002<\/p>\n<h4>23.20.2 \u534f\u8bae\u5347\u7ea7\u673a\u5236<\/h4>\n<p>http<\/p>\n<p># \u5ba2\u6237\u7aef\u8bf7\u6c42\u5347\u7ea7<br \/>\nGET \/chat HTTP\/1.1<br \/>\nHost: server.example.com<br \/>\nUpgrade: websocket<br \/>\nConnection: Upgrade<br \/>\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ&#061;&#061;<br \/>\nSec-WebSocket-Version: 13<\/p>\n<p># \u670d\u52a1\u5668\u540c\u610f\u5347\u7ea7<br \/>\nHTTP\/1.1 101 Switching Protocols<br \/>\nUpgrade: websocket<br \/>\nConnection: Upgrade<br \/>\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK&#043;xOo&#061;<\/p>\n<p># \u670d\u52a1\u5668\u62d2\u7edd\u5347\u7ea7&#xff08;\u8981\u6c42\u5176\u4ed6\u534f\u8bae&#xff09;<br \/>\nHTTP\/1.1 426 Upgrade Required<br \/>\nUpgrade: TLS\/1.2, HTTP\/1.1<br \/>\nConnection: Upgrade<br \/>\nContent-Type: application\/json<\/p>\n<p>{<br \/>\n  &#034;error&#034;: &#034;upgrade_required&#034;,<br \/>\n  &#034;message&#034;: &#034;Please upgrade to a more secure protocol&#034;,<br \/>\n  &#034;supported_upgrades&#034;: [<br \/>\n    {<br \/>\n      &#034;protocol&#034;: &#034;TLS\/1.2&#034;,<br \/>\n      &#034;description&#034;: &#034;Transport Layer Security 1.2&#034;<br \/>\n    },<br \/>\n    {<br \/>\n      &#034;protocol&#034;: &#034;HTTP\/2&#034;,<br \/>\n      &#034;description&#034;: &#034;HTTP version 2&#034;<br \/>\n    }<br \/>\n  ]<br \/>\n}<\/p>\n<h4>23.20.3 \u4f7f\u7528\u573a\u666f<\/h4>\n<li>\n<p>\u5b89\u5168\u534f\u8bae\u5347\u7ea7&#xff1a;\u8981\u6c42\u4eceHTTP\u5347\u7ea7\u5230HTTPS<\/p>\n<\/li>\n<li>\n<p>API\u7248\u672c\u5f03\u7528&#xff1a;\u8981\u6c42\u4f7f\u7528\u65b0\u7248\u672cAPI<\/p>\n<\/li>\n<li>\n<p>\u4f20\u8f93\u534f\u8bae\u5347\u7ea7&#xff1a;\u4eceHTTP\/1.1\u5347\u7ea7\u5230HTTP\/2\u6216HTTP\/3<\/p>\n<\/li>\n<li>\n<p>WebSocket\u63e1\u624b&#xff1a;\u534f\u5546WebSocket\u534f\u8bae<\/p>\n<\/li>\n<h4>23.20.4 \u5b9e\u73b0\u793a\u4f8b<\/h4>\n<p>python<\/p>\n<p># Flask\u534f\u8bae\u5347\u7ea7\u5904\u7406<br \/>\nfrom flask import Flask, request, jsonify<br \/>\nimport ssl<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p>&#064;app.before_request<br \/>\ndef require_secure_connection():<br \/>\n    # \u68c0\u67e5\u662f\u5426\u4e3aHTTPS<br \/>\n    if not request.is_secure:<br \/>\n        # \u68c0\u67e5\u662f\u5426\u652f\u6301\u5347\u7ea7<br \/>\n        upgrade_header &#061; request.headers.get(&#039;Upgrade&#039;)<\/p>\n<p>        if upgrade_header and &#039;TLS&#039; in upgrade_header:<br \/>\n            # \u5ba2\u6237\u7aef\u5df2\u8bf7\u6c42\u5347\u7ea7<br \/>\n            return  # \u5141\u8bb8\u7ee7\u7eed&#xff0c;\u5c06\u7531\u5e95\u5c42\u670d\u52a1\u5668\u5904\u7406<\/p>\n<p>        # \u8fd4\u56de426\u8981\u6c42\u5347\u7ea7<br \/>\n        response &#061; jsonify({<br \/>\n            &#039;error&#039;: &#039;upgrade_required&#039;,<br \/>\n            &#039;message&#039;: &#039;Secure connection required&#039;,<br \/>\n            &#039;upgrade_to&#039;: &#039;HTTPS&#039;,<br \/>\n            &#039;url&#039;: f&#039;https:\/\/{request.host}{request.path}&#039;,<br \/>\n            &#039;status_code&#039;: 301  # \u4e5f\u63d0\u4f9b\u91cd\u5b9a\u5411\u9009\u9879<br \/>\n        })<br \/>\n        response.status_code &#061; 426<br \/>\n        response.headers[&#039;Upgrade&#039;] &#061; &#039;TLS\/1.2, TLS\/1.3&#039;<br \/>\n        response.headers[&#039;Connection&#039;] &#061; &#039;Upgrade&#039;<br \/>\n        return response<\/p>\n<p>&#064;app.route(&#039;\/api\/deprecated&#039;, methods&#061;[&#039;GET&#039;])<br \/>\ndef deprecated_api():<br \/>\n    &#034;&#034;&#034;\u5df2\u5f03\u7528\u7684API\u7aef\u70b9&#034;&#034;&#034;<br \/>\n    # \u68c0\u67e5\u5ba2\u6237\u7aef\u662f\u5426\u4f7f\u7528\u65b0\u7248\u672c<br \/>\n    api_version &#061; request.headers.get(&#039;X-API-Version&#039;, &#039;1.0&#039;)<\/p>\n<p>    if api_version &#061;&#061; &#039;1.0&#039;:<br \/>\n        # \u8981\u6c42\u5347\u7ea7\u5230v2<br \/>\n        response &#061; jsonify({<br \/>\n            &#039;error&#039;: &#039;api_version_deprecated&#039;,<br \/>\n            &#039;message&#039;: &#039;API v1.0 is deprecated&#039;,<br \/>\n            &#039;upgrade_to&#039;: &#039;v2.0&#039;,<br \/>\n            &#039;documentation&#039;: &#039;https:\/\/api.example.com\/v2\/docs&#039;,<br \/>\n            &#039;sunset_date&#039;: &#039;2024-12-31&#039;<br \/>\n        })<br \/>\n        response.status_code &#061; 426<br \/>\n        response.headers[&#039;X-API-Deprecated&#039;] &#061; &#039;true&#039;<br \/>\n        response.headers[&#039;X-API-Sunset&#039;] &#061; &#039;2024-12-31T23:59:59Z&#039;<br \/>\n        return response<\/p>\n<p>    # \u65b0\u7248\u672cAPI\u5904\u7406<br \/>\n    return jsonify({&#039;data&#039;: &#039;from v2 API&#039;})<\/p>\n<p># WebSocket\u5347\u7ea7\u5904\u7406<br \/>\n&#064;app.route(&#039;\/ws&#039;)<br \/>\ndef websocket_endpoint():<br \/>\n    # Flask\u672c\u8eab\u4e0d\u76f4\u63a5\u5904\u7406WebSocket<br \/>\n    # \u8fd9\u901a\u5e38\u7531\u4e13\u95e8\u7684WebSocket\u670d\u52a1\u5668\u5904\u7406<br \/>\n    # \u4ee5\u4e0b\u4e3a\u6982\u5ff5\u6027\u4ee3\u7801<\/p>\n<p>    upgrade &#061; request.headers.get(&#039;Upgrade&#039;, &#039;&#039;).lower()<\/p>\n<p>    if upgrade &#061;&#061; &#039;websocket&#039;:<br \/>\n        # \u68c0\u67e5WebSocket\u7248\u672c<br \/>\n        ws_version &#061; request.headers.get(&#039;Sec-WebSocket-Version&#039;)<\/p>\n<p>        if ws_version !&#061; &#039;13&#039;:<br \/>\n            response &#061; jsonify({<br \/>\n                &#039;error&#039;: &#039;unsupported_websocket_version&#039;,<br \/>\n                &#039;message&#039;: f&#039;WebSocket version {ws_version} not supported&#039;,<br \/>\n                &#039;supported_versions&#039;: [&#039;13&#039;]<br \/>\n            })<br \/>\n            response.status_code &#061; 426<br \/>\n            response.headers[&#039;Sec-WebSocket-Version&#039;] &#061; &#039;13&#039;<br \/>\n            return response<\/p>\n<p>        # WebSocket\u63e1\u624b\u5c06\u7531\u5e95\u5c42\u670d\u52a1\u5668\u5904\u7406<br \/>\n        return &#039;&#039;, 101  # \u5b9e\u9645\u5e94\u7531WebSocket\u670d\u52a1\u5668\u5904\u7406<\/p>\n<p>    return jsonify({&#039;error&#039;: &#039;websocket_upgrade_required&#039;}), 426<\/p>\n<h4>23.20.5 \u5ba2\u6237\u7aef\u5904\u7406<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5904\u7406426\u5347\u7ea7\u7684\u5ba2\u6237\u7aef<br \/>\nclass UpgradeAwareClient {<br \/>\n    constructor(baseURL) {<br \/>\n        this.baseURL &#061; baseURL;<br \/>\n        this.upgradeHandlers &#061; new Map();<\/p>\n<p>        \/\/ \u6ce8\u518c\u5347\u7ea7\u5904\u7406\u5668<br \/>\n        this.registerUpgradeHandler(&#039;TLS&#039;, this.handleTlsUpgrade.bind(this));<br \/>\n        this.registerUpgradeHandler(&#039;websocket&#039;, this.handleWebSocketUpgrade.bind(this));<br \/>\n        this.registerUpgradeHandler(&#039;h2&#039;, this.handleHttp2Upgrade.bind(this));<br \/>\n    }<\/p>\n<p>    async request(options) {<br \/>\n        try {<br \/>\n            const response &#061; await fetch(this.baseURL &#043; options.path, options);<\/p>\n<p>            if (response.status &#061;&#061;&#061; 426) {<br \/>\n                \/\/ \u9700\u8981\u5347\u7ea7<br \/>\n                return this.handleUpgradeResponse(response, options);<br \/>\n            }<\/p>\n<p>            return response;<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Request failed:&#039;, error);<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    async handleUpgradeResponse(response, originalOptions) {<br \/>\n        const upgradeHeader &#061; response.headers.get(&#039;Upgrade&#039;);<\/p>\n<p>        if (!upgradeHeader) {<br \/>\n            throw new Error(&#039;426 response without Upgrade header&#039;);<br \/>\n        }<\/p>\n<p>        \/\/ \u89e3\u6790\u652f\u6301\u7684\u5347\u7ea7\u9009\u9879<br \/>\n        const upgrades &#061; upgradeHeader.split(&#039;,&#039;).map(s &#061;&gt; s.trim());<\/p>\n<p>        \/\/ \u9009\u62e9\u652f\u6301\u7684\u5347\u7ea7<br \/>\n        for (const upgrade of upgrades) {<br \/>\n            const handler &#061; this.upgradeHandlers.get(upgrade.toLowerCase());<br \/>\n            if (handler) {<br \/>\n                return handler(response, originalOptions);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        throw new Error(&#096;No handler for required upgrade: ${upgrades.join(&#039;, &#039;)}&#096;);<br \/>\n    }<\/p>\n<p>    async handleTlsUpgrade(response, originalOptions) {<br \/>\n        \/\/ \u4eceHTTP\u5347\u7ea7\u5230HTTPS<br \/>\n        const httpsUrl &#061; this.baseURL.replace(&#039;http:\/\/&#039;, &#039;https:\/\/&#039;);<\/p>\n<p>        console.log(&#039;Upgrading to HTTPS&#8230;&#039;);<\/p>\n<p>        \/\/ \u91cd\u8bd5\u8bf7\u6c42\u5230HTTPS\u7aef\u70b9<br \/>\n        const upgradedOptions &#061; { &#8230;originalOptions };<br \/>\n        upgradedOptions.path &#061; originalOptions.path;<\/p>\n<p>        return fetch(httpsUrl &#043; upgradedOptions.path, upgradedOptions);<br \/>\n    }<\/p>\n<p>    async handleWebSocketUpgrade(response, originalOptions) {<br \/>\n        \/\/ \u5347\u7ea7\u5230WebSocket<br \/>\n        const wsUrl &#061; this.baseURL.replace(&#039;http&#039;, &#039;ws&#039;) &#043; originalOptions.path;<\/p>\n<p>        console.log(&#039;Upgrading to WebSocket&#8230;&#039;);<\/p>\n<p>        \/\/ \u521b\u5efaWebSocket\u8fde\u63a5<br \/>\n        return new Promise((resolve, reject) &#061;&gt; {<br \/>\n            const ws &#061; new WebSocket(wsUrl);<\/p>\n<p>            ws.onopen &#061; () &#061;&gt; {<br \/>\n                console.log(&#039;WebSocket connected&#039;);<\/p>\n<p>                \/\/ \u5982\u679c\u539f\u59cb\u8bf7\u6c42\u662fGET&#xff0c;\u53ef\u4ee5\u89e3\u6790\u4e3a\u6210\u529f<br \/>\n                if (originalOptions.method &#061;&#061;&#061; &#039;GET&#039;) {<br \/>\n                    resolve({<br \/>\n                        status: 101,<br \/>\n                        ok: true,<br \/>\n                        webSocket: ws<br \/>\n                    });<br \/>\n                } else {<br \/>\n                    \/\/ \u5bf9\u4e8e\u5176\u4ed6\u65b9\u6cd5&#xff0c;\u9700\u8981\u989d\u5916\u5904\u7406<br \/>\n                    reject(new Error(&#039;WebSocket upgrade only supported for GET requests&#039;));<br \/>\n                }<br \/>\n            };<\/p>\n<p>            ws.onerror &#061; (error) &#061;&gt; {<br \/>\n                reject(new Error(&#096;WebSocket connection failed: ${error}&#096;));<br \/>\n            };<br \/>\n        });<br \/>\n    }<\/p>\n<p>    async handleHttp2Upgrade(response, originalOptions) {<br \/>\n        \/\/ HTTP\/2\u5347\u7ea7&#xff08;\u6d4f\u89c8\u5668\u4e2d\u901a\u5e38\u81ea\u52a8\u5904\u7406&#xff09;<br \/>\n        console.log(&#039;Upgrading to HTTP\/2&#8230;&#039;);<\/p>\n<p>        \/\/ \u5bf9\u4e8e\u6d4f\u89c8\u5668&#xff0c;\u53ea\u9700\u91cd\u8bd5\u8bf7\u6c42<br \/>\n        return fetch(this.baseURL &#043; originalOptions.path, originalOptions);<br \/>\n    }<\/p>\n<p>    registerUpgradeHandler(protocol, handler) {<br \/>\n        this.upgradeHandlers.set(protocol.toLowerCase(), handler);<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.20.6 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u63d0\u4f9b\u6e05\u6670\u7684\u5347\u7ea7\u8bf4\u660e\u548c\u6587\u6863\u94fe\u63a5<\/p>\n<\/li>\n<li>\n<p>\u652f\u6301\u591a\u79cd\u5347\u7ea7\u9009\u9879<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u4f18\u96c5\u964d\u7ea7<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7\u534f\u8bae\u4f7f\u7528\u60c5\u51b5\u4ee5\u8ba1\u5212\u5f03\u7528<\/p>\n<\/li>\n<\/ul>\n<h3>23.21 428 Precondition Required&#xff08;\u9700\u8981\u524d\u63d0\u6761\u4ef6&#xff09;<\/h3>\n<h4>23.21.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>428\u72b6\u6001\u7801\u8868\u793a\u6e90\u670d\u52a1\u5668\u8981\u6c42\u8bf7\u6c42\u662f\u6761\u4ef6\u6027\u7684\u3002\u7528\u4e8e\u9632\u6b62&#034;\u4e22\u5931\u66f4\u65b0&#034;\u95ee\u9898&#xff0c;\u5ba2\u6237\u7aef\u5728\u66f4\u65b0\u8d44\u6e90\u524d\u5fc5\u987b\u5148\u83b7\u53d6\u5f53\u524d\u72b6\u6001\u3002<\/p>\n<h4>23.21.2 \u4e0e412\u7684\u533a\u522b<\/h4>\n<ul>\n<li>\n<p>412 Precondition Failed&#xff1a;\u5ba2\u6237\u7aef\u63d0\u4f9b\u4e86\u6761\u4ef6\u5934&#xff0c;\u4f46\u6761\u4ef6\u4e0d\u6ee1\u8db3<\/p>\n<\/li>\n<li>\n<p>428 Precondition Required&#xff1a;\u670d\u52a1\u5668\u8981\u6c42\u6761\u4ef6\u8bf7\u6c42&#xff0c;\u4f46\u5ba2\u6237\u7aef\u6ca1\u6709\u63d0\u4f9b\u6761\u4ef6\u5934<\/p>\n<\/li>\n<\/ul>\n<h4>23.21.3 \u5b9e\u73b0\u6a21\u5f0f<\/h4>\n<p>python<\/p>\n<p># Django\u6761\u4ef6\u8bf7\u6c42\u8981\u6c42<br \/>\nfrom django.http import JsonResponse<br \/>\nfrom django.views import View<br \/>\nfrom django.utils.decorators import method_decorator<br \/>\nfrom django.views.decorators.http import require_http_methods<br \/>\nimport hashlib<br \/>\nimport json<\/p>\n<p>class ConditionalUpdateMixin:<br \/>\n    &#034;&#034;&#034;\u8981\u6c42\u6761\u4ef6\u66f4\u65b0\u7684Mixin&#034;&#034;&#034;<\/p>\n<p>    def require_conditional_request(self, request, *args, **kwargs):<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u662f\u5426\u6ee1\u8db3\u6761\u4ef6\u8bf7\u6c42\u8981\u6c42&#034;&#034;&#034;<br \/>\n        # \u5bf9\u4e8e\u5b89\u5168\u65b9\u6cd5&#xff08;GET\u3001HEAD&#xff09;\u4e0d\u9700\u8981\u6761\u4ef6<br \/>\n        if request.method in [&#039;GET&#039;, &#039;HEAD&#039;, &#039;OPTIONS&#039;]:<br \/>\n            return True<\/p>\n<p>        # \u68c0\u67e5\u6761\u4ef6\u5934<br \/>\n        has_condition &#061; any([<br \/>\n            request.META.get(&#039;HTTP_IF_MATCH&#039;),<br \/>\n            request.META.get(&#039;HTTP_IF_NONE_MATCH&#039;),<br \/>\n            request.META.get(&#039;HTTP_IF_MODIFIED_SINCE&#039;),<br \/>\n            request.META.get(&#039;HTTP_IF_UNMODIFIED_SINCE&#039;)<br \/>\n        ])<\/p>\n<p>        if not has_condition:<br \/>\n            # \u8fd4\u56de428&#xff0c;\u8981\u6c42\u6761\u4ef6\u8bf7\u6c42<br \/>\n            response &#061; JsonResponse({<br \/>\n                &#039;error&#039;: &#039;precondition_required&#039;,<br \/>\n                &#039;message&#039;: &#039;This request requires conditional headers&#039;,<br \/>\n                &#039;required_headers&#039;: [<br \/>\n                    &#039;If-Match or If-None-Match&#039;,<br \/>\n                    &#039;If-Modified-Since or If-Unmodified-Since&#039;<br \/>\n                ],<br \/>\n                &#039;how_to&#039;: &#039;First perform a GET request to get the current ETag, then include it in If-Match header&#039;<br \/>\n            })<br \/>\n            response.status_code &#061; 428<br \/>\n            response[&#039;Precondition-Required&#039;] &#061; &#039;true&#039;<br \/>\n            return response<\/p>\n<p>        return True<\/p>\n<p>class ResourceDetailView(ConditionalUpdateMixin, View):<br \/>\n    &#034;&#034;&#034;\u9700\u8981\u6761\u4ef6\u66f4\u65b0\u7684\u8d44\u6e90\u8be6\u60c5\u89c6\u56fe&#034;&#034;&#034;<\/p>\n<p>    def dispatch(self, request, *args, **kwargs):<br \/>\n        # \u68c0\u67e5\u6761\u4ef6\u8bf7\u6c42\u8981\u6c42<br \/>\n        requirement_check &#061; self.require_conditional_request(request, *args, **kwargs)<br \/>\n        if requirement_check is not True:<br \/>\n            return requirement_check<\/p>\n<p>        return super().dispatch(request, *args, **kwargs)<\/p>\n<p>    def get(self, request, resource_id):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u8d44\u6e90&#xff08;\u5305\u542bETag&#xff09;&#034;&#034;&#034;<br \/>\n        resource &#061; self.get_resource(resource_id)<\/p>\n<p>        # \u751f\u6210ETag<br \/>\n        etag &#061; self.generate_etag(resource)<\/p>\n<p>        response &#061; JsonResponse(resource)<br \/>\n        response[&#039;ETag&#039;] &#061; etag<br \/>\n        response[&#039;Last-Modified&#039;] &#061; resource[&#039;modified_at&#039;]<\/p>\n<p>        return response<\/p>\n<p>    def put(self, request, resource_id):<br \/>\n        &#034;&#034;&#034;\u66f4\u65b0\u8d44\u6e90&#xff08;\u9700\u8981If-Match&#xff09;&#034;&#034;&#034;<br \/>\n        resource &#061; self.get_resource(resource_id)<br \/>\n        current_etag &#061; self.generate_etag(resource)<\/p>\n<p>        # \u68c0\u67e5If-Match\u5934<br \/>\n        if_match &#061; request.META.get(&#039;HTTP_IF_MATCH&#039;)<br \/>\n        if not if_match:<br \/>\n            # \u8fd9\u4e0d\u5e94\u8be5\u53d1\u751f&#xff0c;\u56e0\u4e3adispatch\u5df2\u7ecf\u68c0\u67e5\u4e86<br \/>\n            return JsonResponse({<br \/>\n                &#039;error&#039;: &#039;precondition_required&#039;,<br \/>\n                &#039;message&#039;: &#039;If-Match header is required&#039;<br \/>\n            }, status&#061;428)<\/p>\n<p>        # \u9a8c\u8bc1ETag<br \/>\n        if if_match !&#061; current_etag and if_match !&#061; &#039;*&#039;:<br \/>\n            return JsonResponse({<br \/>\n                &#039;error&#039;: &#039;precondition_failed&#039;,<br \/>\n                &#039;message&#039;: &#039;Resource has been modified&#039;,<br \/>\n                &#039;current_etag&#039;: current_etag<br \/>\n            }, status&#061;412)<\/p>\n<p>        # \u66f4\u65b0\u8d44\u6e90<br \/>\n        updated_resource &#061; self.update_resource(resource_id, request.body)<br \/>\n        new_etag &#061; self.generate_etag(updated_resource)<\/p>\n<p>        response &#061; JsonResponse(updated_resource)<br \/>\n        response[&#039;ETag&#039;] &#061; new_etag<br \/>\n        return response<\/p>\n<p>    def get_resource(self, resource_id):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u8d44\u6e90&#xff08;\u7b80\u5316\u5b9e\u73b0&#xff09;&#034;&#034;&#034;<br \/>\n        return {<br \/>\n            &#039;id&#039;: resource_id,<br \/>\n            &#039;name&#039;: f&#039;Resource {resource_id}&#039;,<br \/>\n            &#039;modified_at&#039;: &#039;2024-01-15T10:30:00Z&#039;,<br \/>\n            &#039;data&#039;: &#039;Some data&#039;<br \/>\n        }<\/p>\n<p>    def generate_etag(self, resource):<br \/>\n        &#034;&#034;&#034;\u751f\u6210ETag&#034;&#034;&#034;<br \/>\n        content &#061; json.dumps(resource, sort_keys&#061;True)<br \/>\n        return hashlib.md5(content.encode()).hexdigest()<\/p>\n<p>    def update_resource(self, resource_id, data):<br \/>\n        &#034;&#034;&#034;\u66f4\u65b0\u8d44\u6e90&#xff08;\u7b80\u5316\u5b9e\u73b0&#xff09;&#034;&#034;&#034;<br \/>\n        resource &#061; self.get_resource(resource_id)<br \/>\n        # \u5b9e\u9645\u5b9e\u73b0\u4f1a\u89e3\u6790\u548c\u66f4\u65b0\u6570\u636e<br \/>\n        resource[&#039;modified_at&#039;] &#061; &#039;2024-01-15T11:00:00Z&#039;<br \/>\n        return resource<\/p>\n<h4>23.21.4 \u5ba2\u6237\u7aef\u6d41\u7a0b<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5904\u7406428\u7684\u5ba2\u6237\u7aef<br \/>\nclass ConditionalRequestClient {<br \/>\n    async updateResource(resourceId, updates) {<br \/>\n        \/\/ 1. \u9996\u5148\u83b7\u53d6\u8d44\u6e90\u4ee5\u83b7\u53d6ETag<br \/>\n        const getResponse &#061; await fetch(&#096;\/api\/resources\/${resourceId}&#096;);<\/p>\n<p>        if (!getResponse.ok) {<br \/>\n            throw new Error(&#096;Failed to get resource: ${getResponse.status}&#096;);<br \/>\n        }<\/p>\n<p>        const etag &#061; getResponse.headers.get(&#039;ETag&#039;);<br \/>\n        const resource &#061; await getResponse.json();<\/p>\n<p>        \/\/ 2. \u4f7f\u7528ETag\u8fdb\u884c\u6761\u4ef6\u66f4\u65b0<br \/>\n        const updateResponse &#061; await fetch(&#096;\/api\/resources\/${resourceId}&#096;, {<br \/>\n            method: &#039;PUT&#039;,<br \/>\n            headers: {<br \/>\n                &#039;Content-Type&#039;: &#039;application\/json&#039;,<br \/>\n                &#039;If-Match&#039;: etag<br \/>\n            },<br \/>\n            body: JSON.stringify({<br \/>\n                &#8230;resource,<br \/>\n                &#8230;updates<br \/>\n            })<br \/>\n        });<\/p>\n<p>        if (updateResponse.status &#061;&#061;&#061; 428) {<br \/>\n            \/\/ \u670d\u52a1\u5668\u8981\u6c42\u6761\u4ef6\u8bf7\u6c42&#xff0c;\u4f46\u6211\u4eec\u5df2\u7ecf\u63d0\u4f9b\u4e86<br \/>\n            \/\/ \u8fd9\u53ef\u80fd\u662f\u914d\u7f6e\u9519\u8bef<br \/>\n            throw new Error(&#039;Server requires conditional request but rejected our If-Match header&#039;);<br \/>\n        }<\/p>\n<p>        if (updateResponse.status &#061;&#061;&#061; 412) {<br \/>\n            \/\/ \u524d\u63d0\u6761\u4ef6\u5931\u8d25&#xff08;\u8d44\u6e90\u5df2\u88ab\u4fee\u6539&#xff09;<br \/>\n            \/\/ \u83b7\u53d6\u65b0\u7248\u672c\u5e76\u91cd\u8bd5\u6216\u901a\u77e5\u7528\u6237<br \/>\n            const error &#061; await updateResponse.json();<br \/>\n            throw new Error(&#096;Update conflict: ${error.message}&#096;);<br \/>\n        }<\/p>\n<p>        if (!updateResponse.ok) {<br \/>\n            throw new Error(&#096;Update failed: ${updateResponse.status}&#096;);<br \/>\n        }<\/p>\n<p>        return updateResponse.json();<br \/>\n    }<\/p>\n<p>    async safeUpdateWithRetry(resourceId, updates, maxRetries &#061; 3) {<br \/>\n        for (let attempt &#061; 0; attempt &lt; maxRetries; attempt&#043;&#043;) {<br \/>\n            try {<br \/>\n                return await this.updateResource(resourceId, updates);<br \/>\n            } catch (error) {<br \/>\n                if (error.message.includes(&#039;Update conflict&#039;) &amp;&amp; attempt &lt; maxRetries &#8211; 1) {<br \/>\n                    \/\/ \u51b2\u7a81\u9519\u8bef&#xff0c;\u7b49\u5f85\u540e\u91cd\u8bd5<br \/>\n                    console.log(&#096;Update conflict, retrying (attempt ${attempt &#043; 1})&#8230;&#096;);<br \/>\n                    await this.delay(100 * Math.pow(2, attempt)); \/\/ \u6307\u6570\u9000\u907f<br \/>\n                    continue;<br \/>\n                }<br \/>\n                throw error;<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    delay(ms) {<br \/>\n        return new Promise(resolve &#061;&gt; setTimeout(resolve, ms));<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.21.5 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u4e3a\u6240\u6709\u975e\u5b89\u5168\u65b9\u6cd5\u8981\u6c42\u6761\u4ef6\u8bf7\u6c42<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u6e05\u6670\u7684\u9519\u8bef\u4fe1\u606f\u548c\u89e3\u51b3\u6b65\u9aa4<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u4e50\u89c2\u9501\u907f\u514d\u66f4\u65b0\u51b2\u7a81<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u652f\u6301\u5f31ETag\u7528\u4e8e\u6027\u80fd\u4f18\u5316<\/p>\n<\/li>\n<\/ul>\n<h3>23.22 429 Too Many Requests&#xff08;\u8bf7\u6c42\u8fc7\u591a&#xff09;<\/h3>\n<p>\u6ce8&#xff1a;\u867d\u7136\u6807\u9898\u4e2d\u672a\u5217\u51fa429&#xff0c;\u4f46\u7531\u4e8e\u5b83\u5e38\u4e0e\u5176\u4ed64xx\u72b6\u6001\u7801\u4e00\u8d77\u8ba8\u8bba&#xff0c;\u4e14\u975e\u5e38\u91cd\u8981&#xff0c;\u6b64\u5904\u7b80\u8981\u6db5\u76d6\u3002<\/p>\n<h4>23.22.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>429\u72b6\u6001\u7801\u8868\u793a\u7528\u6237\u5728\u7ed9\u5b9a\u65f6\u95f4\u5185\u53d1\u9001\u4e86\u592a\u591a\u8bf7\u6c42&#xff08;&#034;\u9650\u6d41&#034;&#xff09;\u3002\u7528\u4e8e\u9632\u6b62\u6ee5\u7528\u548c\u4fdd\u8bc1\u670d\u52a1\u53ef\u7528\u6027\u3002<\/p>\n<h4>23.22.2 \u9650\u6d41\u5934\u4fe1\u606f<\/h4>\n<p>http<\/p>\n<p>HTTP\/1.1 429 Too Many Requests<br \/>\nContent-Type: application\/json<br \/>\nRetry-After: 3600<br \/>\nX-RateLimit-Limit: 100<br \/>\nX-RateLimit-Remaining: 0<br \/>\nX-RateLimit-Reset: 1673780400<\/p>\n<p>{<br \/>\n  &#034;error&#034;: &#034;rate_limit_exceeded&#034;,<br \/>\n  &#034;message&#034;: &#034;Too many requests, please try again later&#034;,<br \/>\n  &#034;retry_after&#034;: 3600,<br \/>\n  &#034;limit&#034;: 100,<br \/>\n  &#034;period&#034;: &#034;hour&#034;,<br \/>\n  &#034;documentation&#034;: &#034;https:\/\/api.example.com\/docs\/rate-limiting&#034;<br \/>\n}<\/p>\n<h4>23.22.3 \u9650\u6d41\u7b56\u7565<\/h4>\n<li>\n<p>\u4ee4\u724c\u6876\u7b97\u6cd5&#xff1a;\u5e73\u6ed1\u9650\u6d41&#xff0c;\u5141\u8bb8\u7a81\u53d1<\/p>\n<\/li>\n<li>\n<p>\u6f0f\u6876\u7b97\u6cd5&#xff1a;\u6052\u5b9a\u901f\u7387\u5904\u7406<\/p>\n<\/li>\n<li>\n<p>\u56fa\u5b9a\u7a97\u53e3\u8ba1\u6570\u5668&#xff1a;\u7b80\u5355\u8ba1\u6570&#xff0c;\u4f46\u53ef\u80fd\u5141\u8bb8\u77ac\u65f6\u8d85\u9650<\/p>\n<\/li>\n<li>\n<p>\u6ed1\u52a8\u7a97\u53e3\u65e5\u5fd7&#xff1a;\u7cbe\u786e\u4f46\u5185\u5b58\u6d88\u8017\u5927<\/p>\n<\/li>\n<li>\n<p>\u5206\u5e03\u5f0f\u9650\u6d41&#xff1a;\u7528\u4e8e\u5fae\u670d\u52a1\u67b6\u6784<\/p>\n<\/li>\n<h3>23.23 431 Request Header Fields Too Large&#xff08;\u8bf7\u6c42\u5934\u5b57\u6bb5\u592a\u5927&#xff09;<\/h3>\n<h4>23.23.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>431\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u4e0d\u613f\u610f\u5904\u7406\u8bf7\u6c42&#xff0c;\u56e0\u4e3a\u5355\u4e2a\u5934\u5b57\u6bb5\u6216\u6240\u6709\u5934\u5b57\u6bb5\u7684\u603b\u5927\u5c0f\u8d85\u8fc7\u9650\u5236\u3002<\/p>\n<h4>23.23.2 \u5e38\u89c1\u9650\u5236<\/h4>\n<table>\n<tr>\u670d\u52a1\u5668\u9ed8\u8ba4\u9650\u5236\u914d\u7f6e\u9879<\/tr>\n<tbody>\n<tr>\n<td>Nginx<\/td>\n<td>4KB\/8KB&#xff08;\u53d6\u51b3\u4e8e\u7cfb\u7edf&#xff09;<\/td>\n<td>large_client_header_buffers<\/td>\n<\/tr>\n<tr>\n<td>Apache<\/td>\n<td>8KB<\/td>\n<td>LimitRequestFieldSize<\/td>\n<\/tr>\n<tr>\n<td>IIS<\/td>\n<td>16KB<\/td>\n<td>maxRequestLength<\/td>\n<\/tr>\n<tr>\n<td>Node.js<\/td>\n<td>16KB<\/td>\n<td>maxHeaderSize<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>23.23.3 \u95ee\u9898\u8bca\u65ad<\/h4>\n<p>python<\/p>\n<p># \u8bca\u65ad\u5934\u5927\u5c0f\u95ee\u9898<br \/>\ndef diagnose_header_size(request):<br \/>\n    total_size &#061; 0<br \/>\n    header_sizes &#061; {}<\/p>\n<p>    for key, value in request.headers.items():<br \/>\n        size &#061; len(key) &#043; len(value) &#043; 2  # &#043;2 for &#034;: &#034;<br \/>\n        header_sizes[key] &#061; size<br \/>\n        total_size &#043;&#061; size<\/p>\n<p>    return {<br \/>\n        &#039;total_size&#039;: total_size,<br \/>\n        &#039;header_sizes&#039;: header_sizes,<br \/>\n        &#039;largest_headers&#039;: sorted(<br \/>\n            header_sizes.items(),<br \/>\n            key&#061;lambda x: x[1],<br \/>\n            reverse&#061;True<br \/>\n        )[:5]<br \/>\n    }<\/p>\n<p># \u4e2d\u95f4\u4ef6\u68c0\u67e5\u5934\u5927\u5c0f<br \/>\nfrom django.http import JsonResponse<\/p>\n<p>class HeaderSizeMiddleware:<br \/>\n    def __init__(self, get_response):<br \/>\n        self.get_response &#061; get_response<br \/>\n        self.max_header_size &#061; 8 * 1024  # 8KB<\/p>\n<p>    def __call__(self, request):<br \/>\n        # \u8ba1\u7b97\u8bf7\u6c42\u5934\u5927\u5c0f<br \/>\n        header_size &#061; sum(<br \/>\n            len(key) &#043; len(value) &#043; 2<br \/>\n            for key, value in request.headers.items()<br \/>\n        )<\/p>\n<p>        if header_size &gt; self.max_header_size:<br \/>\n            return JsonResponse({<br \/>\n                &#039;error&#039;: &#039;request_header_fields_too_large&#039;,<br \/>\n                &#039;message&#039;: &#039;Request headers exceed size limit&#039;,<br \/>\n                &#039;max_size&#039;: self.max_header_size,<br \/>\n                &#039;actual_size&#039;: header_size,<br \/>\n                &#039;largest_headers&#039;: self.get_largest_headers(request)<br \/>\n            }, status&#061;431)<\/p>\n<p>        return self.get_response(request)<\/p>\n<p>    def get_largest_headers(self, request):<br \/>\n        headers &#061; []<br \/>\n        for key, value in request.headers.items():<br \/>\n            size &#061; len(key) &#043; len(value) &#043; 2<br \/>\n            headers.append({&#039;name&#039;: key, &#039;size&#039;: size})<\/p>\n<p>        return sorted(headers, key&#061;lambda x: x[&#039;size&#039;], reverse&#061;True)[:3]<\/p>\n<h4>23.23.4 \u5ba2\u6237\u7aef\u4f18\u5316<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u4f18\u5316\u8bf7\u6c42\u5934\u5927\u5c0f<br \/>\nclass HeaderOptimizer {<br \/>\n    constructor() {<br \/>\n        this.essentialHeaders &#061; new Set([<br \/>\n            &#039;authorization&#039;,<br \/>\n            &#039;content-type&#039;,<br \/>\n            &#039;accept&#039;,<br \/>\n            &#039;user-agent&#039;<br \/>\n        ]);<br \/>\n    }<\/p>\n<p>    optimizeHeaders(headers) {<br \/>\n        const optimized &#061; {};<br \/>\n        let totalSize &#061; 0;<\/p>\n<p>        for (const [key, value] of Object.entries(headers)) {<br \/>\n            const lowerKey &#061; key.toLowerCase();<\/p>\n<p>            \/\/ \u53ea\u4fdd\u7559\u5fc5\u8981\u5934<br \/>\n            if (this.essentialHeaders.has(lowerKey) ||<br \/>\n                lowerKey.startsWith(&#039;x-&#039;) ||<br \/>\n                lowerKey.startsWith(&#039;sec-&#039;)) {<\/p>\n<p>                optimized[key] &#061; value;<br \/>\n                totalSize &#043;&#061; key.length &#043; value.length &#043; 2;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/\/ \u68c0\u67e5\u662f\u5426\u8d85\u8fc7\u9650\u5236<br \/>\n        if (totalSize &gt; 8 * 1024) { \/\/ 8KB<br \/>\n            console.warn(&#096;Request headers are large: ${totalSize} bytes&#096;);<\/p>\n<p>            \/\/ \u5c1d\u8bd5\u538b\u7f29\u5927\u503c\u5934<br \/>\n            return this.compressHeaders(optimized);<br \/>\n        }<\/p>\n<p>        return optimized;<br \/>\n    }<\/p>\n<p>    compressHeaders(headers) {<br \/>\n        const compressed &#061; { &#8230;headers };<\/p>\n<p>        \/\/ \u5bf9\u4e8e\u5927\u503c\u7684\u81ea\u5b9a\u4e49\u5934&#xff0c;\u53ef\u4ee5\u8003\u8651\u538b\u7f29<br \/>\n        for (const [key, value] of Object.entries(headers)) {<br \/>\n            if (key.startsWith(&#039;X-&#039;) &amp;&amp; value.length &gt; 1024) {<br \/>\n                \/\/ \u4f7f\u7528Base64\u7f16\u7801\u6216\u5176\u4ed6\u538b\u7f29<br \/>\n                compressed[key] &#061; this.compressValue(value);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return compressed;<br \/>\n    }<\/p>\n<p>    compressValue(value) {<br \/>\n        \/\/ \u7b80\u5355\u538b\u7f29\u793a\u4f8b<br \/>\n        if (typeof value &#061;&#061;&#061; &#039;object&#039;) {<br \/>\n            \/\/ \u5982\u679c\u662f\u5bf9\u8c61&#xff0c;\u8f6c\u4e3a\u7d27\u51d1JSON<br \/>\n            return JSON.stringify(value);<br \/>\n        }<\/p>\n<p>        return value;<br \/>\n    }<\/p>\n<p>    async makeRequest(url, options) {<br \/>\n        const optimizedHeaders &#061; this.optimizeHeaders(options.headers || {});<\/p>\n<p>        const optimizedOptions &#061; {<br \/>\n            &#8230;options,<br \/>\n            headers: optimizedHeaders<br \/>\n        };<\/p>\n<p>        const response &#061; await fetch(url, optimizedOptions);<\/p>\n<p>        if (response.status &#061;&#061;&#061; 431) {<br \/>\n            \/\/ \u5934\u4ecd\u7136\u592a\u5927&#xff0c;\u9700\u8981\u8fdb\u4e00\u6b65\u4f18\u5316<br \/>\n            console.error(&#039;Headers still too large after optimization&#039;);<\/p>\n<p>            \/\/ \u83b7\u53d6\u8bca\u65ad\u4fe1\u606f<br \/>\n            const error &#061; await response.json();<br \/>\n            console.error(&#039;Large headers:&#039;, error.largest_headers);<\/p>\n<p>            throw new Error(&#039;Request headers too large&#039;);<br \/>\n        }<\/p>\n<p>        return response;<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.23.5 \u670d\u52a1\u5668\u914d\u7f6e<\/h4>\n<p>nginx<\/p>\n<p># Nginx\u5934\u5927\u5c0f\u914d\u7f6e<br \/>\nhttp {<br \/>\n    # \u5ba2\u6237\u7aef\u5934\u7f13\u51b2\u533a\u5927\u5c0f<br \/>\n    client_header_buffer_size 1k;<\/p>\n<p>    # \u5927\u5ba2\u6237\u7aef\u5934\u7f13\u51b2\u533a<br \/>\n    large_client_header_buffers 4 8k;<\/p>\n<p>    # \u8bf7\u6c42\u884c\u6700\u5927\u5927\u5c0f<br \/>\n    client_max_body_size 10m;<\/p>\n<p>    # \u7279\u5b9a\u4f4d\u7f6e\u66f4\u4e25\u683c\u7684\u9650\u5236<br \/>\n    location \/api\/ {<br \/>\n        # \u66f4\u4e25\u683c\u7684\u5934\u5927\u5c0f\u9650\u5236<br \/>\n        large_client_header_buffers 2 4k;<\/p>\n<p>        # \u81ea\u5b9a\u4e49\u9519\u8bef\u5904\u7406<br \/>\n        error_page 431 \/431.json;<\/p>\n<p>        location &#061; \/431.json {<br \/>\n            internal;<br \/>\n            default_type application\/json;<br \/>\n            return 200 &#039;{&#034;error&#034;:&#034;headers_too_large&#034;,&#034;message&#034;:&#034;Request headers exceed 4KB limit&#034;}&#039;;<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<h4>23.23.6 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u76d1\u63a7\u5934\u5927\u5c0f\u5206\u5e03\u4ee5\u8bbe\u7f6e\u5408\u7406\u9650\u5236<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u6e05\u6670\u7684\u9519\u8bef\u4fe1\u606f\u6307\u51fa\u54ea\u4e9b\u5934\u592a\u5927<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u652f\u6301\u5934\u538b\u7f29&#xff08;\u5982HTTP\/2 HPACK&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u907f\u514d\u5728\u5934\u4e2d\u5b58\u50a8\u5927\u91cf\u6570\u636e<\/p>\n<\/li>\n<\/ul>\n<h3>23.24 451 Unavailable For Legal Reasons&#xff08;\u56e0\u6cd5\u5f8b\u539f\u56e0\u4e0d\u53ef\u7528&#xff09;<\/h3>\n<h4>23.24.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<p>451\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u7531\u4e8e\u6cd5\u5f8b\u539f\u56e0\u65e0\u6cd5\u63d0\u4f9b\u8bf7\u6c42\u7684\u8d44\u6e90\u3002\u5f15\u7528\u81ea\u96f7\u00b7\u5e03\u62c9\u5fb7\u4f2f\u91cc\u7684\u300a\u534e\u6c0f451\u300b&#xff0c;\u8be5\u72b6\u6001\u7801\u4e13\u95e8\u7528\u4e8e\u5185\u5bb9\u5ba1\u67e5\u573a\u666f\u3002<\/p>\n<h4>23.24.2 \u4f7f\u7528\u573a\u666f<\/h4>\n<li>\n<p>\u653f\u5e9c\u5ba1\u67e5&#xff1a;\u653f\u5e9c\u8981\u6c42\u5c4f\u853d\u7684\u5185\u5bb9<\/p>\n<\/li>\n<li>\n<p>\u7248\u6743\u95ee\u9898&#xff1a;\u4fb5\u6743\u5185\u5bb9\u79fb\u9664<\/p>\n<\/li>\n<li>\n<p>\u6cd5\u9662\u547d\u4ee4&#xff1a;\u6839\u636e\u6cd5\u5f8b\u547d\u4ee4\u5c4f\u853d<\/p>\n<\/li>\n<li>\n<p>\u5730\u533a\u9650\u5236&#xff1a;\u5185\u5bb9\u5730\u7406\u5c01\u9501<\/p>\n<\/li>\n<li>\n<p>GDPR\u5408\u89c4&#xff1a;\u6570\u636e\u4fdd\u62a4\u8981\u6c42\u79fb\u9664<\/p>\n<\/li>\n<h4>23.24.3 \u54cd\u5e94\u683c\u5f0f<\/h4>\n<p>http<\/p>\n<p>HTTP\/1.1 451 Unavailable For Legal Reasons<br \/>\nContent-Type: application\/json<br \/>\nX-Censorship-Reason: government-order<br \/>\nX-Blocking-Authority: Ministry of Truth<br \/>\nX-Appeal-URL: https:\/\/example.com\/appeal<br \/>\nLink: &lt;https:\/\/eff.org\/censorship&gt;; rel&#061;&#034;related&#034;<\/p>\n<p>{<br \/>\n  &#034;error&#034;: &#034;unavailable_for_legal_reasons&#034;,<br \/>\n  &#034;message&#034;: &#034;This resource is not available in your country due to legal restrictions&#034;,<br \/>\n  &#034;reference&#034;: &#034;Government Order 2024-01&#034;,<br \/>\n  &#034;reason&#034;: &#034;violates_local_laws&#034;,<br \/>\n  &#034;country&#034;: &#034;DE&#034;,<br \/>\n  &#034;blocking_authority&#034;: &#034;Federal Agency&#034;,<br \/>\n  &#034;block_date&#034;: &#034;2024-01-15&#034;,<br \/>\n  &#034;appeal_process&#034;: {<br \/>\n    &#034;url&#034;: &#034;https:\/\/example.com\/appeal&#034;,<br \/>\n    &#034;email&#034;: &#034;legal&#064;example.com&#034;,<br \/>\n    &#034;deadline&#034;: &#034;2024-02-15&#034;<br \/>\n  },<br \/>\n  &#034;alternatives&#034;: [<br \/>\n    {<br \/>\n      &#034;description&#034;: &#034;Similar content available in other regions&#034;,<br \/>\n      &#034;url&#034;: &#034;https:\/\/global.example.com\/content\/123&#034;<br \/>\n    }<br \/>\n  ]<br \/>\n}<\/p>\n<h4>23.24.4 \u5b9e\u73b0\u793a\u4f8b<\/h4>\n<p>python<\/p>\n<p># Django\u5730\u7406\u5c01\u9501\u548c\u5185\u5bb9\u5ba1\u67e5<br \/>\nfrom django.http import JsonResponse<br \/>\nfrom django.views import View<br \/>\nfrom django.utils.decorators import method_decorator<br \/>\nfrom django.views.decorators.vary import vary_on_headers<br \/>\nimport geoip2.database<br \/>\nimport json<\/p>\n<p>class LegalComplianceMiddleware:<br \/>\n    &#034;&#034;&#034;\u6cd5\u5f8b\u5408\u89c4\u4e2d\u95f4\u4ef6&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, get_response):<br \/>\n        self.get_response &#061; get_response<br \/>\n        self.geoip_reader &#061; geoip2.database.Reader(&#039;\/path\/to\/GeoLite2-Country.mmdb&#039;)<br \/>\n        self.blocked_content &#061; self.load_blocked_content()<\/p>\n<p>    def __call__(self, request):<br \/>\n        response &#061; self.get_response(request)<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u9700\u8981\u5730\u7406\u5c01\u9501<br \/>\n        if self.is_blocked_in_country(request):<br \/>\n            return self.create_451_response(request)<\/p>\n<p>        return response<\/p>\n<p>    def is_blocked_in_country(self, request):<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u5185\u5bb9\u662f\u5426\u5728\u7528\u6237\u6240\u5728\u56fd\u5bb6\u88ab\u5c01\u9501&#034;&#034;&#034;<br \/>\n        try:<br \/>\n            # \u83b7\u53d6\u7528\u6237\u56fd\u5bb6<br \/>\n            country_code &#061; self.get_user_country(request)<\/p>\n<p>            # \u83b7\u53d6\u8bf7\u6c42\u7684\u8def\u5f84<br \/>\n            path &#061; request.path<\/p>\n<p>            # \u68c0\u67e5\u662f\u5426\u5728\u5c01\u9501\u5217\u8868\u4e2d<br \/>\n            if path in self.blocked_content:<br \/>\n                blocked_in &#061; self.blocked_content[path].get(&#039;blocked_countries&#039;, [])<br \/>\n                return country_code in blocked_in<\/p>\n<p>        except Exception as e:<br \/>\n            print(f&#034;Error checking geo-block: {e}&#034;)<\/p>\n<p>        return False<\/p>\n<p>    def get_user_country(self, request):<br \/>\n        &#034;&#034;&#034;\u4eceIP\u83b7\u53d6\u7528\u6237\u56fd\u5bb6&#034;&#034;&#034;<br \/>\n        # \u4eceX-Forwarded-For\u6216\u76f4\u63a5IP\u83b7\u53d6<br \/>\n        ip &#061; self.get_client_ip(request)<\/p>\n<p>        try:<br \/>\n            response &#061; self.geoip_reader.country(ip)<br \/>\n            return response.country.iso_code<br \/>\n        except:<br \/>\n            # \u9ed8\u8ba4\u56fd\u5bb6\u6216\u57fa\u4e8e\u5176\u4ed6\u5934\u5224\u65ad<br \/>\n            return request.META.get(&#039;HTTP_CF_IPCOUNTRY&#039;, &#039;US&#039;)<\/p>\n<p>    def get_client_ip(self, request):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u5ba2\u6237\u7aefIP&#034;&#034;&#034;<br \/>\n        x_forwarded_for &#061; request.META.get(&#039;HTTP_X_FORWARDED_FOR&#039;)<br \/>\n        if x_forwarded_for:<br \/>\n            ip &#061; x_forwarded_for.split(&#039;,&#039;)[0]<br \/>\n        else:<br \/>\n            ip &#061; request.META.get(&#039;REMOTE_ADDR&#039;)<br \/>\n        return ip<\/p>\n<p>    def create_451_response(self, request):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa451\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        path &#061; request.path<br \/>\n        blocking_info &#061; self.blocked_content.get(path, {})<\/p>\n<p>        response &#061; JsonResponse({<br \/>\n            &#039;error&#039;: &#039;unavailable_for_legal_reasons&#039;,<br \/>\n            &#039;message&#039;: blocking_info.get(&#039;message&#039;, &#039;Content not available in your region&#039;),<br \/>\n            &#039;reason&#039;: blocking_info.get(&#039;reason&#039;, &#039;legal_restriction&#039;),<br \/>\n            &#039;reference&#039;: blocking_info.get(&#039;reference&#039;),<br \/>\n            &#039;country&#039;: self.get_user_country(request),<br \/>\n            &#039;blocking_authority&#039;: blocking_info.get(&#039;authority&#039;),<br \/>\n            &#039;block_date&#039;: blocking_info.get(&#039;block_date&#039;),<br \/>\n            &#039;appeal&#039;: blocking_info.get(&#039;appeal_url&#039;),<br \/>\n            &#039;alternatives&#039;: blocking_info.get(&#039;alternatives&#039;, [])<br \/>\n        })<br \/>\n        response.status_code &#061; 451<\/p>\n<p>        # \u6dfb\u52a0\u4fe1\u606f\u5934<br \/>\n        response[&#039;X-Censorship-Reason&#039;] &#061; blocking_info.get(&#039;reason&#039;, &#039;unknown&#039;)<br \/>\n        if blocking_info.get(&#039;authority&#039;):<br \/>\n            response[&#039;X-Blocking-Authority&#039;] &#061; blocking_info[&#039;authority&#039;]<br \/>\n        if blocking_info.get(&#039;appeal_url&#039;):<br \/>\n            response[&#039;X-Appeal-URL&#039;] &#061; blocking_info[&#039;appeal_url&#039;]<\/p>\n<p>        return response<\/p>\n<p>    def load_blocked_content(self):<br \/>\n        &#034;&#034;&#034;\u52a0\u8f7d\u5c01\u9501\u5185\u5bb9\u5217\u8868&#xff08;\u4ece\u6570\u636e\u5e93\u6216\u6587\u4ef6&#xff09;&#034;&#034;&#034;<br \/>\n        # \u793a\u4f8b\u6570\u636e<br \/>\n        return {<br \/>\n            &#039;\/content\/restricted-article&#039;: {<br \/>\n                &#039;blocked_countries&#039;: [&#039;CN&#039;, &#039;RU&#039;, &#039;IR&#039;],<br \/>\n                &#039;reason&#039;: &#039;government_order&#039;,<br \/>\n                &#039;authority&#039;: &#039;Ministry of Culture&#039;,<br \/>\n                &#039;reference&#039;: &#039;GO-2024-001&#039;,<br \/>\n                &#039;block_date&#039;: &#039;2024-01-01&#039;,<br \/>\n                &#039;appeal_url&#039;: &#039;https:\/\/example.com\/appeal\/restricted-article&#039;,<br \/>\n                &#039;alternatives&#039;: [<br \/>\n                    {<br \/>\n                        &#039;url&#039;: &#039;\/content\/similar-article&#039;,<br \/>\n                        &#039;description&#039;: &#039;Similar topic available&#039;<br \/>\n                    }<br \/>\n                ]<br \/>\n            }<br \/>\n        }<\/p>\n<p>class ContentDetailView(View):<br \/>\n    &#034;&#034;&#034;\u5185\u5bb9\u8be6\u60c5\u89c6\u56fe&#xff08;\u53ef\u80fd\u8fd4\u56de451&#xff09;&#034;&#034;&#034;<\/p>\n<p>    &#064;method_decorator(vary_on_headers(&#039;X-Country-Code&#039;))<br \/>\n    def get(self, request, content_id):<br \/>\n        content &#061; self.get_content(content_id)<\/p>\n<p>        if content.get(&#039;blocked&#039;, False):<br \/>\n            # \u68c0\u67e5\u7528\u6237\u56fd\u5bb6<br \/>\n            user_country &#061; request.META.get(&#039;HTTP_X_COUNTRY_CODE&#039;, &#039;US&#039;)<\/p>\n<p>            if user_country in content.get(&#039;blocked_countries&#039;, []):<br \/>\n                return JsonResponse({<br \/>\n                    &#039;error&#039;: &#039;unavailable_for_legal_reasons&#039;,<br \/>\n                    &#039;message&#039;: f&#039;This content is not available in {user_country}&#039;,<br \/>\n                    &#039;content_id&#039;: content_id,<br \/>\n                    &#039;country&#039;: user_country<br \/>\n                }, status&#061;451)<\/p>\n<p>        return JsonResponse(content)<\/p>\n<p>    def get_content(self, content_id):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u5185\u5bb9&#xff08;\u7b80\u5316\u5b9e\u73b0&#xff09;&#034;&#034;&#034;<br \/>\n        return {<br \/>\n            &#039;id&#039;: content_id,<br \/>\n            &#039;title&#039;: &#039;Sample Content&#039;,<br \/>\n            &#039;blocked&#039;: True,<br \/>\n            &#039;blocked_countries&#039;: [&#039;CN&#039;, &#039;RU&#039;],<br \/>\n            &#039;available_countries&#039;: [&#039;US&#039;, &#039;UK&#039;, &#039;DE&#039;]<br \/>\n        }<\/p>\n<h4>23.24.5 \u900f\u660e\u5ea6\u62a5\u544a<\/h4>\n<p>json<\/p>\n<p>{<br \/>\n  &#034;transparency_report&#034;: {<br \/>\n    &#034;period&#034;: &#034;2024-Q1&#034;,<br \/>\n    &#034;total_requests&#034;: 1000000,<br \/>\n    &#034;451_responses&#034;: 1250,<br \/>\n    &#034;blocking_reasons&#034;: {<br \/>\n      &#034;government_order&#034;: 800,<br \/>\n      &#034;copyright&#034;: 300,<br \/>\n      &#034;court_order&#034;: 100,<br \/>\n      &#034;terms_violation&#034;: 50<br \/>\n    },<br \/>\n    &#034;countries_affected&#034;: [<br \/>\n      {&#034;country&#034;: &#034;CN&#034;, &#034;blocks&#034;: 500},<br \/>\n      {&#034;country&#034;: &#034;RU&#034;, &#034;blocks&#034;: 300},<br \/>\n      {&#034;country&#034;: &#034;IR&#034;, &#034;blocks&#034;: 200},<br \/>\n      {&#034;country&#034;: &#034;DE&#034;, &#034;blocks&#034;: 50}<br \/>\n    ],<br \/>\n    &#034;appeals&#034;: {<br \/>\n      &#034;received&#034;: 200,<br \/>\n      &#034;granted&#034;: 50,<br \/>\n      &#034;denied&#034;: 100,<br \/>\n      &#034;pending&#034;: 50<br \/>\n    },<br \/>\n    &#034;documentation&#034;: &#034;https:\/\/example.com\/transparency\/2024-Q1&#034;<br \/>\n  }<br \/>\n}<\/p>\n<h4>23.24.6 \u6700\u4f73\u5b9e\u8df5<\/h4>\n<ul>\n<li>\n<p>\u63d0\u4f9b\u8be6\u7ec6\u7684\u5c01\u9501\u539f\u56e0\u548c\u6cd5\u5f8b\u4f9d\u636e<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u900f\u660e\u7684\u4e0a\u8bc9\u6d41\u7a0b<\/p>\n<\/li>\n<li>\n<p>\u53d1\u5e03\u900f\u660e\u5ea6\u62a5\u544a<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u7528\u6237\u9690\u79c1&#xff08;\u907f\u514d\u8fc7\u5ea6\u8ffd\u8e2a&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u66ff\u4ee3\u5185\u5bb9\u9009\u9879<\/p>\n<\/li>\n<\/ul>\n<h3>\u603b\u7ed3<\/h3>\n<p>\u672c\u7ae0\u8be6\u7ec6\u63a2\u8ba8\u4e8623\u4e2a&#034;\u5176\u4ed6&#034;4xx HTTP\u72b6\u6001\u7801&#xff0c;\u6bcf\u4e2a\u72b6\u6001\u7801\u90fd\u6709\u5176\u7279\u5b9a\u7684\u4f7f\u7528\u573a\u666f\u548c\u6700\u4f73\u5b9e\u8df5\u3002\u4ece\u652f\u4ed8\u7cfb\u7edf&#xff08;402&#xff09;\u5230\u6cd5\u5f8b\u5408\u89c4&#xff08;451&#xff09;&#xff0c;\u8fd9\u4e9b\u72b6\u6001\u7801\u4e3a\u73b0\u4ee3Web\u5f00\u53d1\u548cAPI\u8bbe\u8ba1\u63d0\u4f9b\u4e86\u4e30\u5bcc\u7684\u8bed\u4e49\u5de5\u5177\u3002<\/p>\n<h4>\u5173\u952e\u8981\u70b9&#xff1a;<\/h4>\n<li>\n<p>\u7cbe\u786e\u8bed\u4e49&#xff1a;\u4f7f\u7528\u6b63\u786e\u7684\u72b6\u6001\u7801\u53ef\u4ee5\u63d0\u4f9b\u66f4\u6e05\u6670\u7684\u9519\u8bef\u4fe1\u606f<\/p>\n<\/li>\n<li>\n<p>\u7528\u6237\u4f53\u9a8c&#xff1a;\u826f\u597d\u7684\u9519\u8bef\u54cd\u5e94\u53ef\u4ee5\u6307\u5bfc\u7528\u6237\u89e3\u51b3\u95ee\u9898<\/p>\n<\/li>\n<li>\n<p>\u5b89\u5168\u6027&#xff1a;\u9002\u5f53\u7684\u72b6\u6001\u7801\u6709\u52a9\u4e8e\u9632\u6b62\u6ee5\u7528\u548c\u653b\u51fb<\/p>\n<\/li>\n<li>\n<p>\u5408\u89c4\u6027&#xff1a;\u7279\u5b9a\u72b6\u6001\u7801&#xff08;\u5982451&#xff09;\u652f\u6301\u6cd5\u5f8b\u5408\u89c4\u8981\u6c42<\/p>\n<\/li>\n<li>\n<p>\u4e92\u64cd\u4f5c\u6027&#xff1a;\u6807\u51c6\u72b6\u6001\u7801\u4fc3\u8fdb\u7cfb\u7edf\u95f4\u7684\u4e92\u64cd\u4f5c\u6027<\/p>\n<\/li>\n<h4>\u5b9e\u65bd\u5efa\u8bae&#xff1a;<\/h4>\n<li>\n<p>\u6e10\u8fdb\u91c7\u7528&#xff1a;\u4ece\u6700\u91cd\u8981\u7684\u72b6\u6001\u7801\u5f00\u59cb&#xff0c;\u9010\u6b65\u5b9e\u73b0\u66f4\u591a<\/p>\n<\/li>\n<li>\n<p>\u6587\u6863\u5316&#xff1a;\u4e3aAPI\u6d88\u8d39\u8005\u63d0\u4f9b\u72b6\u6001\u7801\u7684\u8be6\u7ec6\u6587\u6863<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7&#xff1a;\u8ddf\u8e2a\u72b6\u6001\u7801\u5206\u5e03\u4ee5\u8bc6\u522b\u95ee\u9898<\/p>\n<\/li>\n<li>\n<p>\u6d4b\u8bd5&#xff1a;\u4e3a\u5404\u79cd\u9519\u8bef\u573a\u666f\u7f16\u5199\u6d4b\u8bd5\u7528\u4f8b<\/p>\n<\/li>\n<li>\n<p>\u5ba2\u6237\u7aef\u5904\u7406&#xff1a;\u786e\u4fdd\u5ba2\u6237\u7aef\u80fd\u4f18\u96c5\u5904\u7406\u6240\u6709\u72b6\u6001\u7801<\/p>\n<\/li>\n","protected":false},"excerpt":{"rendered":"<p>\u7b2c22\u7ae0&#xff1a;429 Too Many Requests &#8211; \u901f\u7387\u9650\u5236\u5b9e\u73b0\u6df1\u5ea6\u5206\u679022.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49429 Too Many Requests\u00a0\u72b6\u6001\u7801\u8868\u793a\u7528\u6237\u5728\u7ed9\u5b9a\u65f6\u95f4\u5185\u53d1\u9001\u4e86\u592a\u591a\u8bf7\u6c42&#xff08;\\&#8221;\u901f\u7387\u9650\u5236\\&#8221;&#xff09;\u3002\u8be5\u72b6\u6001\u7801\u7684\u6838\u5fc3\u542b\u4e49\u662f&#xff1a;\u5ba2\u6237\u7aef\u8bf7\u6c42\u9891\u7387\u8d85\u8fc7\u670d\u52a1\u5668\u9650\u5236\u670d\u52a1\u5668\u6682\u65f6\u62d2\u7edd\u5904\u7406\u5f53\u524d\u8bf7\u6c42\u5ba2\u6237\u7aef\u5e94\u8be5\u5728\u7b49\u5f85\u4e00\u6bb5\u65f6\u95f4\u540e\u91cd\u8bd5\u5173\u952e\u7279\u6027&#xff1a;\u901f\u7387\u9650\u5236&#xff1a;\u9650\u5236\u5355\u4f4d\u65f6\u95f4\u5185\u7684\u8bf7\u6c42\u6570\u91cf\u53ef\u6062\u590d\u9519\u8bef&#xff1a;\u7b49\u5f85\u4e00\u6bb5\u65f6\u95f4\u540e\u53ef\u6062\u590d<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[81,323,190],"topic":[],"class_list":["post-62984","post","type-post","status-publish","format-standard","hentry","category-server","tag-python","tag-323","tag-190"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u56db\u90e8\u5206\uff1a\u5ba2\u6237\u7aef\u9519\u8bef\u72b6\u6001\u7801\uff084xx\uff09\u6df1\u5ea6\u89e3\u8bfb\uff08\u4e09\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.wsisp.com\/helps\/62984.html\" \/>\n<meta property=\"og:locale\" content=\"zh_CN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u56db\u90e8\u5206\uff1a\u5ba2\u6237\u7aef\u9519\u8bef\u72b6\u6001\u7801\uff084xx\uff09\u6df1\u5ea6\u89e3\u8bfb\uff08\u4e09\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"og:description\" content=\"\u7b2c22\u7ae0&#xff1a;429 Too Many Requests - \u901f\u7387\u9650\u5236\u5b9e\u73b0\u6df1\u5ea6\u5206\u679022.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49429 Too Many Requests\u00a0\u72b6\u6001\u7801\u8868\u793a\u7528\u6237\u5728\u7ed9\u5b9a\u65f6\u95f4\u5185\u53d1\u9001\u4e86\u592a\u591a\u8bf7\u6c42&#xff08;&quot;\u901f\u7387\u9650\u5236&quot;&#xff09;\u3002\u8be5\u72b6\u6001\u7801\u7684\u6838\u5fc3\u542b\u4e49\u662f&#xff1a;\u5ba2\u6237\u7aef\u8bf7\u6c42\u9891\u7387\u8d85\u8fc7\u670d\u52a1\u5668\u9650\u5236\u670d\u52a1\u5668\u6682\u65f6\u62d2\u7edd\u5904\u7406\u5f53\u524d\u8bf7\u6c42\u5ba2\u6237\u7aef\u5e94\u8be5\u5728\u7b49\u5f85\u4e00\u6bb5\u65f6\u95f4\u540e\u91cd\u8bd5\u5173\u952e\u7279\u6027&#xff1a;\u901f\u7387\u9650\u5236&#xff1a;\u9650\u5236\u5355\u4f4d\u65f6\u95f4\u5185\u7684\u8bf7\u6c42\u6570\u91cf\u53ef\u6062\u590d\u9519\u8bef&#xff1a;\u7b49\u5f85\u4e00\u6bb5\u65f6\u95f4\u540e\u53ef\u6062\u590d\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.wsisp.com\/helps\/62984.html\" \/>\n<meta property=\"og:site_name\" content=\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"article:published_time\" content=\"2026-01-20T19:25:44+00:00\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u4f5c\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4\" \/>\n\t<meta name=\"twitter:data2\" content=\"77 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/62984.html\",\"url\":\"https:\/\/www.wsisp.com\/helps\/62984.html\",\"name\":\"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u56db\u90e8\u5206\uff1a\u5ba2\u6237\u7aef\u9519\u8bef\u72b6\u6001\u7801\uff084xx\uff09\u6df1\u5ea6\u89e3\u8bfb\uff08\u4e09\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"isPartOf\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\"},\"datePublished\":\"2026-01-20T19:25:44+00:00\",\"dateModified\":\"2026-01-20T19:25:44+00:00\",\"author\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/62984.html#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.wsisp.com\/helps\/62984.html\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/62984.html#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"\u9996\u9875\",\"item\":\"https:\/\/www.wsisp.com\/helps\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u56db\u90e8\u5206\uff1a\u5ba2\u6237\u7aef\u9519\u8bef\u72b6\u6001\u7801\uff084xx\uff09\u6df1\u5ea6\u89e3\u8bfb\uff08\u4e09\uff09\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\",\"url\":\"https:\/\/www.wsisp.com\/helps\/\",\"name\":\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"description\":\"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"zh-Hans\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"contentUrl\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"caption\":\"admin\"},\"sameAs\":[\"http:\/\/wp.wsisp.com\"],\"url\":\"https:\/\/www.wsisp.com\/helps\/author\/admin\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u56db\u90e8\u5206\uff1a\u5ba2\u6237\u7aef\u9519\u8bef\u72b6\u6001\u7801\uff084xx\uff09\u6df1\u5ea6\u89e3\u8bfb\uff08\u4e09\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.wsisp.com\/helps\/62984.html","og_locale":"zh_CN","og_type":"article","og_title":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u56db\u90e8\u5206\uff1a\u5ba2\u6237\u7aef\u9519\u8bef\u72b6\u6001\u7801\uff084xx\uff09\u6df1\u5ea6\u89e3\u8bfb\uff08\u4e09\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","og_description":"\u7b2c22\u7ae0&#xff1a;429 Too Many Requests - \u901f\u7387\u9650\u5236\u5b9e\u73b0\u6df1\u5ea6\u5206\u679022.1 \u5b9a\u4e49\u4e0e\u8bed\u4e49429 Too Many Requests\u00a0\u72b6\u6001\u7801\u8868\u793a\u7528\u6237\u5728\u7ed9\u5b9a\u65f6\u95f4\u5185\u53d1\u9001\u4e86\u592a\u591a\u8bf7\u6c42&#xff08;\"\u901f\u7387\u9650\u5236\"&#xff09;\u3002\u8be5\u72b6\u6001\u7801\u7684\u6838\u5fc3\u542b\u4e49\u662f&#xff1a;\u5ba2\u6237\u7aef\u8bf7\u6c42\u9891\u7387\u8d85\u8fc7\u670d\u52a1\u5668\u9650\u5236\u670d\u52a1\u5668\u6682\u65f6\u62d2\u7edd\u5904\u7406\u5f53\u524d\u8bf7\u6c42\u5ba2\u6237\u7aef\u5e94\u8be5\u5728\u7b49\u5f85\u4e00\u6bb5\u65f6\u95f4\u540e\u91cd\u8bd5\u5173\u952e\u7279\u6027&#xff1a;\u901f\u7387\u9650\u5236&#xff1a;\u9650\u5236\u5355\u4f4d\u65f6\u95f4\u5185\u7684\u8bf7\u6c42\u6570\u91cf\u53ef\u6062\u590d\u9519\u8bef&#xff1a;\u7b49\u5f85\u4e00\u6bb5\u65f6\u95f4\u540e\u53ef\u6062\u590d","og_url":"https:\/\/www.wsisp.com\/helps\/62984.html","og_site_name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","article_published_time":"2026-01-20T19:25:44+00:00","author":"admin","twitter_card":"summary_large_image","twitter_misc":{"\u4f5c\u8005":"admin","\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"77 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.wsisp.com\/helps\/62984.html","url":"https:\/\/www.wsisp.com\/helps\/62984.html","name":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u56db\u90e8\u5206\uff1a\u5ba2\u6237\u7aef\u9519\u8bef\u72b6\u6001\u7801\uff084xx\uff09\u6df1\u5ea6\u89e3\u8bfb\uff08\u4e09\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","isPartOf":{"@id":"https:\/\/www.wsisp.com\/helps\/#website"},"datePublished":"2026-01-20T19:25:44+00:00","dateModified":"2026-01-20T19:25:44+00:00","author":{"@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41"},"breadcrumb":{"@id":"https:\/\/www.wsisp.com\/helps\/62984.html#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.wsisp.com\/helps\/62984.html"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.wsisp.com\/helps\/62984.html#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"\u9996\u9875","item":"https:\/\/www.wsisp.com\/helps"},{"@type":"ListItem","position":2,"name":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u56db\u90e8\u5206\uff1a\u5ba2\u6237\u7aef\u9519\u8bef\u72b6\u6001\u7801\uff084xx\uff09\u6df1\u5ea6\u89e3\u8bfb\uff08\u4e09\uff09"}]},{"@type":"WebSite","@id":"https:\/\/www.wsisp.com\/helps\/#website","url":"https:\/\/www.wsisp.com\/helps\/","name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","description":"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"zh-Hans"},{"@type":"Person","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41","name":"admin","image":{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/","url":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","contentUrl":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","caption":"admin"},"sameAs":["http:\/\/wp.wsisp.com"],"url":"https:\/\/www.wsisp.com\/helps\/author\/admin"}]}},"_links":{"self":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/62984","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/comments?post=62984"}],"version-history":[{"count":0,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/62984\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media?parent=62984"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/categories?post=62984"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/tags?post=62984"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/topic?post=62984"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}