{"id":82295,"date":"2026-07-25T07:43:35","date_gmt":"2026-07-24T23:43:35","guid":{"rendered":"https:\/\/www.wsisp.com\/helps\/82295.html"},"modified":"2026-07-25T07:43:35","modified_gmt":"2026-07-24T23:43:35","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%e4%ba%8c%e9%83%a8","status":"publish","type":"post","link":"https:\/\/www.wsisp.com\/helps\/82295.html","title":{"rendered":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e8c\uff09"},"content":{"rendered":"<h2>\u7b2c8\u7ae0&#xff1a;206 Partial Content<\/h2>\n<h3>8.1 206 Partial Content\u7684\u8bed\u4e49\u4e0e\u4f7f\u7528\u573a\u666f<\/h3>\n<h4>RFC\u6807\u51c6\u5b9a\u4e49\u6df1\u5ea6\u89e3\u6790<\/h4>\n<p>\u6839\u636eRFC 7233\u7b2c4.1\u8282&#xff0c;206 Partial Content\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;<\/p>\n<p>&#034;206 (Partial Content)\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u6b63\u5728\u6210\u529f\u6267\u884c\u4e00\u4e2a\u6216\u591a\u4e2a\u5b57\u8282\u8303\u56f4\u8bf7\u6c42&#xff0c;\u56e0\u4e3a\u8bf7\u6c42\u5305\u542bRange\u8bf7\u6c42\u5934\u90e8\u5b57\u6bb5&#xff08;\u7b2c3.1\u8282&#xff09;\u3002\u54cd\u5e94\u5fc5\u987b\u5305\u542bContent-Range\u5934\u90e8\u5b57\u6bb5&#xff08;\u7b2c4.2\u8282&#xff09;&#xff0c;\u8bf4\u660e\u54cd\u5e94\u8868\u793a\u4e2d\u5305\u542b\u7684\u8303\u56f4&#xff0c;\u6216\u8005\u4f7f\u7528multipart\/byteranges\u5a92\u4f53\u7c7b\u578b\u548c\u6bcf\u4e2a\u90e8\u5206\u90fd\u5305\u542b\u81ea\u5df1\u7684Content-Range\u5b57\u6bb5\u3002&#034;<\/p>\n<h5>\u6838\u5fc3\u8bed\u4e49\u7279\u5f81<\/h5>\n<li>\n<p>\u8303\u56f4\u8bf7\u6c42\u652f\u6301&#xff1a;\u5ba2\u6237\u7aef\u53ef\u4ee5\u8bf7\u6c42\u8d44\u6e90\u7684\u7279\u5b9a\u90e8\u5206<\/p>\n<\/li>\n<li>\n<p>\u591a\u8303\u56f4\u54cd\u5e94&#xff1a;\u53ef\u4ee5\u540c\u65f6\u8bf7\u6c42\u591a\u4e2a\u4e0d\u8fde\u7eed\u7684\u8303\u56f4<\/p>\n<\/li>\n<li>\n<p>\u6062\u590d\u4e0b\u8f7d&#xff1a;\u652f\u6301\u65ad\u70b9\u7eed\u4f20\u548c\u5e76\u884c\u4e0b\u8f7d<\/p>\n<\/li>\n<li>\n<p>\u9ad8\u6548\u4f20\u8f93&#xff1a;\u51cf\u5c11\u4e0d\u5fc5\u8981\u7684\u6570\u636e\u4f20\u8f93<\/p>\n<\/li>\n<h4>\u9002\u7528\u573a\u666f\u77e9\u9635<\/h4>\n<p>python<\/p>\n<p># 206 Partial Content\u9002\u7528\u573a\u666f\u5206\u6790\u5668<br \/>\nclass PartialContentUseCaseAnalyzer:<br \/>\n    &#034;&#034;&#034;\u5206\u6790206\u72b6\u6001\u7801\u7684\u9002\u7528\u573a\u666f&#034;&#034;&#034;<\/p>\n<p>    SCENARIOS &#061; {<br \/>\n        &#034;resumable_downloads&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u53ef\u6062\u590d\u7684\u6587\u4ef6\u4e0b\u8f7d&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u5927\u6587\u4ef6\u4e0b\u8f7d\u4e2d\u65ad\u540e\u6062\u590d&#034;,<br \/>\n                &#034;\u7f51\u7edc\u4e0d\u7a33\u5b9a\u65f6\u7684\u5206\u7247\u4e0b\u8f7d&#034;,<br \/>\n                &#034;\u6d4f\u89c8\u5668\u4e0b\u8f7d\u7ba1\u7406\u5668&#034;,<br \/>\n                &#034;\u79fb\u52a8\u5e94\u7528\u4e2d\u7684\u5a92\u4f53\u4e0b\u8f7d&#034;<br \/>\n            ],<br \/>\n            &#034;range_type&#034;: &#034;\u5355\u8303\u56f4\u6216\u8fde\u7eed\u8303\u56f4&#034;,<br \/>\n            &#034;typical_usage&#034;: &#034;Range: bytes&#061;1000-2000&#034;<br \/>\n        },<br \/>\n        &#034;streaming_media&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u6d41\u5a92\u4f53\u64ad\u653e&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u89c6\u9891\u64ad\u653e\u5668\u8df3\u8f6c\u65f6\u95f4\u70b9&#034;,<br \/>\n                &#034;\u97f3\u9891\u64ad\u653e\u5668\u968f\u673a\u8bbf\u95ee&#034;,<br \/>\n                &#034;\u5728\u7ebf\u89c6\u9891\u9884\u89c8&#034;,<br \/>\n                &#034;\u6d41\u5a92\u4f53\u670d\u52a1\u5668&#034;<br \/>\n            ],<br \/>\n            &#034;range_type&#034;: &#034;\u5355\u8303\u56f4&#034;,<br \/>\n            &#034;typical_usage&#034;: &#034;Range: bytes&#061;0-1023 (\u521d\u59cb\u7247\u6bb5)&#034;<br \/>\n        },<br \/>\n        &#034;parallel_downloads&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u5e76\u884c\u4e0b\u8f7d\u52a0\u901f&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u4e0b\u8f7d\u7ba1\u7406\u5668\u5206\u5757\u4e0b\u8f7d&#034;,<br \/>\n                &#034;BitTorrent\u5ba2\u6237\u7aef&#034;,<br \/>\n                &#034;\u591a\u7ebf\u7a0b\u4e0b\u8f7d\u5de5\u5177&#034;,<br \/>\n                &#034;CDN\u8fb9\u7f18\u8282\u70b9\u540c\u6b65&#034;<br \/>\n            ],<br \/>\n            &#034;range_type&#034;: &#034;\u591a\u8303\u56f4&#034;,<br \/>\n            &#034;typical_usage&#034;: &#034;Range: bytes&#061;0-999,2000-2999,5000-5999&#034;<br \/>\n        },<br \/>\n        &#034;data_sampling&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u5927\u6570\u636e\u91c7\u6837&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u5927\u578b\u65e5\u5fd7\u6587\u4ef6\u67e5\u770b&#034;,<br \/>\n                &#034;\u6570\u636e\u5e93\u5907\u4efd\u6587\u4ef6\u68c0\u67e5&#034;,<br \/>\n                &#034;\u5a92\u4f53\u6587\u4ef6\u5143\u6570\u636e\u8bfb\u53d6&#034;,<br \/>\n                &#034;\u6587\u4ef6\u683c\u5f0f\u9a8c\u8bc1&#034;<br \/>\n            ],<br \/>\n            &#034;range_type&#034;: &#034;\u5355\u8303\u56f4&#034;,<br \/>\n            &#034;typical_usage&#034;: &#034;Range: bytes&#061;0-511 (\u8bfb\u53d6\u6587\u4ef6\u5934)&#034;<br \/>\n        },<br \/>\n        &#034;efficient_updates&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u9ad8\u6548\u589e\u91cf\u66f4\u65b0&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u6e38\u620f\u8865\u4e01\u4e0b\u8f7d&#034;,<br \/>\n                &#034;\u8f6f\u4ef6\u589e\u91cf\u66f4\u65b0&#034;,<br \/>\n                &#034;\u6570\u636e\u96c6\u589e\u91cf\u540c\u6b65&#034;,<br \/>\n                &#034;\u6587\u6863\u7248\u672c\u5dee\u5f02&#034;<br \/>\n            ],<br \/>\n            &#034;range_type&#034;: &#034;\u5355\u8303\u56f4\u6216\u591a\u4e2a\u5355\u8303\u56f4&#034;,<br \/>\n            &#034;typical_usage&#034;: &#034;Range: bytes&#061;5000-&#034;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    def analyze_request(self, request_headers, resource_size):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u5206\u6790\u8303\u56f4\u8bf7\u6c42<\/p>\n<p>        Args:<br \/>\n            request_headers: \u8bf7\u6c42\u5934\u90e8\u5b57\u5178<br \/>\n            resource_size: \u8d44\u6e90\u603b\u5927\u5c0f&#xff08;\u5b57\u8282&#xff09;<\/p>\n<p>        Returns:<br \/>\n            \u5206\u6790\u7ed3\u679c\u5b57\u5178<br \/>\n        &#034;&#034;&#034;<br \/>\n        range_header &#061; request_headers.get(&#039;Range&#039;)<\/p>\n<p>        if not range_header:<br \/>\n            return {<br \/>\n                &#039;is_range_request&#039;: False,<br \/>\n                &#039;recommendation&#039;: &#039;\u4f7f\u7528\u5e38\u89c4\u8bf7\u6c42&#039;<br \/>\n            }<\/p>\n<p>        try:<br \/>\n            parsed_ranges &#061; self._parse_range_header(range_header, resource_size)<\/p>\n<p>            analysis &#061; {<br \/>\n                &#039;is_range_request&#039;: True,<br \/>\n                &#039;range_type&#039;: &#039;single&#039; if len(parsed_ranges) &#061;&#061; 1 else &#039;multiple&#039;,<br \/>\n                &#039;ranges&#039;: parsed_ranges,<br \/>\n                &#039;total_size&#039;: resource_size,<br \/>\n                &#039;recommended_response&#039;: self._recommend_response_type(parsed_ranges)<br \/>\n            }<\/p>\n<p>            # \u68c0\u67e5\u573a\u666f\u9002\u914d\u6027<br \/>\n            analysis[&#039;scenarios&#039;] &#061; self._match_scenarios(parsed_ranges, resource_size)<\/p>\n<p>            return analysis<\/p>\n<p>        except ValueError as e:<br \/>\n            return {<br \/>\n                &#039;is_range_request&#039;: True,<br \/>\n                &#039;error&#039;: str(e),<br \/>\n                &#039;recommendation&#039;: &#039;\u8fd4\u56de416 Range Not Satisfiable&#039;<br \/>\n            }<\/p>\n<p>    def _parse_range_header(self, range_header, resource_size):<br \/>\n        &#034;&#034;&#034;\u89e3\u6790Range\u5934\u90e8&#034;&#034;&#034;<br \/>\n        if not range_header.startswith(&#039;bytes&#061;&#039;):<br \/>\n            raise ValueError(&#039;Only byte ranges are supported&#039;)<\/p>\n<p>        ranges_str &#061; range_header[6:]  # \u79fb\u9664&#039;bytes&#061;&#039;<br \/>\n        range_specs &#061; ranges_str.split(&#039;,&#039;)<\/p>\n<p>        parsed_ranges &#061; []<\/p>\n<p>        for spec in range_specs:<br \/>\n            spec &#061; spec.strip()<\/p>\n<p>            if &#039;-&#039; not in spec:<br \/>\n                raise ValueError(f&#039;Invalid range spec: {spec}&#039;)<\/p>\n<p>            start_str, end_str &#061; spec.split(&#039;-&#039;, 1)<\/p>\n<p>            if start_str &#061;&#061; &#039;&#039; and end_str &#061;&#061; &#039;&#039;:<br \/>\n                raise ValueError(&#039;Empty range spec&#039;)<\/p>\n<p>            # \u5904\u7406\u4e0d\u540c\u7c7b\u578b\u7684\u8303\u56f4<br \/>\n            if start_str &#061;&#061; &#039;&#039;:<br \/>\n                # suffix-byte-range-spec: \u6700\u540eN\u5b57\u8282<br \/>\n                suffix_length &#061; int(end_str)<br \/>\n                if suffix_length &lt;&#061; 0:<br \/>\n                    raise ValueError(&#039;Suffix length must be positive&#039;)<\/p>\n<p>                start &#061; max(0, resource_size &#8211; suffix_length)<br \/>\n                end &#061; resource_size &#8211; 1<\/p>\n<p>            elif end_str &#061;&#061; &#039;&#039;:<br \/>\n                # \u4ecestart\u5230\u7ed3\u5c3e<br \/>\n                start &#061; int(start_str)<br \/>\n                if start &gt;&#061; resource_size:<br \/>\n                    raise ValueError(f&#039;Start {start} beyond resource size {resource_size}&#039;)<br \/>\n                end &#061; resource_size &#8211; 1<\/p>\n<p>            else:<br \/>\n                # \u5b8c\u6574\u7684\u8303\u56f4<br \/>\n                start &#061; int(start_str)<br \/>\n                end &#061; int(end_str)<\/p>\n<p>                if start &gt; end:<br \/>\n                    raise ValueError(f&#039;Start {start} greater than end {end}&#039;)<\/p>\n<p>                if start &gt;&#061; resource_size:<br \/>\n                    raise ValueError(f&#039;Start {start} beyond resource size {resource_size}&#039;)<\/p>\n<p>            # \u8c03\u6574\u7ed3\u675f\u4f4d\u7f6e\u4e0d\u8d85\u8fc7\u8d44\u6e90\u5927\u5c0f<br \/>\n            end &#061; min(end, resource_size &#8211; 1)<\/p>\n<p>            # \u786e\u4fdd\u8303\u56f4\u6709\u6548<br \/>\n            if start &lt; 0 or end &lt; 0 or start &gt; end:<br \/>\n                raise ValueError(f&#039;Invalid range: {start}-{end}&#039;)<\/p>\n<p>            parsed_ranges.append({<br \/>\n                &#039;start&#039;: start,<br \/>\n                &#039;end&#039;: end,<br \/>\n                &#039;length&#039;: end &#8211; start &#043; 1,<br \/>\n                &#039;spec&#039;: spec<br \/>\n            })<\/p>\n<p>        return parsed_ranges<\/p>\n<p>    def _recommend_response_type(self, ranges):<br \/>\n        &#034;&#034;&#034;\u63a8\u8350\u54cd\u5e94\u7c7b\u578b&#034;&#034;&#034;<br \/>\n        if len(ranges) &#061;&#061; 1:<br \/>\n            return {<br \/>\n                &#039;status&#039;: 206,<br \/>\n                &#039;content_type&#039;: &#039;application\/octet-stream&#039;,<br \/>\n                &#039;headers&#039;: [&#039;Content-Range&#039;],<br \/>\n                &#039;body_type&#039;: &#039;single_part&#039;<br \/>\n            }<br \/>\n        else:<br \/>\n            return {<br \/>\n                &#039;status&#039;: 206,<br \/>\n                &#039;content_type&#039;: &#039;multipart\/byteranges; boundary&#061;&#039;,<br \/>\n                &#039;headers&#039;: [&#039;Content-Type&#039;, &#039;Content-Length&#039;],<br \/>\n                &#039;body_type&#039;: &#039;multipart&#039;<br \/>\n            }<\/p>\n<p>    def _match_scenarios(self, ranges, resource_size):<br \/>\n        &#034;&#034;&#034;\u5339\u914d\u9002\u7528\u573a\u666f&#034;&#034;&#034;<br \/>\n        matched &#061; []<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u9002\u5408\u6d41\u5a92\u4f53<br \/>\n        if len(ranges) &#061;&#061; 1:<br \/>\n            range_length &#061; ranges[0][&#039;length&#039;]<br \/>\n            if range_length &lt;&#061; 1024 * 1024:  # 1MB\u4ee5\u4e0b<br \/>\n                matched.append(&#039;streaming_media&#039;)<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u9002\u5408\u5e76\u884c\u4e0b\u8f7d<br \/>\n        if len(ranges) &gt; 1:<br \/>\n            matched.append(&#039;parallel_downloads&#039;)<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u9002\u5408\u6062\u590d\u4e0b\u8f7d<br \/>\n        for r in ranges:<br \/>\n            if r[&#039;start&#039;] &gt; 0:<br \/>\n                matched.append(&#039;resumable_downloads&#039;)<br \/>\n                break<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u9002\u5408\u6570\u636e\u91c7\u6837<br \/>\n        if len(ranges) &#061;&#061; 1 and ranges[0][&#039;length&#039;] &lt; 1024:  # \u5c0f\u4e8e1KB<br \/>\n            matched.append(&#039;data_sampling&#039;)<\/p>\n<p>        return list(set(matched))  # \u53bb\u91cd<\/p>\n<h3>8.2 Range\u548cContent-Range\u5934\u90e8\u8be6\u89e3<\/h3>\n<h4>Range\u5934\u90e8\u683c\u5f0f\u89c4\u8303<\/h4>\n<p>python<\/p>\n<p># Range\u5934\u90e8\u89e3\u6790\u5668<br \/>\nclass RangeHeaderParser:<br \/>\n    &#034;&#034;&#034;\u89e3\u6790\u548c\u9a8c\u8bc1Range\u8bf7\u6c42\u5934\u90e8&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, max_ranges&#061;10, max_range_length&#061;100*1024*1024):  # 100MB<br \/>\n        self.max_ranges &#061; max_ranges<br \/>\n        self.max_range_length &#061; max_range_length<\/p>\n<p>    def parse(self, range_header, content_length):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u89e3\u6790Range\u5934\u90e8<\/p>\n<p>        Args:<br \/>\n            range_header: Range\u5934\u90e8\u503c<br \/>\n            content_length: \u5185\u5bb9\u957f\u5ea6<\/p>\n<p>        Returns:<br \/>\n            {<br \/>\n                &#039;valid&#039;: bool,<br \/>\n                &#039;ranges&#039;: list,<br \/>\n                &#039;error&#039;: str,<br \/>\n                &#039;content_range&#039;: str (\u7528\u4e8e416\u54cd\u5e94)<br \/>\n            }<br \/>\n        &#034;&#034;&#034;<br \/>\n        if not range_header:<br \/>\n            return {<br \/>\n                &#039;valid&#039;: False,<br \/>\n                &#039;error&#039;: &#039;No Range header provided&#039;,<br \/>\n                &#039;content_range&#039;: f&#039;bytes *\/{content_length}&#039;<br \/>\n            }<\/p>\n<p>        if not range_header.startswith(&#039;bytes&#061;&#039;):<br \/>\n            return {<br \/>\n                &#039;valid&#039;: False,<br \/>\n                &#039;error&#039;: &#039;Only byte ranges are supported&#039;,<br \/>\n                &#039;content_range&#039;: f&#039;bytes *\/{content_length}&#039;<br \/>\n            }<\/p>\n<p>        # \u63d0\u53d6\u8303\u56f4\u89c4\u683c<br \/>\n        ranges_str &#061; range_header[6:]  # \u79fb\u9664&#039;bytes&#061;&#039;<br \/>\n        range_specs &#061; [s.strip() for s in ranges_str.split(&#039;,&#039;)]<\/p>\n<p>        # \u68c0\u67e5\u6570\u91cf\u9650\u5236<br \/>\n        if len(range_specs) &gt; self.max_ranges:<br \/>\n            return {<br \/>\n                &#039;valid&#039;: False,<br \/>\n                &#039;error&#039;: f&#039;Too many ranges (max {self.max_ranges})&#039;,<br \/>\n                &#039;content_range&#039;: f&#039;bytes *\/{content_length}&#039;<br \/>\n            }<\/p>\n<p>        parsed_ranges &#061; []<br \/>\n        valid_ranges &#061; []<\/p>\n<p>        for i, spec in enumerate(range_specs):<br \/>\n            result &#061; self._parse_single_range(spec, content_length)<\/p>\n<p>            if not result[&#039;valid&#039;]:<br \/>\n                # \u5355\u4e2a\u8303\u56f4\u65e0\u6548&#xff0c;\u6574\u4e2a\u8bf7\u6c42\u65e0\u6548<br \/>\n                return {<br \/>\n                    &#039;valid&#039;: False,<br \/>\n                    &#039;error&#039;: f&#039;Invalid range spec {i&#043;1}: {result[&#034;error&#034;]}&#039;,<br \/>\n                    &#039;content_range&#039;: f&#039;bytes *\/{content_length}&#039;<br \/>\n                }<\/p>\n<p>            parsed_ranges.append(result)<\/p>\n<p>            # \u68c0\u67e5\u8303\u56f4\u957f\u5ea6\u9650\u5236<br \/>\n            if result[&#039;length&#039;] &gt; self.max_range_length:<br \/>\n                return {<br \/>\n                    &#039;valid&#039;: False,<br \/>\n                    &#039;error&#039;: f&#039;Range {i&#043;1} too large (max {self.max_range_length} bytes)&#039;,<br \/>\n                    &#039;content_range&#039;: f&#039;bytes *\/{content_length}&#039;<br \/>\n                }<\/p>\n<p>            # \u6dfb\u52a0\u5230\u6709\u6548\u8303\u56f4\u5217\u8868<br \/>\n            valid_ranges.append({<br \/>\n                &#039;start&#039;: result[&#039;start&#039;],<br \/>\n                &#039;end&#039;: result[&#039;end&#039;],<br \/>\n                &#039;length&#039;: result[&#039;length&#039;]<br \/>\n            })<\/p>\n<p>        # \u68c0\u67e5\u8303\u56f4\u91cd\u53e0\u548c\u6392\u5e8f<br \/>\n        sorted_ranges &#061; sorted(valid_ranges, key&#061;lambda x: x[&#039;start&#039;])<br \/>\n        for i in range(1, len(sorted_ranges)):<br \/>\n            if sorted_ranges[i][&#039;start&#039;] &lt;&#061; sorted_ranges[i-1][&#039;end&#039;]:<br \/>\n                return {<br \/>\n                    &#039;valid&#039;: False,<br \/>\n                    &#039;error&#039;: f&#039;Overlapping ranges: {sorted_ranges[i-1]} and {sorted_ranges[i]}&#039;,<br \/>\n                    &#039;content_range&#039;: f&#039;bytes *\/{content_length}&#039;<br \/>\n                }<\/p>\n<p>        return {<br \/>\n            &#039;valid&#039;: True,<br \/>\n            &#039;ranges&#039;: sorted_ranges,<br \/>\n            &#039;total_length&#039;: content_length,<br \/>\n            &#039;requested_ranges&#039;: len(range_specs)<br \/>\n        }<\/p>\n<p>    def _parse_single_range(self, spec, content_length):<br \/>\n        &#034;&#034;&#034;\u89e3\u6790\u5355\u4e2a\u8303\u56f4\u89c4\u683c&#034;&#034;&#034;<\/p>\n<p>        if &#039;-&#039; not in spec:<br \/>\n            return {<br \/>\n                &#039;valid&#039;: False,<br \/>\n                &#039;error&#039;: f&#039;Invalid format: {spec}&#039;<br \/>\n            }<\/p>\n<p>        start_str, end_str &#061; spec.split(&#039;-&#039;, 1)<\/p>\n<p>        try:<br \/>\n            # \u540e\u7f00\u5b57\u8282\u8303\u56f4\u89c4\u683c (\u4f8b\u5982: -500)<br \/>\n            if start_str &#061;&#061; &#039;&#039;:<br \/>\n                if not end_str.isdigit():<br \/>\n                    return {<br \/>\n                        &#039;valid&#039;: False,<br \/>\n                        &#039;error&#039;: f&#039;Invalid suffix: {end_str}&#039;<br \/>\n                    }<\/p>\n<p>                suffix_length &#061; int(end_str)<br \/>\n                if suffix_length &lt;&#061; 0:<br \/>\n                    return {<br \/>\n                        &#039;valid&#039;: False,<br \/>\n                        &#039;error&#039;: f&#039;Suffix must be positive: {suffix_length}&#039;<br \/>\n                    }<\/p>\n<p>                start &#061; max(0, content_length &#8211; suffix_length)<br \/>\n                end &#061; content_length &#8211; 1<\/p>\n<p>            # \u5f00\u59cb\u5230\u7ed3\u675f\u7684\u8303\u56f4 (\u4f8b\u5982: 1000-2000)<br \/>\n            elif end_str &#061;&#061; &#039;&#039;:<br \/>\n                if not start_str.isdigit():<br \/>\n                    return {<br \/>\n                        &#039;valid&#039;: False,<br \/>\n                        &#039;error&#039;: f&#039;Invalid start: {start_str}&#039;<br \/>\n                    }<\/p>\n<p>                start &#061; int(start_str)<br \/>\n                if start &gt;&#061; content_length:<br \/>\n                    return {<br \/>\n                        &#039;valid&#039;: False,<br \/>\n                        &#039;error&#039;: f&#039;Start {start} beyond content length {content_length}&#039;<br \/>\n                    }<br \/>\n                end &#061; content_length &#8211; 1<\/p>\n<p>            # \u5b8c\u6574\u7684\u8303\u56f4\u89c4\u683c<br \/>\n            else:<br \/>\n                if not (start_str.isdigit() and end_str.isdigit()):<br \/>\n                    return {<br \/>\n                        &#039;valid&#039;: False,<br \/>\n                        &#039;error&#039;: f&#039;Invalid start or end: {start_str}-{end_str}&#039;<br \/>\n                    }<\/p>\n<p>                start &#061; int(start_str)<br \/>\n                end &#061; int(end_str)<\/p>\n<p>                if start &gt; end:<br \/>\n                    return {<br \/>\n                        &#039;valid&#039;: False,<br \/>\n                        &#039;error&#039;: f&#039;Start {start} greater than end {end}&#039;<br \/>\n                    }<\/p>\n<p>                if start &gt;&#061; content_length:<br \/>\n                    return {<br \/>\n                        &#039;valid&#039;: False,<br \/>\n                        &#039;error&#039;: f&#039;Start {start} beyond content length {content_length}&#039;<br \/>\n                    }<\/p>\n<p>            # \u8c03\u6574\u7ed3\u675f\u4f4d\u7f6e<br \/>\n            end &#061; min(end, content_length &#8211; 1)<\/p>\n<p>            # \u6700\u7ec8\u9a8c\u8bc1<br \/>\n            if start &lt; 0 or end &lt; 0 or start &gt; end:<br \/>\n                return {<br \/>\n                    &#039;valid&#039;: False,<br \/>\n                    &#039;error&#039;: f&#039;Invalid range: {start}-{end}&#039;<br \/>\n                }<\/p>\n<p>            return {<br \/>\n                &#039;valid&#039;: True,<br \/>\n                &#039;start&#039;: start,<br \/>\n                &#039;end&#039;: end,<br \/>\n                &#039;length&#039;: end &#8211; start &#043; 1,<br \/>\n                &#039;spec&#039;: spec<br \/>\n            }<\/p>\n<p>        except ValueError:<br \/>\n            return {<br \/>\n                &#039;valid&#039;: False,<br \/>\n                &#039;error&#039;: f&#039;Invalid range spec: {spec}&#039;<br \/>\n            }<\/p>\n<p>    def generate_content_range(self, start, end, total_length):<br \/>\n        &#034;&#034;&#034;\u751f\u6210Content-Range\u5934\u90e8\u503c&#034;&#034;&#034;<br \/>\n        return f&#039;bytes {start}-{end}\/{total_length}&#039;<\/p>\n<p>    def generate_416_content_range(self, total_length):<br \/>\n        &#034;&#034;&#034;\u751f\u6210416\u54cd\u5e94\u7684Content-Range\u5934\u90e8\u503c&#034;&#034;&#034;<br \/>\n        return f&#039;bytes *\/{total_length}&#039;<\/p>\n<p># \u4f7f\u7528\u793a\u4f8b<br \/>\nparser &#061; RangeHeaderParser()<\/p>\n<p># \u6d4b\u8bd5\u5404\u79cdRange\u5934\u90e8<br \/>\ntest_cases &#061; [<br \/>\n    (&#034;bytes&#061;0-499&#034;, 1000),<br \/>\n    (&#034;bytes&#061;500-999&#034;, 1000),<br \/>\n    (&#034;bytes&#061;-500&#034;, 1000),  # \u6700\u540e500\u5b57\u8282<br \/>\n    (&#034;bytes&#061;950-&#034;, 1000),  # \u4ece950\u5230\u7ed3\u5c3e<br \/>\n    (&#034;bytes&#061;0-0&#034;, 1000),   # \u7b2c\u4e00\u4e2a\u5b57\u8282<br \/>\n    (&#034;bytes&#061;2-5,10-15&#034;, 1000),  # \u591a\u4e2a\u8303\u56f4<br \/>\n    (&#034;bytes&#061;1000-2000&#034;, 500),   # \u65e0\u6548&#xff1a;\u5f00\u59cb\u8d85\u51fa\u8303\u56f4<br \/>\n    (&#034;bytes&#061;200-100&#034;, 1000),    # \u65e0\u6548&#xff1a;\u5f00\u59cb&gt;\u7ed3\u675f<br \/>\n    (&#034;bytes&#061;abc-def&#034;, 1000),    # \u65e0\u6548&#xff1a;\u975e\u6570\u5b57<br \/>\n]<\/p>\n<p>for range_header, content_length in test_cases:<br \/>\n    print(f&#034;\\\\nRange: {range_header}, Content-Length: {content_length}&#034;)<br \/>\n    result &#061; parser.parse(range_header, content_length)<\/p>\n<p>    if result[&#039;valid&#039;]:<br \/>\n        print(f&#034;  \u6709\u6548\u8303\u56f4: {result[&#039;ranges&#039;]}&#034;)<br \/>\n    else:<br \/>\n        print(f&#034;  \u65e0\u6548: {result[&#039;error&#039;]}&#034;)<br \/>\n        print(f&#034;  416 Content-Range: {result.get(&#039;content_range&#039;, &#039;N\/A&#039;)}&#034;)<\/p>\n<h4>Content-Range\u5934\u90e8\u683c\u5f0f<\/h4>\n<p>python<\/p>\n<p># Content-Range\u5934\u90e8\u751f\u6210\u5668<br \/>\nclass ContentRangeHeaderBuilder:<br \/>\n    &#034;&#034;&#034;\u751f\u6210Content-Range\u54cd\u5e94\u5934\u90e8&#034;&#034;&#034;<\/p>\n<p>    &#064;staticmethod<br \/>\n    def for_single_range(start, end, total_length):<br \/>\n        &#034;&#034;&#034;\u5355\u4e2a\u8303\u56f4\u7684Content-Range&#034;&#034;&#034;<br \/>\n        if start &lt; 0 or end &lt; 0 or start &gt; end or end &gt;&#061; total_length:<br \/>\n            raise ValueError(f&#034;Invalid range: {start}-{end}\/{total_length}&#034;)<\/p>\n<p>        return f&#034;bytes {start}-{end}\/{total_length}&#034;<\/p>\n<p>    &#064;staticmethod<br \/>\n    def for_multipart_boundary(boundary):<br \/>\n        &#034;&#034;&#034;\u591a\u90e8\u5206\u54cd\u5e94\u7684Content-Type&#034;&#034;&#034;<br \/>\n        return f&#034;multipart\/byteranges; boundary&#061;{boundary}&#034;<\/p>\n<p>    &#064;staticmethod<br \/>\n    def for_part_in_multipart(start, end, total_length, content_type&#061;&#034;application\/octet-stream&#034;):<br \/>\n        &#034;&#034;&#034;\u591a\u90e8\u5206\u54cd\u5e94\u4e2d\u6bcf\u4e2a\u90e8\u5206\u7684\u5934\u90e8&#034;&#034;&#034;<br \/>\n        headers &#061; [<br \/>\n            f&#034;Content-Range: bytes {start}-{end}\/{total_length}&#034;,<br \/>\n            f&#034;Content-Type: {content_type}&#034;<br \/>\n        ]<br \/>\n        return &#034;\\\\r\\\\n&#034;.join(headers)<\/p>\n<p>    &#064;staticmethod<br \/>\n    def for_unsatisfiable_range(total_length):<br \/>\n        &#034;&#034;&#034;416\u54cd\u5e94\u7684Content-Range&#034;&#034;&#034;<br \/>\n        return f&#034;bytes *\/{total_length}&#034;<\/p>\n<p># \u5b8c\u6574206\u54cd\u5e94\u751f\u6210\u5668<br \/>\nclass PartialContentResponseBuilder:<br \/>\n    &#034;&#034;&#034;\u6784\u5efa206 Partial Content\u54cd\u5e94&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, resource_path, resource_size, mime_type&#061;&#034;application\/octet-stream&#034;):<br \/>\n        self.resource_path &#061; resource_path<br \/>\n        self.resource_size &#061; resource_size<br \/>\n        self.mime_type &#061; mime_type<br \/>\n        self.range_parser &#061; RangeHeaderParser()<\/p>\n<p>    def build_response(self, range_header, request_headers&#061;None):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u6784\u5efa206\u54cd\u5e94<\/p>\n<p>        Args:<br \/>\n            range_header: Range\u8bf7\u6c42\u5934\u90e8\u503c<br \/>\n            request_headers: \u5b8c\u6574\u7684\u8bf7\u6c42\u5934\u90e8\u5b57\u5178<\/p>\n<p>        Returns:<br \/>\n            (status_code, headers, body_generator)<br \/>\n        &#034;&#034;&#034;<br \/>\n        # \u89e3\u6790Range\u5934\u90e8<br \/>\n        parse_result &#061; self.range_parser.parse(range_header, self.resource_size)<\/p>\n<p>        if not parse_result[&#039;valid&#039;]:<br \/>\n            # \u8fd4\u56de416 Range Not Satisfiable<br \/>\n            return 416, {<br \/>\n                &#039;Content-Range&#039;: parse_result.get(&#039;content_range&#039;, f&#039;bytes *\/{self.resource_size}&#039;),<br \/>\n                &#039;Content-Type&#039;: &#039;text\/plain&#039;<br \/>\n            }, b&#039;Requested range not satisfiable&#039;<\/p>\n<p>        ranges &#061; parse_result[&#039;ranges&#039;]<\/p>\n<p>        # \u5982\u679c\u662f\u5355\u4e2a\u8303\u56f4<br \/>\n        if len(ranges) &#061;&#061; 1:<br \/>\n            return self._build_single_range_response(ranges[0], request_headers)<br \/>\n        else:<br \/>\n            return self._build_multipart_response(ranges, request_headers)<\/p>\n<p>    def _build_single_range_response(self, range_info, request_headers):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa\u5355\u4e2a\u8303\u56f4\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        start &#061; range_info[&#039;start&#039;]<br \/>\n        end &#061; range_info[&#039;end&#039;]<br \/>\n        length &#061; range_info[&#039;length&#039;]<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Content-Range&#039;: ContentRangeHeaderBuilder.for_single_range(<br \/>\n                start, end, self.resource_size<br \/>\n            ),<br \/>\n            &#039;Content-Length&#039;: str(length),<br \/>\n            &#039;Content-Type&#039;: self.mime_type,<br \/>\n            &#039;Accept-Ranges&#039;: &#039;bytes&#039;,<br \/>\n            &#039;ETag&#039;: self._generate_etag(),<br \/>\n            &#039;Last-Modified&#039;: self._get_last_modified(),<br \/>\n            &#039;Cache-Control&#039;: &#039;public, max-age&#061;31536000&#039;,  # \u53ef\u7f13\u5b581\u5e74<br \/>\n        }<\/p>\n<p>        # \u6dfb\u52a0\u6761\u4ef6\u8bf7\u6c42\u652f\u6301<br \/>\n        if request_headers:<br \/>\n            self._add_conditional_headers(headers, request_headers)<\/p>\n<p>        # \u521b\u5efa\u751f\u6210\u5668\u6765\u8bfb\u53d6\u6587\u4ef6\u90e8\u5206<br \/>\n        def body_generator():<br \/>\n            with open(self.resource_path, &#039;rb&#039;) as f:<br \/>\n                f.seek(start)<br \/>\n                remaining &#061; length<br \/>\n                chunk_size &#061; 64 * 1024  # 64KB\u5757<\/p>\n<p>                while remaining &gt; 0:<br \/>\n                    read_size &#061; min(chunk_size, remaining)<br \/>\n                    chunk &#061; f.read(read_size)<br \/>\n                    if not chunk:<br \/>\n                        break<br \/>\n                    yield chunk<br \/>\n                    remaining -&#061; len(chunk)<\/p>\n<p>        return 206, headers, body_generator()<\/p>\n<p>    def _build_multipart_response(self, ranges, request_headers):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa\u591a\u90e8\u5206\u8303\u56f4\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        boundary &#061; f&#034;boundary_{hash(str(ranges))}_{int(time.time())}&#034;<br \/>\n        content_type &#061; f&#034;multipart\/byteranges; boundary&#061;{boundary}&#034;<\/p>\n<p>        # \u9996\u5148\u8ba1\u7b97\u603b\u957f\u5ea6<br \/>\n        total_length &#061; self._calculate_multipart_length(ranges, boundary)<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Content-Type&#039;: content_type,<br \/>\n            &#039;Content-Length&#039;: str(total_length),<br \/>\n            &#039;Accept-Ranges&#039;: &#039;bytes&#039;,<br \/>\n            &#039;ETag&#039;: self._generate_etag(),<br \/>\n            &#039;Cache-Control&#039;: &#039;no-store&#039;,  # \u591a\u90e8\u5206\u54cd\u5e94\u901a\u5e38\u4e0d\u7f13\u5b58<br \/>\n        }<\/p>\n<p>        def body_generator():<br \/>\n            with open(self.resource_path, &#039;rb&#039;) as f:<br \/>\n                for i, range_info in enumerate(ranges):<br \/>\n                    # \u6bcf\u4e2a\u90e8\u5206\u7684\u8fb9\u754c<br \/>\n                    yield f&#034;\\\\r\\\\n&#8211;{boundary}\\\\r\\\\n&#034;.encode()<\/p>\n<p>                    # \u90e8\u5206\u5934\u90e8<br \/>\n                    part_headers &#061; ContentRangeHeaderBuilder.for_part_in_multipart(<br \/>\n                        range_info[&#039;start&#039;], range_info[&#039;end&#039;],<br \/>\n                        self.resource_size, self.mime_type<br \/>\n                    )<br \/>\n                    yield f&#034;{part_headers}\\\\r\\\\n\\\\r\\\\n&#034;.encode()<\/p>\n<p>                    # \u90e8\u5206\u5185\u5bb9<br \/>\n                    f.seek(range_info[&#039;start&#039;])<br \/>\n                    remaining &#061; range_info[&#039;length&#039;]<br \/>\n                    chunk_size &#061; 64 * 1024<\/p>\n<p>                    while remaining &gt; 0:<br \/>\n                        read_size &#061; min(chunk_size, remaining)<br \/>\n                        chunk &#061; f.read(read_size)<br \/>\n                        if not chunk:<br \/>\n                            break<br \/>\n                        yield chunk<br \/>\n                        remaining -&#061; len(chunk)<\/p>\n<p>            # \u7ed3\u675f\u8fb9\u754c<br \/>\n            yield f&#034;\\\\r\\\\n&#8211;{boundary}&#8211;\\\\r\\\\n&#034;.encode()<\/p>\n<p>        return 206, headers, body_generator()<\/p>\n<p>    def _calculate_multipart_length(self, ranges, boundary):<br \/>\n        &#034;&#034;&#034;\u8ba1\u7b97\u591a\u90e8\u5206\u54cd\u5e94\u7684\u603b\u957f\u5ea6&#034;&#034;&#034;<br \/>\n        total &#061; 0<\/p>\n<p>        for range_info in ranges:<br \/>\n            # \u8fb9\u754c\u884c<br \/>\n            total &#043;&#061; len(f&#034;\\\\r\\\\n&#8211;{boundary}\\\\r\\\\n&#034;)<\/p>\n<p>            # \u5934\u90e8<br \/>\n            part_headers &#061; ContentRangeHeaderBuilder.for_part_in_multipart(<br \/>\n                range_info[&#039;start&#039;], range_info[&#039;end&#039;],<br \/>\n                self.resource_size, self.mime_type<br \/>\n            )<br \/>\n            total &#043;&#061; len(f&#034;{part_headers}\\\\r\\\\n\\\\r\\\\n&#034;)<\/p>\n<p>            # \u5185\u5bb9<br \/>\n            total &#043;&#061; range_info[&#039;length&#039;]<\/p>\n<p>        # \u7ed3\u675f\u8fb9\u754c<br \/>\n        total &#043;&#061; len(f&#034;\\\\r\\\\n&#8211;{boundary}&#8211;\\\\r\\\\n&#034;)<\/p>\n<p>        return total<\/p>\n<p>    def _generate_etag(self):<br \/>\n        &#034;&#034;&#034;\u751f\u6210ETag&#xff08;\u793a\u4f8b\u5b9e\u73b0&#xff09;&#034;&#034;&#034;<br \/>\n        import hashlib<br \/>\n        import os<\/p>\n<p>        stat &#061; os.stat(self.resource_path)<br \/>\n        content &#061; f&#034;{self.resource_path}:{stat.st_size}:{stat.st_mtime}&#034;<br \/>\n        return f&#039;&#034;{hashlib.md5(content.encode()).hexdigest()}&#034;&#039;<\/p>\n<p>    def _get_last_modified(self):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u6700\u540e\u4fee\u6539\u65f6\u95f4&#034;&#034;&#034;<br \/>\n        import os<br \/>\n        from datetime import datetime<\/p>\n<p>        mtime &#061; os.path.getmtime(self.resource_path)<br \/>\n        return datetime.fromtimestamp(mtime).strftime(&#039;%a, %d %b %Y %H:%M:%S GMT&#039;)<\/p>\n<p>    def _add_conditional_headers(self, response_headers, request_headers):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u6761\u4ef6\u8bf7\u6c42\u76f8\u5173\u5934\u90e8&#034;&#034;&#034;<br \/>\n        # \u68c0\u67e5If-Range<br \/>\n        if &#039;If-Range&#039; in request_headers:<br \/>\n            # \u8fd9\u91cc\u5e94\u8be5\u9a8c\u8bc1If-Range\u6761\u4ef6<br \/>\n            # \u5982\u679c\u6761\u4ef6\u4e0d\u6ee1\u8db3&#xff0c;\u5e94\u8be5\u8fd4\u56de\u6574\u4e2a\u8d44\u6e90\u800c\u4e0d\u662f\u90e8\u5206\u5185\u5bb9<br \/>\n            pass<\/p>\n<p>        # \u68c0\u67e5If-Match\/If-None-Match<br \/>\n        if &#039;If-Match&#039; in request_headers or &#039;If-None-Match&#039; in request_headers:<br \/>\n            response_headers[&#039;Vary&#039;] &#061; &#039;If-Match, If-None-Match&#039;<\/p>\n<h3>8.3 \u670d\u52a1\u5668\u7aef\u5b9e\u73b0<\/h3>\n<h4>Nginx\u914d\u7f6e\u793a\u4f8b<\/h4>\n<p>nginx<\/p>\n<p># Nginx\u90e8\u5206\u5185\u5bb9\u54cd\u5e94\u914d\u7f6e<br \/>\nhttp {<br \/>\n    # \u542f\u7528\u5b57\u8282\u8303\u56f4\u652f\u6301<br \/>\n    slice             1m;  # \u5207\u7247\u5927\u5c0f&#xff0c;\u7528\u4e8e\u89c6\u9891\u6d41<br \/>\n    proxy_cache_path \/var\/cache\/nginx levels&#061;1:2 keys_zone&#061;video_cache:10m<br \/>\n                     max_size&#061;10g inactive&#061;60m use_temp_path&#061;off;<\/p>\n<p>    # \u89c6\u9891\u6d41\u670d\u52a1\u5668\u914d\u7f6e<br \/>\n    server {<br \/>\n        listen 80;<br \/>\n        server_name video.example.com;<\/p>\n<p>        location \/videos\/ {<br \/>\n            # \u542f\u7528\u8303\u56f4\u8bf7\u6c42\u652f\u6301<br \/>\n            mp4;<br \/>\n            mp4_buffer_size       1m;<br \/>\n            mp4_max_buffer_size   5m;<\/p>\n<p>            # \u9650\u5236\u8bf7\u6c42\u901f\u7387<br \/>\n            limit_rate 100m;  # \u6700\u5927100MB\/s<br \/>\n            limit_rate_after 10m;  # 10MB\u540e\u5f00\u59cb\u9650\u901f<\/p>\n<p>            # \u7f13\u5b58\u63a7\u5236<br \/>\n            add_header Cache-Control &#034;public, max-age&#061;31536000&#034;;<br \/>\n            add_header Accept-Ranges bytes;<br \/>\n            add_header X-Content-Type-Options nosniff;<\/p>\n<p>            # \u8bbf\u95ee\u63a7\u5236<br \/>\n            satisfy any;<br \/>\n            allow 192.168.1.0\/24;<br \/>\n            deny all;<\/p>\n<p>            # \u4ee3\u7406\u8bbe\u7f6e<br \/>\n            proxy_pass http:\/\/video_backend;<br \/>\n            proxy_set_header Range $http_range;<br \/>\n            proxy_set_header If-Range $http_if_range;<\/p>\n<p>            # \u7f13\u51b2\u548c\u8d85\u65f6<br \/>\n            proxy_buffering on;<br \/>\n            proxy_buffer_size 128k;<br \/>\n            proxy_buffers 4 256k;<br \/>\n            proxy_busy_buffers_size 256k;<br \/>\n            proxy_read_timeout 300;<br \/>\n        }<\/p>\n<p>        # \u5927\u6587\u4ef6\u4e0b\u8f7d\u914d\u7f6e<br \/>\n        location \/downloads\/ {<br \/>\n            # \u57fa\u7840\u8bbe\u7f6e<br \/>\n            root \/var\/www;<br \/>\n            autoindex off;<\/p>\n<p>            # \u8303\u56f4\u8bf7\u6c42\u652f\u6301<br \/>\n            add_header Accept-Ranges bytes;<\/p>\n<p>            # \u5b89\u5168\u5934\u90e8<br \/>\n            add_header X-Content-Type-Options nosniff;<br \/>\n            add_header X-Frame-Options DENY;<br \/>\n            add_header X-XSS-Protection &#034;1; mode&#061;block&#034;;<\/p>\n<p>            # \u4e0b\u8f7d\u9650\u5236<br \/>\n            limit_rate 50m;  # \u9650\u5236\u4e0b\u8f7d\u901f\u5ea6<br \/>\n            limit_rate_after 100m;  # 100MB\u540e\u9650\u901f<\/p>\n<p>            # \u9632\u76d7\u94fe<br \/>\n            valid_referers none blocked server_names *.example.com;<br \/>\n            if ($invalid_referer) {<br \/>\n                return 403;<br \/>\n            }<\/p>\n<p>            # \u6587\u4ef6\u7c7b\u578b\u5904\u7406<br \/>\n            location ~* \\\\.(mp4|avi|mkv|mov)$ {<br \/>\n                # \u89c6\u9891\u6587\u4ef6\u7279\u6b8a\u5904\u7406<br \/>\n                add_header Cache-Control &#034;public, max-age&#061;604800&#034;;  # 7\u5929<br \/>\n            }<\/p>\n<p>            location ~* \\\\.(zip|rar|7z|tar\\\\.gz)$ {<br \/>\n                # \u538b\u7f29\u6587\u4ef6<br \/>\n                add_header Content-Disposition &#034;attachment&#034;;<br \/>\n                add_header Cache-Control &#034;no-cache&#034;;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        # API\u7aef\u70b9\u5904\u7406\u8303\u56f4\u8bf7\u6c42<br \/>\n        location \/api\/files\/ {<br \/>\n            # \u4ee3\u7406\u5230\u5e94\u7528\u670d\u52a1\u5668<br \/>\n            proxy_pass http:\/\/app_backend;<\/p>\n<p>            # \u4f20\u9012\u8303\u56f4\u76f8\u5173\u5934\u90e8<br \/>\n            proxy_set_header Range $http_range;<br \/>\n            proxy_set_header If-Range $http_if_range;<br \/>\n            proxy_set_header X-Original-Range $http_range;<\/p>\n<p>            # \u5904\u7406\u5e94\u7528\u670d\u52a1\u5668\u7684206\u54cd\u5e94<br \/>\n            proxy_intercept_errors on;<br \/>\n            error_page 416 &#061; &#064;handle_416;<\/p>\n<p>            # \u7f13\u51b2\u8bbe\u7f6e<br \/>\n            proxy_buffering on;<br \/>\n            proxy_buffer_size 4k;<br \/>\n            proxy_buffers 8 4k;<\/p>\n<p>            # \u8d85\u65f6\u8bbe\u7f6e<br \/>\n            proxy_connect_timeout 60;<br \/>\n            proxy_send_timeout 300;<br \/>\n            proxy_read_timeout 300;<br \/>\n        }<\/p>\n<p>        # 416\u9519\u8bef\u5904\u7406<br \/>\n        location &#064;handle_416 {<br \/>\n            default_type application\/json;<br \/>\n            return 416 &#039;{&#034;error&#034;: &#034;Requested range not satisfiable&#034;}&#039;;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    # \u4e0a\u6e38\u670d\u52a1\u5668\u914d\u7f6e<br \/>\n    upstream video_backend {<br \/>\n        server 192.168.1.100:8080;<br \/>\n        server 192.168.1.101:8080;<br \/>\n        keepalive 32;<br \/>\n    }<\/p>\n<p>    upstream app_backend {<br \/>\n        server 192.168.1.200:8000;<br \/>\n        server 192.168.1.201:8000;<br \/>\n        keepalive 16;<br \/>\n    }<br \/>\n}<\/p>\n<p># \u7279\u6b8a\u60c5\u51b5\u5904\u7406<br \/>\nevents {<br \/>\n    worker_connections 1024;<br \/>\n    use epoll;<br \/>\n}<\/p>\n<h4>Python Flask\u5b9e\u73b0<\/h4>\n<p>python<\/p>\n<p># \u5b8c\u6574\u7684206 Partial Content\u670d\u52a1\u5668\u5b9e\u73b0<br \/>\nfrom flask import Flask, request, Response, send_file, jsonify<br \/>\nimport os<br \/>\nimport mimetypes<br \/>\nfrom datetime import datetime<br \/>\nimport hashlib<br \/>\nimport time<br \/>\nfrom functools import wraps<br \/>\nimport re<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p># \u914d\u7f6e<br \/>\napp.config[&#039;MAX_CONTENT_LENGTH&#039;] &#061; 100 * 1024 * 1024 * 1024  # 100GB\u6700\u5927\u6587\u4ef6<br \/>\napp.config[&#039;RANGE_REQUEST_MAX_RANGES&#039;] &#061; 10<br \/>\napp.config[&#039;RANGE_REQUEST_MAX_SIZE&#039;] &#061; 100 * 1024 * 1024  # 100MB\u6700\u5927\u8303\u56f4<br \/>\napp.config[&#039;CHUNK_SIZE&#039;] &#061; 64 * 1024  # 64KB\u5757\u5927\u5c0f<\/p>\n<p>class RangeRequestHandler:<br \/>\n    &#034;&#034;&#034;\u5904\u7406\u8303\u56f4\u8bf7\u6c42\u7684\u5904\u7406\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, file_path, mime_type&#061;None):<br \/>\n        self.file_path &#061; file_path<br \/>\n        self.mime_type &#061; mime_type or self._guess_mime_type(file_path)<br \/>\n        self.file_size &#061; os.path.getsize(file_path)<br \/>\n        self.last_modified &#061; datetime.fromtimestamp(<br \/>\n            os.path.getmtime(file_path)<br \/>\n        )<br \/>\n        self.etag &#061; self._generate_etag()<\/p>\n<p>    def _guess_mime_type(self, file_path):<br \/>\n        &#034;&#034;&#034;\u731c\u6d4bMIME\u7c7b\u578b&#034;&#034;&#034;<br \/>\n        mime_type, _ &#061; mimetypes.guess_type(file_path)<br \/>\n        return mime_type or &#039;application\/octet-stream&#039;<\/p>\n<p>    def _generate_etag(self):<br \/>\n        &#034;&#034;&#034;\u751f\u6210ETag&#034;&#034;&#034;<br \/>\n        stat &#061; os.stat(self.file_path)<br \/>\n        content &#061; f&#034;{self.file_path}:{stat.st_size}:{stat.st_mtime}&#034;<br \/>\n        return f&#039;&#034;{hashlib.md5(content.encode()).hexdigest()}&#034;&#039;<\/p>\n<p>    def handle_request(self, range_header&#061;None):<br \/>\n        &#034;&#034;&#034;\u5904\u7406HTTP\u8bf7\u6c42&#034;&#034;&#034;<\/p>\n<p>        # \u68c0\u67e5\u6761\u4ef6\u8bf7\u6c42<br \/>\n        if not self._check_conditions():<br \/>\n            return self._build_conditional_failure_response()<\/p>\n<p>        # \u5982\u679c\u6ca1\u6709Range\u5934\u90e8&#xff0c;\u8fd4\u56de\u6574\u4e2a\u6587\u4ef6<br \/>\n        if not range_header:<br \/>\n            return self._serve_full_file()<\/p>\n<p>        # \u89e3\u6790Range\u5934\u90e8<br \/>\n        ranges &#061; self._parse_range_header(range_header)<br \/>\n        if not ranges:<br \/>\n            return self._build_range_not_satisfiable_response()<\/p>\n<p>        # \u5904\u7406\u8303\u56f4\u8bf7\u6c42<br \/>\n        if len(ranges) &#061;&#061; 1:<br \/>\n            return self._serve_single_range(ranges[0])<br \/>\n        else:<br \/>\n            return self._serve_multipart_ranges(ranges)<\/p>\n<p>    def _check_conditions(self):<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u6761\u4ef6\u8bf7\u6c42\u5934\u90e8&#034;&#034;&#034;<br \/>\n        # \u68c0\u67e5If-Match<br \/>\n        if_match &#061; request.headers.get(&#039;If-Match&#039;)<br \/>\n        if if_match and self.etag not in self._parse_etag_list(if_match):<br \/>\n            return False<\/p>\n<p>        # \u68c0\u67e5If-None-Match<br \/>\n        if_none_match &#061; request.headers.get(&#039;If-None-Match&#039;)<br \/>\n        if if_none_match and self.etag in self._parse_etag_list(if_none_match):<br \/>\n            # \u5bf9\u4e8eGET\/HEAD&#xff0c;\u8fd4\u56de304&#xff1b;\u5bf9\u4e8e\u5176\u4ed6\u65b9\u6cd5&#xff0c;\u8fd4\u56de412<br \/>\n            if request.method in (&#039;GET&#039;, &#039;HEAD&#039;):<br \/>\n                return None  # \u7279\u6b8a\u6807\u8bb0&#xff0c;\u8868\u793a304<br \/>\n            return False<\/p>\n<p>        # \u68c0\u67e5If-Modified-Since<br \/>\n        if_modified_since &#061; request.headers.get(&#039;If-Modified-Since&#039;)<br \/>\n        if if_modified_since:<br \/>\n            try:<br \/>\n                since &#061; datetime.strptime(<br \/>\n                    if_modified_since, &#039;%a, %d %b %Y %H:%M:%S GMT&#039;<br \/>\n                )<br \/>\n                if self.last_modified &lt;&#061; since:<br \/>\n                    return None  # \u7279\u6b8a\u6807\u8bb0&#xff0c;\u8868\u793a304<br \/>\n            except ValueError:<br \/>\n                pass<\/p>\n<p>        # \u68c0\u67e5If-Unmodified-Since<br \/>\n        if_unmodified_since &#061; request.headers.get(&#039;If-Unmodified-Since&#039;)<br \/>\n        if if_unmodified_since:<br \/>\n            try:<br \/>\n                since &#061; datetime.strptime(<br \/>\n                    if_unmodified_since, &#039;%a, %d %b %Y %H:%M:%S GMT&#039;<br \/>\n                )<br \/>\n                if self.last_modified &gt; since:<br \/>\n                    return False<br \/>\n            except ValueError:<br \/>\n                pass<\/p>\n<p>        # \u68c0\u67e5If-Range&#xff08;\u4ec5\u5728\u90e8\u5206\u8bf7\u6c42\u65f6\u68c0\u67e5&#xff09;<br \/>\n        if_range &#061; request.headers.get(&#039;If-Range&#039;)<br \/>\n        if if_range and request.headers.get(&#039;Range&#039;):<br \/>\n            # If-Range\u53ef\u4ee5\u662fETag\u6216\u65e5\u671f<br \/>\n            if if_range.startswith(&#039;&#034;&#039;):<br \/>\n                # ETag\u683c\u5f0f<br \/>\n                if if_range !&#061; self.etag:<br \/>\n                    # \u8fd4\u56de\u6574\u4e2a\u8d44\u6e90<br \/>\n                    return self._serve_full_file()<br \/>\n            else:<br \/>\n                # \u65e5\u671f\u683c\u5f0f<br \/>\n                try:<br \/>\n                    since &#061; datetime.strptime(<br \/>\n                        if_range, &#039;%a, %d %b %Y %H:%M:%S GMT&#039;<br \/>\n                    )<br \/>\n                    if self.last_modified &gt; since:<br \/>\n                        # \u8fd4\u56de\u6574\u4e2a\u8d44\u6e90<br \/>\n                        return self._serve_full_file()<br \/>\n                except ValueError:<br \/>\n                    pass<\/p>\n<p>        return True<\/p>\n<p>    def _parse_etag_list(self, etag_header):<br \/>\n        &#034;&#034;&#034;\u89e3\u6790ETag\u5217\u8868&#034;&#034;&#034;<br \/>\n        etags &#061; []<br \/>\n        for etag in etag_header.split(&#039;,&#039;):<br \/>\n            etag &#061; etag.strip()<br \/>\n            if etag:<br \/>\n                etags.append(etag)<br \/>\n        return etags<\/p>\n<p>    def _parse_range_header(self, range_header):<br \/>\n        &#034;&#034;&#034;\u89e3\u6790Range\u5934\u90e8&#034;&#034;&#034;<br \/>\n        if not range_header.startswith(&#039;bytes&#061;&#039;):<br \/>\n            return None<\/p>\n<p>        range_str &#061; range_header[6:]  # \u79fb\u9664&#039;bytes&#061;&#039;<br \/>\n        range_specs &#061; [s.strip() for s in range_str.split(&#039;,&#039;)]<\/p>\n<p>        # \u9650\u5236\u8303\u56f4\u6570\u91cf<br \/>\n        if len(range_specs) &gt; app.config[&#039;RANGE_REQUEST_MAX_RANGES&#039;]:<br \/>\n            return None<\/p>\n<p>        ranges &#061; []<\/p>\n<p>        for spec in range_specs:<br \/>\n            range_info &#061; self._parse_single_range(spec)<br \/>\n            if not range_info:<br \/>\n                return None<\/p>\n<p>            # \u68c0\u67e5\u8303\u56f4\u5927\u5c0f\u9650\u5236<br \/>\n            range_length &#061; range_info[&#039;end&#039;] &#8211; range_info[&#039;start&#039;] &#043; 1<br \/>\n            if range_length &gt; app.config[&#039;RANGE_REQUEST_MAX_SIZE&#039;]:<br \/>\n                return None<\/p>\n<p>            ranges.append(range_info)<\/p>\n<p>        # \u68c0\u67e5\u8303\u56f4\u91cd\u53e0\u548c\u6392\u5e8f<br \/>\n        ranges.sort(key&#061;lambda x: x[&#039;start&#039;])<br \/>\n        for i in range(1, len(ranges)):<br \/>\n            if ranges[i][&#039;start&#039;] &lt;&#061; ranges[i-1][&#039;end&#039;]:<br \/>\n                return None<\/p>\n<p>        return ranges<\/p>\n<p>    def _parse_single_range(self, range_spec):<br \/>\n        &#034;&#034;&#034;\u89e3\u6790\u5355\u4e2a\u8303\u56f4\u89c4\u683c&#034;&#034;&#034;<br \/>\n        if &#039;-&#039; not in range_spec:<br \/>\n            return None<\/p>\n<p>        start_str, end_str &#061; range_spec.split(&#039;-&#039;, 1)<\/p>\n<p>        try:<br \/>\n            # \u540e\u7f00\u8303\u56f4\u89c4\u683c<br \/>\n            if start_str &#061;&#061; &#039;&#039;:<br \/>\n                suffix_length &#061; int(end_str)<br \/>\n                if suffix_length &lt;&#061; 0:<br \/>\n                    return None<\/p>\n<p>                start &#061; max(0, self.file_size &#8211; suffix_length)<br \/>\n                end &#061; self.file_size &#8211; 1<\/p>\n<p>            # \u5f00\u59cb\u5230\u7ed3\u5c3e<br \/>\n            elif end_str &#061;&#061; &#039;&#039;:<br \/>\n                start &#061; int(start_str)<br \/>\n                if start &gt;&#061; self.file_size:<br \/>\n                    return None<br \/>\n                end &#061; self.file_size &#8211; 1<\/p>\n<p>            # \u5b8c\u6574\u8303\u56f4<br \/>\n            else:<br \/>\n                start &#061; int(start_str)<br \/>\n                end &#061; int(end_str)<\/p>\n<p>                if start &gt; end:<br \/>\n                    return None<\/p>\n<p>                if start &gt;&#061; self.file_size:<br \/>\n                    return None<\/p>\n<p>            # \u8c03\u6574\u7ed3\u675f\u4f4d\u7f6e<br \/>\n            end &#061; min(end, self.file_size &#8211; 1)<\/p>\n<p>            # \u9a8c\u8bc1\u6700\u7ec8\u8303\u56f4<br \/>\n            if start &lt; 0 or end &lt; 0 or start &gt; end:<br \/>\n                return None<\/p>\n<p>            return {<br \/>\n                &#039;start&#039;: start,<br \/>\n                &#039;end&#039;: end,<br \/>\n                &#039;length&#039;: end &#8211; start &#043; 1<br \/>\n            }<\/p>\n<p>        except ValueError:<br \/>\n            return None<\/p>\n<p>    def _build_conditional_failure_response(self):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa\u6761\u4ef6\u8bf7\u6c42\u5931\u8d25\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        if request.method in (&#039;GET&#039;, &#039;HEAD&#039;):<br \/>\n            # 304 Not Modified<br \/>\n            return Response(status&#061;304, headers&#061;{<br \/>\n                &#039;ETag&#039;: self.etag,<br \/>\n                &#039;Last-Modified&#039;: self.last_modified.strftime(&#039;%a, %d %b %Y %H:%M:%S GMT&#039;)<br \/>\n            })<br \/>\n        else:<br \/>\n            # 412 Precondition Failed<br \/>\n            return Response(&#039;Precondition Failed&#039;, status&#061;412)<\/p>\n<p>    def _build_range_not_satisfiable_response(self):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa416\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        return Response(<br \/>\n            &#039;Requested range not satisfiable&#039;,<br \/>\n            status&#061;416,<br \/>\n            headers&#061;{<br \/>\n                &#039;Content-Range&#039;: f&#039;bytes *\/{self.file_size}&#039;,<br \/>\n                &#039;Content-Type&#039;: &#039;text\/plain&#039;<br \/>\n            }<br \/>\n        )<\/p>\n<p>    def _serve_full_file(self):<br \/>\n        &#034;&#034;&#034;\u63d0\u4f9b\u6574\u4e2a\u6587\u4ef6&#034;&#034;&#034;<br \/>\n        headers &#061; {<br \/>\n            &#039;Content-Type&#039;: self.mime_type,<br \/>\n            &#039;Content-Length&#039;: str(self.file_size),<br \/>\n            &#039;Accept-Ranges&#039;: &#039;bytes&#039;,<br \/>\n            &#039;ETag&#039;: self.etag,<br \/>\n            &#039;Last-Modified&#039;: self.last_modified.strftime(&#039;%a, %d %b %Y %H:%M:%S GMT&#039;),<br \/>\n            &#039;Cache-Control&#039;: &#039;public, max-age&#061;31536000&#039;  # 1\u5e74<br \/>\n        }<\/p>\n<p>        if request.method &#061;&#061; &#039;HEAD&#039;:<br \/>\n            return Response(headers&#061;headers)<\/p>\n<p>        return send_file(<br \/>\n            self.file_path,<br \/>\n            mimetype&#061;self.mime_type,<br \/>\n            as_attachment&#061;False,<br \/>\n            conditional&#061;True<br \/>\n        )<\/p>\n<p>    def _serve_single_range(self, range_info):<br \/>\n        &#034;&#034;&#034;\u63d0\u4f9b\u5355\u4e2a\u8303\u56f4&#034;&#034;&#034;<br \/>\n        start &#061; range_info[&#039;start&#039;]<br \/>\n        end &#061; range_info[&#039;end&#039;]<br \/>\n        length &#061; range_info[&#039;length&#039;]<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Content-Range&#039;: f&#039;bytes {start}-{end}\/{self.file_size}&#039;,<br \/>\n            &#039;Content-Length&#039;: str(length),<br \/>\n            &#039;Content-Type&#039;: self.mime_type,<br \/>\n            &#039;Accept-Ranges&#039;: &#039;bytes&#039;,<br \/>\n            &#039;ETag&#039;: self.etag,<br \/>\n            &#039;Cache-Control&#039;: &#039;public, max-age&#061;31536000&#039;<br \/>\n        }<\/p>\n<p>        if request.method &#061;&#061; &#039;HEAD&#039;:<br \/>\n            return Response(headers&#061;headers)<\/p>\n<p>        def generate():<br \/>\n            with open(self.file_path, &#039;rb&#039;) as f:<br \/>\n                f.seek(start)<br \/>\n                remaining &#061; length<br \/>\n                chunk_size &#061; app.config[&#039;CHUNK_SIZE&#039;]<\/p>\n<p>                while remaining &gt; 0:<br \/>\n                    read_size &#061; min(chunk_size, remaining)<br \/>\n                    chunk &#061; f.read(read_size)<br \/>\n                    if not chunk:<br \/>\n                        break<br \/>\n                    yield chunk<br \/>\n                    remaining -&#061; len(chunk)<\/p>\n<p>        return Response(<br \/>\n            generate(),<br \/>\n            status&#061;206,<br \/>\n            headers&#061;headers<br \/>\n        )<\/p>\n<p>    def _serve_multipart_ranges(self, ranges):<br \/>\n        &#034;&#034;&#034;\u63d0\u4f9b\u591a\u90e8\u5206\u8303\u56f4&#034;&#034;&#034;<br \/>\n        boundary &#061; f&#034;boundary_{hash(str(ranges))}_{int(time.time())}&#034;<br \/>\n        content_type &#061; f&#034;multipart\/byteranges; boundary&#061;{boundary}&#034;<\/p>\n<p>        # \u8ba1\u7b97\u603b\u957f\u5ea6<br \/>\n        total_length &#061; self._calculate_multipart_length(ranges, boundary)<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Content-Type&#039;: content_type,<br \/>\n            &#039;Content-Length&#039;: str(total_length),<br \/>\n            &#039;Accept-Ranges&#039;: &#039;bytes&#039;,<br \/>\n            &#039;ETag&#039;: self.etag,<br \/>\n            &#039;Cache-Control&#039;: &#039;no-store&#039;  # \u591a\u90e8\u5206\u54cd\u5e94\u4e0d\u7f13\u5b58<br \/>\n        }<\/p>\n<p>        if request.method &#061;&#061; &#039;HEAD&#039;:<br \/>\n            return Response(headers&#061;headers)<\/p>\n<p>        def generate():<br \/>\n            with open(self.file_path, &#039;rb&#039;) as f:<br \/>\n                for i, range_info in enumerate(ranges):<br \/>\n                    # \u8fb9\u754c<br \/>\n                    yield f&#034;\\\\r\\\\n&#8211;{boundary}\\\\r\\\\n&#034;.encode()<\/p>\n<p>                    # \u90e8\u5206\u5934\u90e8<br \/>\n                    part_headers &#061; [<br \/>\n                        f&#034;Content-Range: bytes {range_info[&#039;start&#039;]}-{range_info[&#039;end&#039;]}\/{self.file_size}&#034;,<br \/>\n                        f&#034;Content-Type: {self.mime_type}&#034;<br \/>\n                    ]<br \/>\n                    yield &#034;\\\\r\\\\n&#034;.join(part_headers).encode()<br \/>\n                    yield &#034;\\\\r\\\\n\\\\r\\\\n&#034;.encode()<\/p>\n<p>                    # \u90e8\u5206\u5185\u5bb9<br \/>\n                    f.seek(range_info[&#039;start&#039;])<br \/>\n                    remaining &#061; range_info[&#039;length&#039;]<br \/>\n                    chunk_size &#061; app.config[&#039;CHUNK_SIZE&#039;]<\/p>\n<p>                    while remaining &gt; 0:<br \/>\n                        read_size &#061; min(chunk_size, remaining)<br \/>\n                        chunk &#061; f.read(read_size)<br \/>\n                        if not chunk:<br \/>\n                            break<br \/>\n                        yield chunk<br \/>\n                        remaining -&#061; len(chunk)<\/p>\n<p>                # \u7ed3\u675f\u8fb9\u754c<br \/>\n                yield f&#034;\\\\r\\\\n&#8211;{boundary}&#8211;\\\\r\\\\n&#034;.encode()<\/p>\n<p>        return Response(<br \/>\n            generate(),<br \/>\n            status&#061;206,<br \/>\n            headers&#061;headers<br \/>\n        )<\/p>\n<p>    def _calculate_multipart_length(self, ranges, boundary):<br \/>\n        &#034;&#034;&#034;\u8ba1\u7b97\u591a\u90e8\u5206\u54cd\u5e94\u957f\u5ea6&#034;&#034;&#034;<br \/>\n        total &#061; 0<\/p>\n<p>        for range_info in ranges:<br \/>\n            # \u8fb9\u754c\u884c<br \/>\n            total &#043;&#061; len(f&#034;\\\\r\\\\n&#8211;{boundary}\\\\r\\\\n&#034;)<\/p>\n<p>            # \u5934\u90e8<br \/>\n            headers &#061; [<br \/>\n                f&#034;Content-Range: bytes {range_info[&#039;start&#039;]}-{range_info[&#039;end&#039;]}\/{self.file_size}&#034;,<br \/>\n                f&#034;Content-Type: {self.mime_type}&#034;<br \/>\n            ]<br \/>\n            total &#043;&#061; len(&#034;\\\\r\\\\n&#034;.join(headers)) &#043; len(&#034;\\\\r\\\\n\\\\r\\\\n&#034;)<\/p>\n<p>            # \u5185\u5bb9<br \/>\n            total &#043;&#061; range_info[&#039;length&#039;]<\/p>\n<p>        # \u7ed3\u675f\u8fb9\u754c<br \/>\n        total &#043;&#061; len(f&#034;\\\\r\\\\n&#8211;{boundary}&#8211;\\\\r\\\\n&#034;)<\/p>\n<p>        return total<\/p>\n<p># API\u7aef\u70b9<br \/>\n&#064;app.route(&#039;\/api\/files\/&lt;path:filename&gt;&#039;)<br \/>\ndef serve_file(filename):<br \/>\n    &#034;&#034;&#034;\u63d0\u4f9b\u6587\u4ef6\u4e0b\u8f7d&#xff08;\u652f\u6301\u8303\u56f4\u8bf7\u6c42&#xff09;&#034;&#034;&#034;<\/p>\n<p>    # \u5b89\u5168\u9a8c\u8bc1&#xff1a;\u9632\u6b62\u76ee\u5f55\u904d\u5386<br \/>\n    safe_filename &#061; os.path.basename(filename)<br \/>\n    file_path &#061; os.path.join(app.config[&#039;UPLOAD_FOLDER&#039;], safe_filename)<\/p>\n<p>    if not os.path.exists(file_path):<br \/>\n        return jsonify({&#039;error&#039;: &#039;File not found&#039;}), 404<\/p>\n<p>    # \u521b\u5efa\u5904\u7406\u5668<br \/>\n    handler &#061; RangeRequestHandler(file_path)<\/p>\n<p>    # \u83b7\u53d6Range\u5934\u90e8<br \/>\n    range_header &#061; request.headers.get(&#039;Range&#039;)<\/p>\n<p>    # \u5904\u7406\u8bf7\u6c42<br \/>\n    return handler.handle_request(range_header)<\/p>\n<p>&#064;app.route(&#039;\/api\/stream\/video\/&lt;video_id&gt;&#039;)<br \/>\ndef stream_video(video_id):<br \/>\n    &#034;&#034;&#034;\u6d41\u5f0f\u4f20\u8f93\u89c6\u9891&#xff08;\u652f\u6301\u8303\u56f4\u8bf7\u6c42&#xff09;&#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u89c6\u9891\u6587\u4ef6\u8def\u5f84<br \/>\n    video_path &#061; get_video_path(video_id)  # \u5047\u8bbe\u7684\u51fd\u6570<br \/>\n    if not video_path or not os.path.exists(video_path):<br \/>\n        return jsonify({&#039;error&#039;: &#039;Video not found&#039;}), 404<\/p>\n<p>    # \u89c6\u9891\u6587\u4ef6\u7684MIME\u7c7b\u578b<br \/>\n    mime_type &#061; &#039;video\/mp4&#039;  # \u6839\u636e\u5b9e\u9645\u683c\u5f0f\u8c03\u6574<\/p>\n<p>    # \u521b\u5efa\u5904\u7406\u5668<br \/>\n    handler &#061; RangeRequestHandler(video_path, mime_type)<\/p>\n<p>    # \u83b7\u53d6Range\u5934\u90e8<br \/>\n    range_header &#061; request.headers.get(&#039;Range&#039;)<\/p>\n<p>    # \u5904\u7406\u8bf7\u6c42<br \/>\n    return handler.handle_request(range_header)<\/p>\n<p># \u4e2d\u95f4\u4ef6&#xff1a;\u8bb0\u5f55\u8303\u56f4\u8bf7\u6c42\u7edf\u8ba1<br \/>\n&#064;app.after_request<br \/>\ndef log_range_requests(response):<br \/>\n    &#034;&#034;&#034;\u8bb0\u5f55\u8303\u56f4\u8bf7\u6c42\u7edf\u8ba1&#034;&#034;&#034;<br \/>\n    if request.headers.get(&#039;Range&#039;) and response.status_code &#061;&#061; 206:<br \/>\n        # \u8bb0\u5f55\u5230\u65e5\u5fd7<br \/>\n        app.logger.info(f&#034;Range request: {request.headers.get(&#039;Range&#039;)} -&gt; {response.status}&#034;)<\/p>\n<p>        # \u6dfb\u52a0\u81ea\u5b9a\u4e49\u5934\u90e8<br \/>\n        response.headers[&#039;X-Range-Request&#039;] &#061; &#039;processed&#039;<\/p>\n<p>        # \u5982\u679c\u662f\u8303\u56f4\u54cd\u5e94&#xff0c;\u6dfb\u52a0\u7edf\u8ba1\u4fe1\u606f<br \/>\n        content_range &#061; response.headers.get(&#039;Content-Range&#039;)<br \/>\n        if content_range:<br \/>\n            # \u89e3\u6790\u8303\u56f4\u4fe1\u606f<br \/>\n            match &#061; re.match(r&#039;bytes (\\\\d&#043;)-(\\\\d&#043;)\/(\\\\d&#043;)&#039;, content_range)<br \/>\n            if match:<br \/>\n                start, end, total &#061; match.groups()<br \/>\n                bytes_served &#061; int(end) &#8211; int(start) &#043; 1<br \/>\n                response.headers[&#039;X-Bytes-Served&#039;] &#061; str(bytes_served)<br \/>\n                response.headers[&#039;X-Total-Bytes&#039;] &#061; total<\/p>\n<p>    return response<\/p>\n<p># \u8f85\u52a9\u51fd\u6570<br \/>\ndef get_video_path(video_id):<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u89c6\u9891\u6587\u4ef6\u8def\u5f84&#xff08;\u793a\u4f8b\u5b9e\u73b0&#xff09;&#034;&#034;&#034;<br \/>\n    # \u5b9e\u9645\u4e2d\u53ef\u80fd\u4f1a\u4ece\u6570\u636e\u5e93\u6216\u6587\u4ef6\u7cfb\u7edf\u4e2d\u67e5\u627e<br \/>\n    video_dir &#061; &#039;\/var\/www\/videos&#039;<br \/>\n    for ext in [&#039;.mp4&#039;, &#039;.avi&#039;, &#039;.mkv&#039;, &#039;.mov&#039;]:<br \/>\n        path &#061; os.path.join(video_dir, f&#034;{video_id}{ext}&#034;)<br \/>\n        if os.path.exists(path):<br \/>\n            return path<br \/>\n    return None<\/p>\n<p>if __name__ &#061;&#061; &#039;__main__&#039;:<br \/>\n    app.config[&#039;UPLOAD_FOLDER&#039;] &#061; &#039;\/var\/www\/uploads&#039;<br \/>\n    os.makedirs(app.config[&#039;UPLOAD_FOLDER&#039;], exist_ok&#061;True)<\/p>\n<p>    app.run(host&#061;&#039;0.0.0.0&#039;, port&#061;8000, threaded&#061;True)<\/p>\n<h3>8.4 \u5ba2\u6237\u7aef\u5904\u7406\u5b9e\u73b0<\/h3>\n<h4>JavaScript Fetch API\u5b9e\u73b0<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u6d4f\u89c8\u5668\u7aef\u8303\u56f4\u8bf7\u6c42\u5904\u7406\u5668<br \/>\nclass RangeRequestClient {<br \/>\n    \/**<br \/>\n     * \u6d4f\u89c8\u5668\u7aef\u8303\u56f4\u8bf7\u6c42\u5904\u7406\u5668<br \/>\n     *\/<\/p>\n<p>    constructor(options &#061; {}) {<br \/>\n        this.options &#061; {<br \/>\n            chunkSize: 1024 * 1024,  \/\/ 1MB\u5757\u5927\u5c0f<br \/>\n            maxRetries: 3,<br \/>\n            retryDelay: 1000,<br \/>\n            parallelDownloads: 3,<br \/>\n            &#8230;options<br \/>\n        };<\/p>\n<p>        this.downloadQueue &#061; [];<br \/>\n        this.activeDownloads &#061; 0;<br \/>\n        this.downloadStats &#061; new Map();<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u5206\u5757\u4e0b\u8f7d\u6587\u4ef6<br \/>\n     * &#064;param {string} url &#8211; \u6587\u4ef6URL<br \/>\n     * &#064;param {Object} options &#8211; \u4e0b\u8f7d\u9009\u9879<br \/>\n     *\/<br \/>\n    async downloadFile(url, options &#061; {}) {<br \/>\n        const {<br \/>\n            onProgress,<br \/>\n            onComplete,<br \/>\n            onError,<br \/>\n            resume &#061; false<br \/>\n        } &#061; options;<\/p>\n<p>        try {<br \/>\n            \/\/ 1. \u83b7\u53d6\u6587\u4ef6\u4fe1\u606f<br \/>\n            const fileInfo &#061; await this.getFileInfo(url);<br \/>\n            if (!fileInfo) {<br \/>\n                throw new Error(&#039;\u65e0\u6cd5\u83b7\u53d6\u6587\u4ef6\u4fe1\u606f&#039;);<br \/>\n            }<\/p>\n<p>            \/\/ 2. \u68c0\u67e5\u662f\u5426\u652f\u6301\u8303\u56f4\u8bf7\u6c42<br \/>\n            if (!fileInfo.acceptRanges) {<br \/>\n                \/\/ \u56de\u9000\u5230\u666e\u901a\u4e0b\u8f7d<br \/>\n                return await this.downloadFullFile(url, options);<br \/>\n            }<\/p>\n<p>            \/\/ 3. \u68c0\u67e5\u662f\u5426\u6709\u90e8\u5206\u4e0b\u8f7d\u7684\u6587\u4ef6<br \/>\n            const downloadedParts &#061; resume ? await this.getDownloadedParts(url, fileInfo) : [];<\/p>\n<p>            \/\/ 4. \u8ba1\u7b97\u9700\u8981\u4e0b\u8f7d\u7684\u8303\u56f4<br \/>\n            const ranges &#061; this.calculateRanges(fileInfo.size, downloadedParts);<\/p>\n<p>            \/\/ 5. \u5e76\u884c\u4e0b\u8f7d\u6240\u6709\u8303\u56f4<br \/>\n            const chunks &#061; await this.downloadRanges(url, ranges, {<br \/>\n                onProgress: (chunkIndex, progress) &#061;&gt; {<br \/>\n                    if (onProgress) {<br \/>\n                        const overallProgress &#061; this.calculateOverallProgress(<br \/>\n                            ranges, chunkIndex, progress<br \/>\n                        );<br \/>\n                        onProgress(overallProgress);<br \/>\n                    }<br \/>\n                }<br \/>\n            });<\/p>\n<p>            \/\/ 6. \u5408\u5e76\u6240\u6709\u5757<br \/>\n            const blob &#061; this.mergeChunks(chunks, fileInfo.size);<\/p>\n<p>            \/\/ 7. \u6e05\u7406\u4e34\u65f6\u6570\u636e<br \/>\n            await this.cleanupTempData(url);<\/p>\n<p>            if (onComplete) {<br \/>\n                onComplete(blob, fileInfo);<br \/>\n            }<\/p>\n<p>            return blob;<\/p>\n<p>        } catch (error) {<br \/>\n            if (onError) {<br \/>\n                onError(error);<br \/>\n            }<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u83b7\u53d6\u6587\u4ef6\u4fe1\u606f<br \/>\n     *\/<br \/>\n    async getFileInfo(url) {<br \/>\n        try {<br \/>\n            const response &#061; await fetch(url, {<br \/>\n                method: &#039;HEAD&#039;,<br \/>\n                headers: {<br \/>\n                    &#039;Accept&#039;: &#039;*\/*&#039;<br \/>\n                }<br \/>\n            });<\/p>\n<p>            if (!response.ok) {<br \/>\n                throw new Error(&#096;HTTP ${response.status}&#096;);<br \/>\n            }<\/p>\n<p>            const contentLength &#061; response.headers.get(&#039;Content-Length&#039;);<br \/>\n            const acceptRanges &#061; response.headers.get(&#039;Accept-Ranges&#039;) &#061;&#061;&#061; &#039;bytes&#039;;<br \/>\n            const contentType &#061; response.headers.get(&#039;Content-Type&#039;);<br \/>\n            const lastModified &#061; response.headers.get(&#039;Last-Modified&#039;);<br \/>\n            const etag &#061; response.headers.get(&#039;ETag&#039;);<\/p>\n<p>            return {<br \/>\n                size: parseInt(contentLength, 10) || 0,<br \/>\n                acceptRanges,<br \/>\n                contentType,<br \/>\n                lastModified,<br \/>\n                etag,<br \/>\n                url<br \/>\n            };<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;Failed to get file info:&#039;, error);<br \/>\n            return null;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u83b7\u53d6\u5df2\u4e0b\u8f7d\u7684\u90e8\u5206<br \/>\n     *\/<br \/>\n    async getDownloadedParts(url, fileInfo) {<br \/>\n        const storageKey &#061; &#096;download:${btoa(url)}&#096;;<\/p>\n<p>        try {<br \/>\n            const saved &#061; localStorage.getItem(storageKey);<br \/>\n            if (!saved) {<br \/>\n                return [];<br \/>\n            }<\/p>\n<p>            const data &#061; JSON.parse(saved);<\/p>\n<p>            \/\/ \u9a8c\u8bc1\u6587\u4ef6\u662f\u5426\u5df2\u66f4\u6539<br \/>\n            if (data.etag !&#061;&#061; fileInfo.etag ||<br \/>\n                data.size !&#061;&#061; fileInfo.size) {<br \/>\n                localStorage.removeItem(storageKey);<br \/>\n                return [];<br \/>\n            }<\/p>\n<p>            return data.parts || [];<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;Failed to get downloaded parts:&#039;, error);<br \/>\n            return [];<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u8ba1\u7b97\u9700\u8981\u4e0b\u8f7d\u7684\u8303\u56f4<br \/>\n     *\/<br \/>\n    calculateRanges(fileSize, downloadedParts) {<br \/>\n        const ranges &#061; [];<\/p>\n<p>        if (downloadedParts.length &#061;&#061;&#061; 0) {<br \/>\n            \/\/ \u5168\u65b0\u4e0b\u8f7d&#xff1a;\u5c06\u6587\u4ef6\u5206\u6210\u591a\u4e2a\u5757<br \/>\n            for (let start &#061; 0; start &lt; fileSize; start &#043;&#061; this.options.chunkSize) {<br \/>\n                const end &#061; Math.min(start &#043; this.options.chunkSize &#8211; 1, fileSize &#8211; 1);<br \/>\n                ranges.push({ start, end, downloaded: false });<br \/>\n            }<br \/>\n        } else {<br \/>\n            \/\/ \u6062\u590d\u4e0b\u8f7d&#xff1a;\u53ea\u4e0b\u8f7d\u7f3a\u5931\u7684\u90e8\u5206<br \/>\n            let currentPos &#061; 0;<\/p>\n<p>            \/\/ \u6309\u5f00\u59cb\u4f4d\u7f6e\u6392\u5e8f<br \/>\n            downloadedParts.sort((a, b) &#061;&gt; a.start &#8211; b.start);<\/p>\n<p>            for (const part of downloadedParts) {<br \/>\n                if (currentPos &lt; part.start) {<br \/>\n                    \/\/ \u7f3a\u5931\u7684\u90e8\u5206<br \/>\n                    ranges.push({<br \/>\n                        start: currentPos,<br \/>\n                        end: part.start &#8211; 1,<br \/>\n                        downloaded: false<br \/>\n                    });<br \/>\n                }<br \/>\n                \/\/ \u5df2\u4e0b\u8f7d\u7684\u90e8\u5206<br \/>\n                ranges.push({<br \/>\n                    &#8230;part,<br \/>\n                    downloaded: true<br \/>\n                });<br \/>\n                currentPos &#061; part.end &#043; 1;<br \/>\n            }<\/p>\n<p>            \/\/ \u68c0\u67e5\u6700\u540e\u7684\u90e8\u5206<br \/>\n            if (currentPos &lt; fileSize) {<br \/>\n                ranges.push({<br \/>\n                    start: currentPos,<br \/>\n                    end: fileSize &#8211; 1,<br \/>\n                    downloaded: false<br \/>\n                });<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return ranges;<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u5e76\u884c\u4e0b\u8f7d\u8303\u56f4<br \/>\n     *\/<br \/>\n    async downloadRanges(url, ranges, callbacks) {<br \/>\n        const chunks &#061; new Array(ranges.length);<br \/>\n        const errors &#061; [];<\/p>\n<p>        \/\/ \u53ea\u4e0b\u8f7d\u672a\u5b8c\u6210\u7684\u90e8\u5206<br \/>\n        const rangesToDownload &#061; ranges.filter(r &#061;&gt; !r.downloaded);<\/p>\n<p>        \/\/ \u521b\u5efa\u4e0b\u8f7d\u4efb\u52a1<br \/>\n        const downloadTasks &#061; rangesToDownload.map((range, index) &#061;&gt; ({<br \/>\n            index: ranges.indexOf(range),<br \/>\n            range,<br \/>\n            retries: 0<br \/>\n        }));<\/p>\n<p>        \/\/ \u5e76\u884c\u4e0b\u8f7d<br \/>\n        await this.parallelDownload(url, downloadTasks, chunks, errors, callbacks);<\/p>\n<p>        \/\/ \u5904\u7406\u5df2\u4e0b\u8f7d\u7684\u90e8\u5206<br \/>\n        ranges.forEach((range, index) &#061;&gt; {<br \/>\n            if (range.downloaded) {<br \/>\n                \/\/ \u4ecelocalStorage\u52a0\u8f7d\u5df2\u4e0b\u8f7d\u7684\u6570\u636e<br \/>\n                const chunk &#061; this.loadChunkFromStorage(url, range);<br \/>\n                if (chunk) {<br \/>\n                    chunks[index] &#061; chunk;<br \/>\n                } else {<br \/>\n                    errors.push(&#096;Failed to load chunk ${index} from storage&#096;);<br \/>\n                }<br \/>\n            }<br \/>\n        });<\/p>\n<p>        if (errors.length &gt; 0) {<br \/>\n            throw new Error(&#096;Download failed: ${errors.join(&#039;, &#039;)}&#096;);<br \/>\n        }<\/p>\n<p>        return chunks;<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u5e76\u884c\u4e0b\u8f7d\u5b9e\u73b0<br \/>\n     *\/<br \/>\n    async parallelDownload(url, tasks, chunks, errors, callbacks) {<br \/>\n        return new Promise((resolve, reject) &#061;&gt; {<br \/>\n            let completed &#061; 0;<\/p>\n<p>            const processNext &#061; () &#061;&gt; {<br \/>\n                \/\/ \u627e\u5230\u4e0b\u4e00\u4e2a\u672a\u5f00\u59cb\u7684\u4efb\u52a1<br \/>\n                const task &#061; tasks.find(t &#061;&gt; !t.started &amp;&amp; t.retries &lt; this.options.maxRetries);<\/p>\n<p>                if (!task) {<br \/>\n                    if (completed &#061;&#061;&#061; tasks.length) {<br \/>\n                        resolve();<br \/>\n                    }<br \/>\n                    return;<br \/>\n                }<\/p>\n<p>                task.started &#061; true;<br \/>\n                this.activeDownloads&#043;&#043;;<\/p>\n<p>                this.downloadRange(url, task.range, task.retries)<br \/>\n                    .then(async (chunk) &#061;&gt; {<br \/>\n                        chunks[task.index] &#061; chunk;<\/p>\n<p>                        \/\/ \u4fdd\u5b58\u5230\u672c\u5730\u5b58\u50a8<br \/>\n                        await this.saveChunkToStorage(url, task.range, chunk);<\/p>\n<p>                        if (callbacks?.onProgress) {<br \/>\n                            callbacks.onProgress(task.index, 100);<br \/>\n                        }<\/p>\n<p>                        completed&#043;&#043;;<br \/>\n                        this.activeDownloads&#8211;;<\/p>\n<p>                        \/\/ \u5904\u7406\u4e0b\u4e00\u4e2a\u4efb\u52a1<br \/>\n                        setTimeout(processNext, 0);<br \/>\n                    })<br \/>\n                    .catch(async (error) &#061;&gt; {<br \/>\n                        console.error(&#096;Failed to download range ${task.index}:&#096;, error);<\/p>\n<p>                        task.retries&#043;&#043;;<br \/>\n                        task.started &#061; false;<br \/>\n                        this.activeDownloads&#8211;;<\/p>\n<p>                        if (task.retries &gt;&#061; this.options.maxRetries) {<br \/>\n                            errors.push(&#096;Range ${task.range.start}-${task.range.end}: ${error.message}&#096;);<br \/>\n                        }<\/p>\n<p>                        \/\/ \u5ef6\u8fdf\u540e\u91cd\u8bd5<br \/>\n                        setTimeout(processNext, this.options.retryDelay);<br \/>\n                    });<br \/>\n            };<\/p>\n<p>            \/\/ \u542f\u52a8\u521d\u59cb\u4e0b\u8f7d<br \/>\n            for (let i &#061; 0; i &lt; Math.min(this.options.parallelDownloads, tasks.length); i&#043;&#043;) {<br \/>\n                setTimeout(processNext, 0);<br \/>\n            }<br \/>\n        });<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u4e0b\u8f7d\u5355\u4e2a\u8303\u56f4<br \/>\n     *\/<br \/>\n    async downloadRange(url, range, retryCount &#061; 0) {<br \/>\n        const rangeHeader &#061; &#096;bytes&#061;${range.start}-${range.end}&#096;;<\/p>\n<p>        try {<br \/>\n            const response &#061; await fetch(url, {<br \/>\n                headers: {<br \/>\n                    &#039;Range&#039;: rangeHeader,<br \/>\n                    &#039;Accept&#039;: &#039;*\/*&#039;<br \/>\n                },<br \/>\n                \/\/ \u6dfb\u52a0\u8d85\u65f6\u548c\u91cd\u8bd5\u903b\u8f91<br \/>\n                signal: AbortSignal.timeout(30000)  \/\/ 30\u79d2\u8d85\u65f6<br \/>\n            });<\/p>\n<p>            if (response.status &#061;&#061;&#061; 206) {<br \/>\n                const blob &#061; await response.blob();<\/p>\n<p>                \/\/ \u9a8c\u8bc1\u4e0b\u8f7d\u7684\u5927\u5c0f<br \/>\n                if (blob.size !&#061;&#061; range.end &#8211; range.start &#043; 1) {<br \/>\n                    throw new Error(&#096;Size mismatch: expected ${range.end &#8211; range.start &#043; 1}, got ${blob.size}&#096;);<br \/>\n                }<\/p>\n<p>                return blob;<\/p>\n<p>            } else if (response.status &#061;&#061;&#061; 416) {<br \/>\n                \/\/ \u8303\u56f4\u4e0d\u53ef\u6ee1\u8db3&#xff0c;\u53ef\u80fd\u662f\u6587\u4ef6\u5df2\u66f4\u6539<br \/>\n                throw new Error(&#039;Range not satisfiable &#8211; file may have changed&#039;);<\/p>\n<p>            } else {<br \/>\n                throw new Error(&#096;HTTP ${response.status}&#096;);<br \/>\n            }<\/p>\n<p>        } catch (error) {<br \/>\n            if (retryCount &lt; this.options.maxRetries &#8211; 1) {<br \/>\n                console.log(&#096;Retrying range ${range.start}-${range.end} (attempt ${retryCount &#043; 2})&#096;);<br \/>\n            }<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u5408\u5e76\u5757<br \/>\n     *\/<br \/>\n    mergeChunks(chunks, totalSize) {<br \/>\n        \/\/ \u521b\u5efa\u4e00\u4e2a\u8db3\u591f\u5927\u7684ArrayBuffer<br \/>\n        const buffer &#061; new ArrayBuffer(totalSize);<br \/>\n        const view &#061; new Uint8Array(buffer);<\/p>\n<p>        let offset &#061; 0;<\/p>\n<p>        for (const chunk of chunks) {<br \/>\n            if (!chunk) {<br \/>\n                throw new Error(&#039;Missing chunk&#039;);<br \/>\n            }<\/p>\n<p>            const chunkArray &#061; new Uint8Array(chunk);<br \/>\n            view.set(chunkArray, offset);<br \/>\n            offset &#043;&#061; chunkArray.length;<br \/>\n        }<\/p>\n<p>        return new Blob([buffer]);<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u4fdd\u5b58\u5757\u5230\u672c\u5730\u5b58\u50a8<br \/>\n     *\/<br \/>\n    async saveChunkToStorage(url, range, chunk) {<br \/>\n        const storageKey &#061; &#096;download:${btoa(url)}&#096;;<\/p>\n<p>        try {<br \/>\n            \/\/ \u5c06\u5757\u8f6c\u6362\u4e3abase64<br \/>\n            const base64 &#061; await this.blobToBase64(chunk);<\/p>\n<p>            \/\/ \u83b7\u53d6\u73b0\u6709\u6570\u636e<br \/>\n            const existing &#061; localStorage.getItem(storageKey);<br \/>\n            const data &#061; existing ? JSON.parse(existing) : {<br \/>\n                parts: [],<br \/>\n                etag: null,<br \/>\n                size: 0<br \/>\n            };<\/p>\n<p>            \/\/ \u66f4\u65b0\u90e8\u5206\u4fe1\u606f<br \/>\n            const existingPartIndex &#061; data.parts.findIndex(p &#061;&gt;<br \/>\n                p.start &#061;&#061;&#061; range.start &amp;&amp; p.end &#061;&#061;&#061; range.end<br \/>\n            );<\/p>\n<p>            if (existingPartIndex &gt;&#061; 0) {<br \/>\n                data.parts[existingPartIndex].data &#061; base64;<br \/>\n            } else {<br \/>\n                data.parts.push({<br \/>\n                    start: range.start,<br \/>\n                    end: range.end,<br \/>\n                    data: base64<br \/>\n                });<br \/>\n            }<\/p>\n<p>            \/\/ \u4fdd\u5b58\u5230localStorage<br \/>\n            localStorage.setItem(storageKey, JSON.stringify(data));<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;Failed to save chunk:&#039;, error);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u4ece\u672c\u5730\u5b58\u50a8\u52a0\u8f7d\u5757<br \/>\n     *\/<br \/>\n    loadChunkFromStorage(url, range) {<br \/>\n        const storageKey &#061; &#096;download:${btoa(url)}&#096;;<\/p>\n<p>        try {<br \/>\n            const saved &#061; localStorage.getItem(storageKey);<br \/>\n            if (!saved) return null;<\/p>\n<p>            const data &#061; JSON.parse(saved);<br \/>\n            const part &#061; data.parts.find(p &#061;&gt;<br \/>\n                p.start &#061;&#061;&#061; range.start &amp;&amp; p.end &#061;&#061;&#061; range.end<br \/>\n            );<\/p>\n<p>            if (!part || !part.data) return null;<\/p>\n<p>            \/\/ \u5c06base64\u8f6c\u6362\u56deBlob<br \/>\n            return this.base64ToBlob(part.data, &#039;application\/octet-stream&#039;);<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;Failed to load chunk:&#039;, error);<br \/>\n            return null;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * Blob\u8f6cbase64<br \/>\n     *\/<br \/>\n    blobToBase64(blob) {<br \/>\n        return new Promise((resolve, reject) &#061;&gt; {<br \/>\n            const reader &#061; new FileReader();<br \/>\n            reader.onloadend &#061; () &#061;&gt; {<br \/>\n                const base64 &#061; reader.result.split(&#039;,&#039;)[1];<br \/>\n                resolve(base64);<br \/>\n            };<br \/>\n            reader.onerror &#061; reject;<br \/>\n            reader.readAsDataURL(blob);<br \/>\n        });<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * base64\u8f6cBlob<br \/>\n     *\/<br \/>\n    base64ToBlob(base64, contentType) {<br \/>\n        const byteCharacters &#061; atob(base64);<br \/>\n        const byteArrays &#061; [];<\/p>\n<p>        for (let offset &#061; 0; offset &lt; byteCharacters.length; offset &#043;&#061; 512) {<br \/>\n            const slice &#061; byteCharacters.slice(offset, offset &#043; 512);<br \/>\n            const byteNumbers &#061; new Array(slice.length);<\/p>\n<p>            for (let i &#061; 0; i &lt; slice.length; i&#043;&#043;) {<br \/>\n                byteNumbers[i] &#061; slice.charCodeAt(i);<br \/>\n            }<\/p>\n<p>            const byteArray &#061; new Uint8Array(byteNumbers);<br \/>\n            byteArrays.push(byteArray);<br \/>\n        }<\/p>\n<p>        return new Blob(byteArrays, { type: contentType });<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u8ba1\u7b97\u6574\u4f53\u8fdb\u5ea6<br \/>\n     *\/<br \/>\n    calculateOverallProgress(ranges, chunkIndex, chunkProgress) {<br \/>\n        let totalDownloaded &#061; 0;<\/p>\n<p>        for (let i &#061; 0; i &lt; ranges.length; i&#043;&#043;) {<br \/>\n            const range &#061; ranges[i];<br \/>\n            if (i &lt; chunkIndex) {<br \/>\n                \/\/ \u5df2\u5b8c\u6210\u7684\u8303\u56f4<br \/>\n                totalDownloaded &#043;&#061; range.end &#8211; range.start &#043; 1;<br \/>\n            } else if (i &#061;&#061;&#061; chunkIndex) {<br \/>\n                \/\/ \u5f53\u524d\u6b63\u5728\u4e0b\u8f7d\u7684\u8303\u56f4<br \/>\n                const rangeSize &#061; range.end &#8211; range.start &#043; 1;<br \/>\n                totalDownloaded &#043;&#061; (rangeSize * chunkProgress) \/ 100;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        const totalSize &#061; ranges[ranges.length &#8211; 1].end &#043; 1;<br \/>\n        return (totalDownloaded \/ totalSize) * 100;<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u6e05\u7406\u4e34\u65f6\u6570\u636e<br \/>\n     *\/<br \/>\n    async cleanupTempData(url) {<br \/>\n        const storageKey &#061; &#096;download:${btoa(url)}&#096;;<br \/>\n        localStorage.removeItem(storageKey);<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u5b8c\u6574\u6587\u4ef6\u4e0b\u8f7d&#xff08;\u4e0d\u652f\u6301\u8303\u56f4\u8bf7\u6c42\u65f6\u7684\u56de\u9000&#xff09;<br \/>\n     *\/<br \/>\n    async downloadFullFile(url, options) {<br \/>\n        const { onProgress, onComplete, onError } &#061; options;<\/p>\n<p>        try {<br \/>\n            const response &#061; await fetch(url);<\/p>\n<p>            if (!response.ok) {<br \/>\n                throw new Error(&#096;HTTP ${response.status}&#096;);<br \/>\n            }<\/p>\n<p>            const reader &#061; response.body.getReader();<br \/>\n            const contentLength &#061; parseInt(response.headers.get(&#039;Content-Length&#039;), 10) || 0;<\/p>\n<p>            let receivedLength &#061; 0;<br \/>\n            const chunks &#061; [];<\/p>\n<p>            while (true) {<br \/>\n                const { done, value } &#061; await reader.read();<\/p>\n<p>                if (done) {<br \/>\n                    break;<br \/>\n                }<\/p>\n<p>                chunks.push(value);<br \/>\n                receivedLength &#043;&#061; value.length;<\/p>\n<p>                if (onProgress &amp;&amp; contentLength &gt; 0) {<br \/>\n                    onProgress((receivedLength \/ contentLength) * 100);<br \/>\n                }<br \/>\n            }<\/p>\n<p>            const blob &#061; new Blob(chunks);<\/p>\n<p>            if (onComplete) {<br \/>\n                onComplete(blob, {<br \/>\n                    size: contentLength,<br \/>\n                    contentType: response.headers.get(&#039;Content-Type&#039;)<br \/>\n                });<br \/>\n            }<\/p>\n<p>            return blob;<\/p>\n<p>        } catch (error) {<br \/>\n            if (onError) {<br \/>\n                onError(error);<br \/>\n            }<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\nconst downloadManager &#061; new RangeRequestClient({<br \/>\n    chunkSize: 1024 * 1024 * 5,  \/\/ 5MB\u5757<br \/>\n    parallelDownloads: 4,        \/\/ 4\u4e2a\u5e76\u884c\u4e0b\u8f7d<br \/>\n    maxRetries: 3                \/\/ \u6700\u591a\u91cd\u8bd53\u6b21<br \/>\n});<\/p>\n<p>\/\/ \u5f00\u59cb\u4e0b\u8f7d<br \/>\ndocument.getElementById(&#039;download-btn&#039;).addEventListener(&#039;click&#039;, async () &#061;&gt; {<br \/>\n    const url &#061; document.getElementById(&#039;file-url&#039;).value;<br \/>\n    const progressBar &#061; document.getElementById(&#039;progress-bar&#039;);<br \/>\n    const statusText &#061; document.getElementById(&#039;status-text&#039;);<\/p>\n<p>    try {<br \/>\n        statusText.textContent &#061; &#039;\u6b63\u5728\u51c6\u5907\u4e0b\u8f7d&#8230;&#039;;<\/p>\n<p>        await downloadManager.downloadFile(url, {<br \/>\n            resume: true,  \/\/ \u542f\u7528\u65ad\u70b9\u7eed\u4f20<\/p>\n<p>            onProgress: (progress) &#061;&gt; {<br \/>\n                progressBar.style.width &#061; &#096;${progress}%&#096;;<br \/>\n                progressBar.textContent &#061; &#096;${progress.toFixed(1)}%&#096;;<br \/>\n                statusText.textContent &#061; &#096;\u4e0b\u8f7d\u4e2d: ${progress.toFixed(1)}%&#096;;<br \/>\n            },<\/p>\n<p>            onComplete: (blob, fileInfo) &#061;&gt; {<br \/>\n                statusText.textContent &#061; &#039;\u4e0b\u8f7d\u5b8c\u6210!&#039;;<\/p>\n<p>                \/\/ \u521b\u5efa\u4e0b\u8f7d\u94fe\u63a5<br \/>\n                const downloadUrl &#061; URL.createObjectURL(blob);<br \/>\n                const a &#061; document.createElement(&#039;a&#039;);<br \/>\n                a.href &#061; downloadUrl;<br \/>\n                a.download &#061; getFileNameFromUrl(url);<br \/>\n                a.click();<\/p>\n<p>                \/\/ \u6e05\u7406<br \/>\n                URL.revokeObjectURL(downloadUrl);<br \/>\n            },<\/p>\n<p>            onError: (error) &#061;&gt; {<br \/>\n                console.error(&#039;\u4e0b\u8f7d\u5931\u8d25:&#039;, error);<br \/>\n                statusText.textContent &#061; &#096;\u4e0b\u8f7d\u5931\u8d25: ${error.message}&#096;;<br \/>\n                progressBar.style.backgroundColor &#061; &#039;#dc3545&#039;;<br \/>\n            }<br \/>\n        });<\/p>\n<p>    } catch (error) {<br \/>\n        console.error(&#039;\u4e0b\u8f7d\u5931\u8d25:&#039;, error);<br \/>\n        statusText.textContent &#061; &#096;\u9519\u8bef: ${error.message}&#096;;<br \/>\n    }<br \/>\n});<\/p>\n<p>\/\/ \u6682\u505c\u4e0b\u8f7d&#xff08;\u793a\u4f8b&#xff09;<br \/>\ndocument.getElementById(&#039;pause-btn&#039;).addEventListener(&#039;click&#039;, () &#061;&gt; {<br \/>\n    \/\/ \u5728\u5b9e\u9645\u5b9e\u73b0\u4e2d&#xff0c;\u8fd9\u91cc\u9700\u8981\u5b58\u50a8\u5f53\u524d\u72b6\u6001<br \/>\n    localStorage.setItem(&#039;downloadPaused&#039;, &#039;true&#039;);<br \/>\n    alert(&#039;\u4e0b\u8f7d\u5df2\u6682\u505c&#xff0c;\u4e0b\u6b21\u53ef\u4ee5\u6062\u590d\u4e0b\u8f7d&#039;);<br \/>\n});<\/p>\n<p>function getFileNameFromUrl(url) {<br \/>\n    return url.split(&#039;\/&#039;).pop() || &#039;download.bin&#039;;<br \/>\n}<\/p>\n<h3>8.5 \u6700\u4f73\u5b9e\u8df5\u4e0e\u6027\u80fd\u4f18\u5316<\/h3>\n<h4>\u6027\u80fd\u4f18\u5316\u7b56\u7565<\/h4>\n<p>python<\/p>\n<p># \u8303\u56f4\u8bf7\u6c42\u6027\u80fd\u4f18\u5316\u5668<br \/>\nclass RangeRequestOptimizer:<br \/>\n    &#034;&#034;&#034;\u8303\u56f4\u8bf7\u6c42\u6027\u80fd\u4f18\u5316\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.cache &#061; {}<br \/>\n        self.statistics &#061; {<br \/>\n            &#039;total_requests&#039;: 0,<br \/>\n            &#039;range_requests&#039;: 0,<br \/>\n            &#039;bytes_served&#039;: 0,<br \/>\n            &#039;cache_hits&#039;: 0,<br \/>\n            &#039;cache_misses&#039;: 0<br \/>\n        }<\/p>\n<p>    def optimize_response(self, file_path, range_info, use_cache&#061;True):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u4f18\u5316\u8303\u56f4\u54cd\u5e94<\/p>\n<p>        Args:<br \/>\n            file_path: \u6587\u4ef6\u8def\u5f84<br \/>\n            range_info: \u8303\u56f4\u4fe1\u606f<br \/>\n            use_cache: \u662f\u5426\u4f7f\u7528\u7f13\u5b58<\/p>\n<p>        Returns:<br \/>\n            (data, from_cache)<br \/>\n        &#034;&#034;&#034;<br \/>\n        self.statistics[&#039;total_requests&#039;] &#043;&#061; 1<br \/>\n        self.statistics[&#039;range_requests&#039;] &#043;&#061; 1<\/p>\n<p>        cache_key &#061; self._generate_cache_key(file_path, range_info)<\/p>\n<p>        # \u68c0\u67e5\u7f13\u5b58<br \/>\n        if use_cache and cache_key in self.cache:<br \/>\n            self.statistics[&#039;cache_hits&#039;] &#043;&#061; 1<br \/>\n            return self.cache[cache_key], True<\/p>\n<p>        # \u4ece\u6587\u4ef6\u8bfb\u53d6<br \/>\n        data &#061; self._read_file_range(file_path, range_info)<\/p>\n<p>        # \u7f13\u5b58\u7ed3\u679c&#xff08;\u5982\u679c\u503c\u5f97\u7f13\u5b58&#xff09;<br \/>\n        if self._should_cache(range_info):<br \/>\n            self.cache[cache_key] &#061; data<br \/>\n            self.statistics[&#039;cache_misses&#039;] &#043;&#061; 1<\/p>\n<p>        return data, False<\/p>\n<p>    def _generate_cache_key(self, file_path, range_info):<br \/>\n        &#034;&#034;&#034;\u751f\u6210\u7f13\u5b58\u952e&#034;&#034;&#034;<br \/>\n        import hashlib<br \/>\n        import os<\/p>\n<p>        stat &#061; os.stat(file_path)<br \/>\n        key_content &#061; f&#034;{file_path}:{stat.st_mtime}:{range_info[&#039;start&#039;]}:{range_info[&#039;end&#039;]}&#034;<br \/>\n        return hashlib.md5(key_content.encode()).hexdigest()<\/p>\n<p>    def _read_file_range(self, file_path, range_info):<br \/>\n        &#034;&#034;&#034;\u4ece\u6587\u4ef6\u8bfb\u53d6\u8303\u56f4&#034;&#034;&#034;<br \/>\n        start &#061; range_info[&#039;start&#039;]<br \/>\n        end &#061; range_info[&#039;end&#039;]<br \/>\n        length &#061; end &#8211; start &#043; 1<\/p>\n<p>        with open(file_path, &#039;rb&#039;) as f:<br \/>\n            f.seek(start)<br \/>\n            return f.read(length)<\/p>\n<p>    def _should_cache(self, range_info):<br \/>\n        &#034;&#034;&#034;\u5224\u65ad\u662f\u5426\u5e94\u8be5\u7f13\u5b58&#034;&#034;&#034;<br \/>\n        # \u57fa\u4e8e\u8303\u56f4\u5927\u5c0f\u51b3\u5b9a\u662f\u5426\u7f13\u5b58<br \/>\n        range_size &#061; range_info[&#039;end&#039;] &#8211; range_info[&#039;start&#039;] &#043; 1<\/p>\n<p>        # \u5c0f\u8303\u56f4\u8bf7\u6c42\u66f4\u53ef\u80fd\u88ab\u91cd\u590d\u8bf7\u6c42&#xff08;\u4f8b\u5982\u89c6\u9891\u6d41\u7684\u5f00\u59cb\u90e8\u5206&#xff09;<br \/>\n        if range_size &lt;&#061; 1024 * 1024:  # 1MB\u4ee5\u4e0b<br \/>\n            return True<\/p>\n<p>        # \u7279\u5b9a\u6a21\u5f0f\u7684\u8303\u56f4\u8bf7\u6c42&#xff08;\u4f8b\u5982\u89c6\u9891\u7684\u56fa\u5b9a\u65f6\u95f4\u70b9&#xff09;<br \/>\n        if range_info[&#039;start&#039;] % (1024 * 1024) &#061;&#061; 0:  # \u5bf9\u9f50\u52301MB\u8fb9\u754c<br \/>\n            return True<\/p>\n<p>        return False<\/p>\n<p>    def preload_cache(self, file_path, common_ranges):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u9884\u52a0\u8f7d\u5e38\u7528\u8303\u56f4\u5230\u7f13\u5b58<\/p>\n<p>        Args:<br \/>\n            file_path: \u6587\u4ef6\u8def\u5f84<br \/>\n            common_ranges: \u5e38\u7528\u8303\u56f4\u5217\u8868<br \/>\n        &#034;&#034;&#034;<br \/>\n        for range_info in common_ranges:<br \/>\n            cache_key &#061; self._generate_cache_key(file_path, range_info)<br \/>\n            if cache_key not in self.cache:<br \/>\n                data &#061; self._read_file_range(file_path, range_info)<br \/>\n                self.cache[cache_key] &#061; data<\/p>\n<p>    def cleanup_cache(self, max_size_mb&#061;100):<br \/>\n        &#034;&#034;&#034;\u6e05\u7406\u7f13\u5b58&#xff0c;\u4fdd\u6301\u6700\u5927\u5927\u5c0f&#034;&#034;&#034;<br \/>\n        import sys<\/p>\n<p>        cache_size &#061; sum(sys.getsizeof(v) for v in self.cache.values())<br \/>\n        max_size &#061; max_size_mb * 1024 * 1024<\/p>\n<p>        if cache_size &gt; max_size:<br \/>\n            # \u4f7f\u7528LRU\u7b56\u7565\u6e05\u7406\u7f13\u5b58<br \/>\n            # \u8fd9\u91cc\u7b80\u5316\u5b9e\u73b0&#xff1a;\u76f4\u63a5\u6e05\u7406\u4e00\u534a\u7f13\u5b58<br \/>\n            keys &#061; list(self.cache.keys())<br \/>\n            for key in keys[:len(keys)\/\/2]:<br \/>\n                del self.cache[key]<\/p>\n<p>    def get_optimization_suggestions(self, request_patterns):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u57fa\u4e8e\u8bf7\u6c42\u6a21\u5f0f\u63d0\u4f9b\u4f18\u5316\u5efa\u8bae<\/p>\n<p>        Args:<br \/>\n            request_patterns: \u8bf7\u6c42\u6a21\u5f0f\u5217\u8868<\/p>\n<p>        Returns:<br \/>\n            \u4f18\u5316\u5efa\u8bae\u5b57\u5178<br \/>\n        &#034;&#034;&#034;<br \/>\n        suggestions &#061; []<\/p>\n<p>        # \u5206\u6790\u8bf7\u6c42\u6a21\u5f0f<br \/>\n        sequential_requests &#061; self._analyze_sequential_requests(request_patterns)<br \/>\n        repeated_ranges &#061; self._find_repeated_ranges(request_patterns)<\/p>\n<p>        # \u5efa\u8bae1&#xff1a;\u9884\u52a0\u8f7d\u5e38\u7528\u8303\u56f4<br \/>\n        if repeated_ranges:<br \/>\n            suggestions.append({<br \/>\n                &#039;type&#039;: &#039;preload&#039;,<br \/>\n                &#039;priority&#039;: &#039;high&#039;,<br \/>\n                &#039;message&#039;: f&#039;\u53d1\u73b0{len(repeated_ranges)}\u4e2a\u91cd\u590d\u8bf7\u6c42\u7684\u8303\u56f4&#xff0c;\u5efa\u8bae\u9884\u52a0\u8f7d\u5230\u7f13\u5b58&#039;,<br \/>\n                &#039;ranges&#039;: repeated_ranges[:5]  # \u524d5\u4e2a\u6700\u5e38\u8bf7\u6c42\u7684\u8303\u56f4<br \/>\n            })<\/p>\n<p>        # \u5efa\u8bae2&#xff1a;\u8c03\u6574\u5757\u5927\u5c0f<br \/>\n        avg_range_size &#061; self._calculate_average_range_size(request_patterns)<br \/>\n        if avg_range_size &gt; 1024 * 1024 * 10:  # 10MB\u4ee5\u4e0a<br \/>\n            suggestions.append({<br \/>\n                &#039;type&#039;: &#039;chunk_size&#039;,<br \/>\n                &#039;priority&#039;: &#039;medium&#039;,<br \/>\n                &#039;message&#039;: f&#039;\u5e73\u5747\u8bf7\u6c42\u8303\u56f4\u8f83\u5927({avg_range_size\/1024\/1024:.1f}MB)&#xff0c;\u5efa\u8bae\u589e\u52a0\u5757\u5927\u5c0f&#039;,<br \/>\n                &#039;recommended_size&#039;: min(avg_range_size * 2, 1024 * 1024 * 100)  # \u6700\u5927100MB<br \/>\n            })<\/p>\n<p>        # \u5efa\u8bae3&#xff1a;\u987a\u5e8f\u8bfb\u53d6\u4f18\u5316<br \/>\n        if sequential_requests[&#039;is_sequential&#039;]:<br \/>\n            suggestions.append({<br \/>\n                &#039;type&#039;: &#039;sequential_read&#039;,<br \/>\n                &#039;priority&#039;: &#039;low&#039;,<br \/>\n                &#039;message&#039;: &#039;\u68c0\u6d4b\u5230\u987a\u5e8f\u8bfb\u53d6\u6a21\u5f0f&#xff0c;\u53ef\u4ee5\u542f\u7528\u9884\u8bfb\u4f18\u5316&#039;,<br \/>\n                &#039;read_ahead_size&#039;: sequential_requests[&#039;avg_gap&#039;] * 2<br \/>\n            })<\/p>\n<p>        return suggestions<\/p>\n<p>    def _analyze_sequential_requests(self, patterns):<br \/>\n        &#034;&#034;&#034;\u5206\u6790\u987a\u5e8f\u8bf7\u6c42\u6a21\u5f0f&#034;&#034;&#034;<br \/>\n        if len(patterns) &lt; 3:<br \/>\n            return {&#039;is_sequential&#039;: False}<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u5927\u81f4\u8fde\u7eed<br \/>\n        gaps &#061; []<br \/>\n        for i in range(1, len(patterns)):<br \/>\n            prev_end &#061; patterns[i-1][&#039;end&#039;]<br \/>\n            curr_start &#061; patterns[i][&#039;start&#039;]<\/p>\n<p>            if curr_start &gt;&#061; prev_end:<br \/>\n                gaps.append(curr_start &#8211; prev_end)<\/p>\n<p>        if len(gaps) &#061;&#061; 0:<br \/>\n            return {&#039;is_sequential&#039;: False}<\/p>\n<p>        avg_gap &#061; sum(gaps) \/ len(gaps)<\/p>\n<p>        # \u5982\u679c\u5e73\u5747\u95f4\u9694\u5c0f\u4e8e\u5757\u5927\u5c0f\u768410%&#xff0c;\u8ba4\u4e3a\u662f\u987a\u5e8f\u8bfb\u53d6<br \/>\n        avg_range_size &#061; self._calculate_average_range_size(patterns)<\/p>\n<p>        return {<br \/>\n            &#039;is_sequential&#039;: avg_gap &lt; avg_range_size * 0.1,<br \/>\n            &#039;avg_gap&#039;: avg_gap,<br \/>\n            &#039;gap_count&#039;: len(gaps)<br \/>\n        }<\/p>\n<p>    def _find_repeated_ranges(self, patterns):<br \/>\n        &#034;&#034;&#034;\u67e5\u627e\u91cd\u590d\u8bf7\u6c42\u7684\u8303\u56f4&#034;&#034;&#034;<br \/>\n        from collections import Counter<\/p>\n<p>        range_strings &#061; [f&#034;{p[&#039;start&#039;]}-{p[&#039;end&#039;]}&#034; for p in patterns]<br \/>\n        counter &#061; Counter(range_strings)<\/p>\n<p>        # \u8fd4\u56de\u8bf7\u6c42\u6b21\u6570\u5927\u4e8e1\u7684\u8303\u56f4<br \/>\n        repeated &#061; []<br \/>\n        for range_str, count in counter.items():<br \/>\n            if count &gt; 1:<br \/>\n                start, end &#061; map(int, range_str.split(&#039;-&#039;))<br \/>\n                repeated.append({<br \/>\n                    &#039;start&#039;: start,<br \/>\n                    &#039;end&#039;: end,<br \/>\n                    &#039;count&#039;: count<br \/>\n                })<\/p>\n<p>        # \u6309\u8bf7\u6c42\u6b21\u6570\u6392\u5e8f<br \/>\n        repeated.sort(key&#061;lambda x: x[&#039;count&#039;], reverse&#061;True)<br \/>\n        return repeated<\/p>\n<p>    def _calculate_average_range_size(self, patterns):<br \/>\n        &#034;&#034;&#034;\u8ba1\u7b97\u5e73\u5747\u8303\u56f4\u5927\u5c0f&#034;&#034;&#034;<br \/>\n        if not patterns:<br \/>\n            return 0<\/p>\n<p>        total_size &#061; sum(p[&#039;end&#039;] &#8211; p[&#039;start&#039;] &#043; 1 for p in patterns)<br \/>\n        return total_size \/ len(patterns)<\/p>\n<p># \u5b8c\u6574\u7684HTTP\u8303\u56f4\u8bf7\u6c42\u670d\u52a1\u5668\u4f18\u5316\u7248<br \/>\nclass OptimizedRangeServer:<br \/>\n    &#034;&#034;&#034;\u4f18\u5316\u7684\u8303\u56f4\u8bf7\u6c42\u670d\u52a1\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, base_dir, cache_size_mb&#061;100):<br \/>\n        self.base_dir &#061; base_dir<br \/>\n        self.optimizer &#061; RangeRequestOptimizer()<br \/>\n        self.cache_size_mb &#061; cache_size_mb<\/p>\n<p>        # \u9884\u5b9a\u4e49\u5e38\u7528\u8303\u56f4&#xff08;\u4f8b\u5982\u89c6\u9891\u6587\u4ef6\u7684\u5f00\u59cb\u90e8\u5206&#xff09;<br \/>\n        self.common_ranges &#061; [<br \/>\n            {&#039;start&#039;: 0, &#039;end&#039;: 1024 * 1024 &#8211; 1},      # \u7b2c\u4e00\u4e2a1MB<br \/>\n            {&#039;start&#039;: 0, &#039;end&#039;: 1024 * 10 &#8211; 1},        # \u524d10KB&#xff08;\u6587\u4ef6\u5934&#xff09;<br \/>\n            {&#039;start&#039;: 0, &#039;end&#039;: 1024 * 512 &#8211; 1},       # \u524d512KB<br \/>\n        ]<\/p>\n<p>    def serve_request(self, file_path, range_header):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u63d0\u4f9b\u8303\u56f4\u8bf7\u6c42\u670d\u52a1<\/p>\n<p>        Args:<br \/>\n            file_path: \u76f8\u5bf9\u6587\u4ef6\u8def\u5f84<br \/>\n            range_header: Range\u5934\u90e8\u503c<\/p>\n<p>        Returns:<br \/>\n            HTTP\u54cd\u5e94<br \/>\n        &#034;&#034;&#034;<br \/>\n        full_path &#061; os.path.join(self.base_dir, file_path)<\/p>\n<p>        if not os.path.exists(full_path):<br \/>\n            return self._error_response(404, &#039;File not found&#039;)<\/p>\n<p>        file_size &#061; os.path.getsize(full_path)<\/p>\n<p>        # \u89e3\u6790Range\u5934\u90e8<br \/>\n        parser &#061; RangeHeaderParser()<br \/>\n        parse_result &#061; parser.parse(range_header, file_size)<\/p>\n<p>        if not parse_result[&#039;valid&#039;]:<br \/>\n            return self._range_not_satisfiable_response(file_size)<\/p>\n<p>        ranges &#061; parse_result[&#039;ranges&#039;]<\/p>\n<p>        # \u5355\u4e2a\u8303\u56f4<br \/>\n        if len(ranges) &#061;&#061; 1:<br \/>\n            return self._serve_single_range(full_path, ranges[0], file_size)<\/p>\n<p>        # \u591a\u4e2a\u8303\u56f4<br \/>\n        else:<br \/>\n            return self._serve_multipart_ranges(full_path, ranges, file_size)<\/p>\n<p>    def _serve_single_range(self, file_path, range_info, file_size):<br \/>\n        &#034;&#034;&#034;\u63d0\u4f9b\u5355\u4e2a\u8303\u56f4&#034;&#034;&#034;<br \/>\n        # \u4f7f\u7528\u4f18\u5316\u5668\u83b7\u53d6\u6570\u636e<br \/>\n        data, from_cache &#061; self.optimizer.optimize_response(<br \/>\n            file_path, range_info, use_cache&#061;True<br \/>\n        )<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Content-Range&#039;: f&#039;bytes {range_info[&#034;start&#034;]}-{range_info[&#034;end&#034;]}\/{file_size}&#039;,<br \/>\n            &#039;Content-Length&#039;: str(len(data)),<br \/>\n            &#039;Content-Type&#039;: self._get_content_type(file_path),<br \/>\n            &#039;Accept-Ranges&#039;: &#039;bytes&#039;,<br \/>\n            &#039;Cache-Control&#039;: &#039;public, max-age&#061;31536000&#039;,<br \/>\n            &#039;X-Cache&#039;: &#039;HIT&#039; if from_cache else &#039;MISS&#039;,<br \/>\n            &#039;X-Optimized&#039;: &#039;true&#039;<br \/>\n        }<\/p>\n<p>        return Response(data, status&#061;206, headers&#061;headers)<\/p>\n<p>    def _serve_multipart_ranges(self, file_path, ranges, file_size):<br \/>\n        &#034;&#034;&#034;\u63d0\u4f9b\u591a\u4e2a\u8303\u56f4&#034;&#034;&#034;<br \/>\n        boundary &#061; f&#034;boundary_{hash(str(ranges))}_{int(time.time())}&#034;<br \/>\n        content_type &#061; f&#034;multipart\/byteranges; boundary&#061;{boundary}&#034;<\/p>\n<p>        # \u751f\u6210\u591a\u90e8\u5206\u54cd\u5e94<br \/>\n        parts &#061; []<br \/>\n        total_length &#061; 0<\/p>\n<p>        for range_info in ranges:<br \/>\n            # \u83b7\u53d6\u8303\u56f4\u6570\u636e<br \/>\n            data, from_cache &#061; self.optimizer.optimize_response(<br \/>\n                file_path, range_info, use_cache&#061;True<br \/>\n            )<\/p>\n<p>            # \u6784\u5efa\u90e8\u5206<br \/>\n            part &#061; self._build_multipart_part(<br \/>\n                boundary, range_info, file_size, data, from_cache<br \/>\n            )<br \/>\n            parts.append(part)<br \/>\n            total_length &#043;&#061; len(part)<\/p>\n<p>        # \u6dfb\u52a0\u7ed3\u675f\u8fb9\u754c<br \/>\n        end_boundary &#061; f&#034;\\\\r\\\\n&#8211;{boundary}&#8211;\\\\r\\\\n&#034;<br \/>\n        parts.append(end_boundary.encode())<br \/>\n        total_length &#043;&#061; len(end_boundary)<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Content-Type&#039;: content_type,<br \/>\n            &#039;Content-Length&#039;: str(total_length),<br \/>\n            &#039;Accept-Ranges&#039;: &#039;bytes&#039;,<br \/>\n            &#039;Cache-Control&#039;: &#039;no-store&#039;,<br \/>\n            &#039;X-Optimized&#039;: &#039;true&#039;<br \/>\n        }<\/p>\n<p>        # \u7ec4\u5408\u6240\u6709\u90e8\u5206<br \/>\n        def generate():<br \/>\n            for part in parts:<br \/>\n                if isinstance(part, bytes):<br \/>\n                    yield part<br \/>\n                else:<br \/>\n                    yield part<\/p>\n<p>        return Response(generate(), status&#061;206, headers&#061;headers)<\/p>\n<p>    def _build_multipart_part(self, boundary, range_info, file_size, data, from_cache):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa\u591a\u90e8\u5206\u54cd\u5e94\u7684\u5355\u4e2a\u90e8\u5206&#034;&#034;&#034;<br \/>\n        part_headers &#061; [<br \/>\n            f&#039;&#8211;{boundary}&#039;,<br \/>\n            f&#039;Content-Range: bytes {range_info[&#034;start&#034;]}-{range_info[&#034;end&#034;]}\/{file_size}&#039;,<br \/>\n            f&#039;Content-Type: {self._get_content_type(None)}&#039;,<br \/>\n            f&#039;X-Cache: {&#034;HIT&#034; if from_cache else &#034;MISS&#034;}&#039;,<br \/>\n            &#039;&#039;<br \/>\n        ]<\/p>\n<p>        header_bytes &#061; &#039;\\\\r\\\\n&#039;.join(part_headers).encode()<br \/>\n        return header_bytes &#043; data<\/p>\n<p>    def _get_content_type(self, file_path):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u5185\u5bb9\u7c7b\u578b&#034;&#034;&#034;<br \/>\n        if not file_path:<br \/>\n            return &#039;application\/octet-stream&#039;<\/p>\n<p>        mime_types &#061; {<br \/>\n            &#039;.mp4&#039;: &#039;video\/mp4&#039;,<br \/>\n            &#039;.avi&#039;: &#039;video\/x-msvideo&#039;,<br \/>\n            &#039;.mkv&#039;: &#039;video\/x-matroska&#039;,<br \/>\n            &#039;.mov&#039;: &#039;video\/quicktime&#039;,<br \/>\n            &#039;.mp3&#039;: &#039;audio\/mpeg&#039;,<br \/>\n            &#039;.jpg&#039;: &#039;image\/jpeg&#039;,<br \/>\n            &#039;.png&#039;: &#039;image\/png&#039;,<br \/>\n            &#039;.pdf&#039;: &#039;application\/pdf&#039;,<br \/>\n            &#039;.zip&#039;: &#039;application\/zip&#039;<br \/>\n        }<\/p>\n<p>        ext &#061; os.path.splitext(file_path)[1].lower()<br \/>\n        return mime_types.get(ext, &#039;application\/octet-stream&#039;)<\/p>\n<p>    def _error_response(self, status, message):<br \/>\n        &#034;&#034;&#034;\u9519\u8bef\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        return Response(message, status&#061;status)<\/p>\n<p>    def _range_not_satisfiable_response(self, file_size):<br \/>\n        &#034;&#034;&#034;416\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        return Response(<br \/>\n            &#039;Requested range not satisfiable&#039;,<br \/>\n            status&#061;416,<br \/>\n            headers&#061;{<br \/>\n                &#039;Content-Range&#039;: f&#039;bytes *\/{file_size}&#039;,<br \/>\n                &#039;Content-Type&#039;: &#039;text\/plain&#039;<br \/>\n            }<br \/>\n        )<\/p>\n<p>    def preload_common_ranges(self, file_path):<br \/>\n        &#034;&#034;&#034;\u9884\u52a0\u8f7d\u5e38\u7528\u8303\u56f4&#034;&#034;&#034;<br \/>\n        full_path &#061; os.path.join(self.base_dir, file_path)<br \/>\n        if os.path.exists(full_path):<br \/>\n            self.optimizer.preload_cache(full_path, self.common_ranges)<\/p>\n<p>    def cleanup(self):<br \/>\n        &#034;&#034;&#034;\u6e05\u7406\u8d44\u6e90&#034;&#034;&#034;<br \/>\n        self.optimizer.cleanup_cache(self.cache_size_mb)<\/p>\n<p>    def get_stats(self):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u7edf\u8ba1\u4fe1\u606f&#034;&#034;&#034;<br \/>\n        return self.optimizer.statistics<\/p>\n<p># \u4f7f\u7528\u793a\u4f8b<br \/>\nif __name__ &#061;&#061; &#039;__main__&#039;:<br \/>\n    import argparse<\/p>\n<p>    parser &#061; argparse.ArgumentParser(description&#061;&#039;Optimized Range Request Server&#039;)<br \/>\n    parser.add_argument(&#039;&#8211;port&#039;, type&#061;int, default&#061;8080, help&#061;&#039;Port to listen on&#039;)<br \/>\n    parser.add_argument(&#039;&#8211;dir&#039;, default&#061;&#039;.\/files&#039;, help&#061;&#039;Directory to serve files from&#039;)<br \/>\n    parser.add_argument(&#039;&#8211;cache&#039;, type&#061;int, default&#061;100, help&#061;&#039;Cache size in MB&#039;)<\/p>\n<p>    args &#061; parser.parse_args()<\/p>\n<p>    # \u521b\u5efa\u670d\u52a1\u5668<br \/>\n    server &#061; OptimizedRangeServer(args.dir, args.cache)<\/p>\n<p>    # \u521b\u5efaFlask\u5e94\u7528<br \/>\n    app &#061; Flask(__name__)<\/p>\n<p>    &#064;app.route(&#039;\/files\/&lt;path:filename&gt;&#039;)<br \/>\n    def serve_file(filename):<br \/>\n        &#034;&#034;&#034;\u63d0\u4f9b\u6587\u4ef6\u670d\u52a1&#034;&#034;&#034;<br \/>\n        range_header &#061; request.headers.get(&#039;Range&#039;)<\/p>\n<p>        if range_header:<br \/>\n            return server.serve_request(filename, range_header)<br \/>\n        else:<br \/>\n            # \u5b8c\u6574\u6587\u4ef6\u8bf7\u6c42<br \/>\n            full_path &#061; os.path.join(server.base_dir, filename)<br \/>\n            if os.path.exists(full_path):<br \/>\n                return send_file(full_path)<br \/>\n            else:<br \/>\n                return jsonify({&#039;error&#039;: &#039;File not found&#039;}), 404<\/p>\n<p>    &#064;app.route(&#039;\/preload\/&lt;path:filename&gt;&#039;)<br \/>\n    def preload_file(filename):<br \/>\n        &#034;&#034;&#034;\u9884\u52a0\u8f7d\u6587\u4ef6\u5e38\u7528\u8303\u56f4&#034;&#034;&#034;<br \/>\n        try:<br \/>\n            server.preload_common_ranges(filename)<br \/>\n            return jsonify({<br \/>\n                &#039;success&#039;: True,<br \/>\n                &#039;message&#039;: f&#039;Preloaded common ranges for {filename}&#039;<br \/>\n            })<br \/>\n        except Exception as e:<br \/>\n            return jsonify({<br \/>\n                &#039;success&#039;: False,<br \/>\n                &#039;error&#039;: str(e)<br \/>\n            }), 500<\/p>\n<p>    &#064;app.route(&#039;\/stats&#039;)<br \/>\n    def get_stats():<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u670d\u52a1\u5668\u7edf\u8ba1&#034;&#034;&#034;<br \/>\n        return jsonify(server.get_stats())<\/p>\n<p>    &#064;app.before_request<br \/>\n    def log_request():<br \/>\n        &#034;&#034;&#034;\u8bb0\u5f55\u8bf7\u6c42\u65e5\u5fd7&#034;&#034;&#034;<br \/>\n        app.logger.info(f&#034;{request.method} {request.path} &#8211; Range: {request.headers.get(&#039;Range&#039;)}&#034;)<\/p>\n<p>    # \u5b9a\u671f\u6e05\u7406\u7f13\u5b58<br \/>\n    import atexit<br \/>\n    atexit.register(server.cleanup)<\/p>\n<p>    # \u542f\u52a8\u670d\u52a1\u5668<br \/>\n    app.run(host&#061;&#039;0.0.0.0&#039;, port&#061;args.port, threaded&#061;True)<\/p>\n<h4>\u6700\u4f73\u5b9e\u8df5\u603b\u7ed3<\/h4>\n<p>yaml<\/p>\n<p># 206 Partial Content\u6700\u4f73\u5b9e\u8df5\u6e05\u5355<br \/>\nbest_practices_206:<\/p>\n<p>  # \u670d\u52a1\u5668\u5b9e\u73b0<br \/>\n  server_implementation:<br \/>\n    &#8211; \u59cb\u7ec8\u8bbe\u7f6eAccept-Ranges: bytes\u5934\u90e8<br \/>\n    &#8211; \u6b63\u786e\u89e3\u6790\u548c\u5904\u7406Range\u8bf7\u6c42\u5934\u90e8<br \/>\n    &#8211; \u5bf9\u4e8e\u65e0\u6548\u8303\u56f4\u8fd4\u56de416 Range Not Satisfiable<br \/>\n    &#8211; \u652f\u6301\u5355\u8303\u56f4\u548c\u591a\u8303\u56f4\u8bf7\u6c42<br \/>\n    &#8211; \u4e3a\u591a\u8303\u56f4\u8bf7\u6c42\u751f\u6210\u6b63\u786e\u7684multipart\/byteranges\u54cd\u5e94<\/p>\n<p>  # \u6027\u80fd\u4f18\u5316<br \/>\n  performance_optimization:<br \/>\n    &#8211; \u4e3a\u5c0f\u8303\u56f4\u8bf7\u6c42\u5b9e\u73b0\u7f13\u5b58<br \/>\n    &#8211; \u9884\u52a0\u8f7d\u5e38\u7528\u8303\u56f4&#xff08;\u5982\u6587\u4ef6\u5f00\u5934&#xff09;<br \/>\n    &#8211; \u4f7f\u7528\u5408\u9002\u7684\u5757\u5927\u5c0f&#xff08;\u901a\u5e3864KB-1MB&#xff09;<br \/>\n    &#8211; \u652f\u6301\u5e76\u884c\u8303\u56f4\u8bf7\u6c42<br \/>\n    &#8211; \u5b9e\u73b0\u6761\u4ef6\u8bf7\u6c42&#xff08;If-Range, If-Match\u7b49&#xff09;<\/p>\n<p>  # \u5b89\u5168\u8003\u8651<br \/>\n  security:<br \/>\n    &#8211; \u9a8c\u8bc1\u8303\u56f4\u8bf7\u6c42\u7684\u5408\u6cd5\u6027<br \/>\n    &#8211; \u9650\u5236\u6700\u5927\u8303\u56f4\u5927\u5c0f\u548c\u6570\u91cf<br \/>\n    &#8211; \u9632\u6b62\u8d44\u6e90\u8017\u5c3d\u653b\u51fb<br \/>\n    &#8211; \u8bb0\u5f55\u5f02\u5e38\u8303\u56f4\u8bf7\u6c42\u6a21\u5f0f<br \/>\n    &#8211; \u5b9e\u65bd\u901f\u7387\u9650\u5236<\/p>\n<p>  # \u5ba2\u6237\u7aef\u5904\u7406<br \/>\n  client_handling:<br \/>\n    &#8211; \u68c0\u67e5Accept-Ranges\u5934\u90e8\u4ee5\u4e86\u89e3\u670d\u52a1\u5668\u652f\u6301<br \/>\n    &#8211; \u6b63\u786e\u5904\u7406206\u548c416\u72b6\u6001\u7801<br \/>\n    &#8211; \u5b9e\u73b0\u65ad\u70b9\u7eed\u4f20\u529f\u80fd<br \/>\n    &#8211; \u5bf9\u5927\u578b\u6587\u4ef6\u4f7f\u7528\u5e76\u884c\u4e0b\u8f7d<br \/>\n    &#8211; \u5904\u7406\u7f51\u7edc\u9519\u8bef\u548c\u91cd\u8bd5<\/p>\n<p>  # \u7f13\u5b58\u7b56\u7565<br \/>\n  caching:<br \/>\n    &#8211; 206\u54cd\u5e94\u901a\u5e38\u53ef\u7f13\u5b58<br \/>\n    &#8211; \u8bbe\u7f6e\u9002\u5f53\u7684Cache-Control\u5934\u90e8<br \/>\n    &#8211; \u5bf9\u4e8e\u89c6\u9891\u6d41\u4f7f\u7528public\u7f13\u5b58<br \/>\n    &#8211; \u8003\u8651\u8303\u56f4\u7279\u5b9a\u7684\u7f13\u5b58\u7b56\u7565<\/p>\n<p>  # \u7279\u6b8a\u60c5\u51b5\u5904\u7406<br \/>\n  special_cases:<br \/>\n    &#8211; \u5904\u7406\u540e\u7f00\u8303\u56f4&#xff08;\u5982bytes&#061;-100&#xff09;<br \/>\n    &#8211; \u5904\u7406\u5f00\u533a\u95f4\u8303\u56f4&#xff08;\u5982bytes&#061;100-&#xff09;<br \/>\n    &#8211; \u5904\u7406\u91cd\u53e0\u8303\u56f4\u7684\u8bf7\u6c42<br \/>\n    &#8211; \u5904\u7406\u8d85\u51fa\u6587\u4ef6\u5927\u5c0f\u7684\u8303\u56f4<\/p>\n<p>  # \u76d1\u63a7\u548c\u65e5\u5fd7<br \/>\n  monitoring:<br \/>\n    &#8211; \u8bb0\u5f55\u8303\u56f4\u8bf7\u6c42\u7edf\u8ba1<br \/>\n    &#8211; \u76d1\u63a7\u8303\u56f4\u8bf7\u6c42\u7684\u6210\u529f\u7387<br \/>\n    &#8211; \u8ddf\u8e2a\u5b57\u8282\u670d\u52a1\u91cf<br \/>\n    &#8211; \u68c0\u6d4b\u5f02\u5e38\u8bf7\u6c42\u6a21\u5f0f<\/p>\n<p>  # \u517c\u5bb9\u6027\u8003\u8651<br \/>\n  compatibility:<br \/>\n    &#8211; \u652f\u6301HTTP\/1.1 Range\u89c4\u8303<br \/>\n    &#8211; \u8003\u8651\u4e0d\u652f\u6301\u8303\u56f4\u8bf7\u6c42\u7684\u5ba2\u6237\u7aef<br \/>\n    &#8211; \u63d0\u4f9b\u56de\u9000\u673a\u5236<br \/>\n    &#8211; \u6d4b\u8bd5\u5404\u79cdRange\u683c\u5f0f<\/p>\n<p>\u901a\u8fc7\u672c\u7ae0\u7684\u8be6\u7ec6\u89e3\u6790&#xff0c;\u6211\u4eec\u6df1\u5165\u4e86\u89e3\u4e86206 Partial Content\u72b6\u6001\u7801\u7684\u5b9e\u73b0\u7ec6\u8282\u3001\u4f18\u5316\u7b56\u7565\u548c\u6700\u4f73\u5b9e\u8df5\u3002206\u72b6\u6001\u7801\u5728\u73b0\u4ee3Web\u5e94\u7528\u4e2d\u975e\u5e38\u91cd\u8981&#xff0c;\u7279\u522b\u662f\u5728\u5927\u6587\u4ef6\u4f20\u8f93\u3001\u6d41\u5a92\u4f53\u548c\u65ad\u70b9\u7eed\u4f20\u7b49\u573a\u666f\u4e2d\u3002<\/p>\n<p>\u6b63\u786e\u5b9e\u73b0206\u54cd\u5e94\u53ef\u4ee5\u663e\u8457\u63d0\u9ad8\u6570\u636e\u4f20\u8f93\u6548\u7387&#xff0c;\u6539\u5584\u7528\u6237\u4f53\u9a8c&#xff0c;\u540c\u65f6\u51cf\u5c11\u670d\u52a1\u5668\u8d1f\u8f7d\u3002<\/p>\n<h2>\u7b2c9\u7ae0&#xff1a;\u5176\u4ed62xx\u72b6\u6001\u7801&#xff08;203\u3001205\u3001207\u3001208\u3001226&#xff09;<\/h2>\n<h3>9.1 203 Non-Authoritative Information<\/h3>\n<h4>9.1.1 \u8bed\u4e49\u6df1\u5ea6\u89e3\u6790<\/h4>\n<p>\u6839\u636eRFC 7231\u7b2c6.3.4\u8282&#xff0c;203 Non-Authoritative Information\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;<\/p>\n<p>203 (Non-Authoritative Information)\u72b6\u6001\u7801\u8868\u793a\u8bf7\u6c42\u5df2\u6210\u529f&#xff0c;\u4f46\u5305\u542b\u7684\u5b9e\u4f53\u5934\u90e8&#xff08;entity-header&#xff09;\u4fe1\u606f\u5e76\u975e\u6765\u81ea\u539f\u59cb\u670d\u52a1\u5668&#xff0c;\u800c\u662f\u6765\u81ea\u672c\u5730\u6216\u7b2c\u4e09\u65b9\u526f\u672c\u3002\u8fd9\u53ef\u80fd\u5305\u62ec\u5bf9\u8868\u793a\u5f62\u5f0f\u7684\u6ce8\u89e3&#xff08;\u5982\u6dfb\u52a0&#034;via&#034;\u5934\u90e8&#xff09;&#xff0c;\u8fd9\u4e9b\u6ce8\u89e3\u7531\u4e2d\u95f4\u8282\u70b9&#xff08;\u5982\u4ee3\u7406\u670d\u52a1\u5668&#xff09;\u5e94\u7528\u3002<\/p>\n<h5>\u6838\u5fc3\u8bed\u4e49\u7279\u5f81<\/h5>\n<li>\n<p>\u4fe1\u606f\u6e90\u975e\u6743\u5a01&#xff1a;\u54cd\u5e94\u5934\u90e8\u4fe1\u606f\u4e0d\u662f\u6765\u81ea\u539f\u59cb\u670d\u52a1\u5668<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u4f53\u4e3b\u4f53\u53ef\u9760&#xff1a;\u54cd\u5e94\u4f53\u5185\u5bb9\u4ecd\u88ab\u8ba4\u4e3a\u6709\u6548<\/p>\n<\/li>\n<li>\n<p>\u4e2d\u95f4\u8282\u70b9\u5904\u7406&#xff1a;\u7531\u4ee3\u7406\u3001\u7f51\u5173\u6216\u7f13\u5b58\u670d\u52a1\u5668\u751f\u6210<\/p>\n<\/li>\n<li>\n<p>\u53ef\u9009\u4f7f\u7528&#xff1a;\u5728HTTP\/1.1\u4e2d\u4e0d\u5e38\u7528&#xff0c;\u901a\u5e38\u7531\u4ee3\u7406\u5904\u7406<\/p>\n<\/li>\n<h4>9.1.2 \u9002\u7528\u573a\u666f\u4e0e\u5b9e\u73b0<\/h4>\n<p>python<\/p>\n<p># 203 Non-Authoritative Information\u670d\u52a1\u5668\u5b9e\u73b0<br \/>\nfrom flask import Flask, jsonify, Response<br \/>\nfrom datetime import datetime<br \/>\nimport hashlib<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p>class NonAuthoritativeResponse:<br \/>\n    &#034;&#034;&#034;203\u54cd\u5e94\u751f\u6210\u5668&#034;&#034;&#034;<\/p>\n<p>    &#064;staticmethod<br \/>\n    def create_response(data, original_headers&#061;None, transformed_by&#061;None):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u521b\u5efa203\u54cd\u5e94<\/p>\n<p>        Args:<br \/>\n            data: \u54cd\u5e94\u6570\u636e<br \/>\n            original_headers: \u539f\u59cb\u5934\u90e8&#xff08;\u5982\u679c\u6709&#xff09;<br \/>\n            transformed_by: \u8f6c\u6362\u670d\u52a1\u7684\u6807\u8bc6<\/p>\n<p>        Returns:<br \/>\n            Flask Response\u5bf9\u8c61<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        # \u6784\u5efa\u54cd\u5e94<br \/>\n        response &#061; jsonify(data)<br \/>\n        response.status_code &#061; 203<\/p>\n<p>        # \u8bbe\u7f6e\u6807\u51c6\u5934\u90e8<br \/>\n        response.headers[&#039;Date&#039;] &#061; datetime.utcnow().strftime(&#039;%a, %d %b %Y %H:%M:%S GMT&#039;)<br \/>\n        response.headers[&#039;Server&#039;] &#061; &#039;ProxyServer\/1.0&#039;<\/p>\n<p>        # \u6dfb\u52a0Warning\u5934\u90e8&#xff08;RFC 7234\u5b9a\u4e49&#xff09;<br \/>\n        response.headers[&#039;Warning&#039;] &#061; &#039;214 Transformation applied&#039;<\/p>\n<p>        # \u6dfb\u52a0Via\u5934\u90e8&#xff08;\u5982\u679c\u6709\u4e2d\u95f4\u4ee3\u7406&#xff09;<br \/>\n        if transformed_by:<br \/>\n            response.headers[&#039;Via&#039;] &#061; f&#039;1.1 {transformed_by}&#039;<\/p>\n<p>        # \u6dfb\u52a0X-\u5934\u90e8\u8868\u793a\u975e\u6743\u5a01\u4fe1\u606f<br \/>\n        response.headers[&#039;X-Information-Source&#039;] &#061; &#039;Non-authoritative&#039;<br \/>\n        response.headers[&#039;X-Transformed-By&#039;] &#061; transformed_by or &#039;proxy-server&#039;<\/p>\n<p>        # \u4fdd\u7559\u539f\u59cb\u5934\u90e8\u4fe1\u606f&#xff08;\u5982\u679c\u63d0\u4f9b&#xff09;<br \/>\n        if original_headers:<br \/>\n            response.headers[&#039;X-Original-Headers&#039;] &#061; str(original_headers)<\/p>\n<p>        # \u6dfb\u52a0\u7f13\u5b58\u63a7\u5236<br \/>\n        response.headers[&#039;Cache-Control&#039;] &#061; &#039;public, max-age&#061;300, s-maxage&#061;600&#039;<\/p>\n<p>        # \u6dfb\u52a0\u5b9e\u4f53\u6807\u7b7e<br \/>\n        etag &#061; NonAuthoritativeResponse._generate_etag(data)<br \/>\n        response.headers[&#039;ETag&#039;] &#061; etag<\/p>\n<p>        return response<\/p>\n<p>    &#064;staticmethod<br \/>\n    def _generate_etag(data):<br \/>\n        &#034;&#034;&#034;\u751f\u6210ETag&#034;&#034;&#034;<br \/>\n        content &#061; str(data).encode(&#039;utf-8&#039;)<br \/>\n        return f&#039;&#034;{hashlib.md5(content).hexdigest()}&#034;&#039;<\/p>\n<p># \u4ee3\u7406\u670d\u52a1\u5668\u7aef\u70b9<br \/>\n&#064;app.route(&#039;\/proxy\/api\/&lt;path:endpoint&gt;&#039;)<br \/>\ndef proxy_endpoint(endpoint):<br \/>\n    &#034;&#034;&#034;<br \/>\n    \u4ee3\u7406\u7aef\u70b9\u793a\u4f8b&#xff1a;\u4ece\u4e0a\u6e38\u83b7\u53d6\u6570\u636e\u5e76\u6dfb\u52a0\u975e\u6743\u5a01\u4fe1\u606f<\/p>\n<p>    \u6a21\u62df\u573a\u666f&#xff1a;<br \/>\n    1. \u4ece\u539f\u59cb\u670d\u52a1\u5668\u83b7\u53d6\u6570\u636e<br \/>\n    2. \u4fee\u6539\u6216\u6dfb\u52a0\u5934\u90e8\u4fe1\u606f<br \/>\n    3. \u8fd4\u56de203\u54cd\u5e94<br \/>\n    &#034;&#034;&#034;<\/p>\n<p>    # \u6a21\u62df\u4ece\u4e0a\u6e38\u670d\u52a1\u5668\u83b7\u53d6\u6570\u636e<br \/>\n    upstream_data &#061; {<br \/>\n        &#039;id&#039;: 123,<br \/>\n        &#039;name&#039;: &#039;Example Resource&#039;,<br \/>\n        &#039;timestamp&#039;: datetime.utcnow().isoformat(),<br \/>\n        &#039;source&#039;: &#039;upstream-server&#039;<br \/>\n    }<\/p>\n<p>    # \u6a21\u62df\u6dfb\u52a0\u7684\u8f6c\u6362\u6216\u6ce8\u89e3<br \/>\n    upstream_data[&#039;annotations&#039;] &#061; {<br \/>\n        &#039;cached&#039;: True,<br \/>\n        &#039;cache_ttl&#039;: 300,<br \/>\n        &#039;proxy_version&#039;: &#039;1.2.3&#039;,<br \/>\n        &#039;processed_at&#039;: datetime.utcnow().isoformat()<br \/>\n    }<\/p>\n<p>    # \u6a21\u62df\u539f\u59cb\u5934\u90e8<br \/>\n    original_headers &#061; {<br \/>\n        &#039;Server&#039;: &#039;UpstreamServer\/1.0&#039;,<br \/>\n        &#039;X-Upstream-ID&#039;: &#039;server-abc123&#039;<br \/>\n    }<\/p>\n<p>    # \u521b\u5efa203\u54cd\u5e94<br \/>\n    return NonAuthoritativeResponse.create_response(<br \/>\n        data&#061;upstream_data,<br \/>\n        original_headers&#061;original_headers,<br \/>\n        transformed_by&#061;&#039;edge-proxy.example.com&#039;<br \/>\n    )<\/p>\n<p># \u5185\u5bb9\u8f6c\u6362\u670d\u52a1\u793a\u4f8b<br \/>\nclass ContentTransformer:<br \/>\n    &#034;&#034;&#034;\u5185\u5bb9\u8f6c\u6362\u670d\u52a1&#xff0c;\u8fd4\u56de203\u54cd\u5e94&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.transformations &#061; {<br \/>\n            &#039;compress&#039;: self._compress_content,<br \/>\n            &#039;filter&#039;: self._filter_content,<br \/>\n            &#039;annotate&#039;: self._annotate_content,<br \/>\n            &#039;translate&#039;: self._translate_content<br \/>\n        }<\/p>\n<p>    def transform_response(self, original_response, transformation_type, **kwargs):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u8f6c\u6362\u54cd\u5e94\u5e76\u8fd4\u56de203<\/p>\n<p>        Args:<br \/>\n            original_response: \u539f\u59cb\u54cd\u5e94\u6570\u636e<br \/>\n            transformation_type: \u8f6c\u6362\u7c7b\u578b<br \/>\n            **kwargs: \u8f6c\u6362\u53c2\u6570<\/p>\n<p>        Returns:<br \/>\n            203\u54cd\u5e94<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        # \u5e94\u7528\u8f6c\u6362<br \/>\n        transformer &#061; self.transformations.get(transformation_type)<br \/>\n        if not transformer:<br \/>\n            return original_response<\/p>\n<p>        transformed_data &#061; transformer(original_response, **kwargs)<\/p>\n<p>        # \u521b\u5efa203\u54cd\u5e94<br \/>\n        response_data &#061; {<br \/>\n            &#039;data&#039;: transformed_data,<br \/>\n            &#039;metadata&#039;: {<br \/>\n                &#039;original_size&#039;: len(str(original_response)),<br \/>\n                &#039;transformed_size&#039;: len(str(transformed_data)),<br \/>\n                &#039;transformation&#039;: transformation_type,<br \/>\n                &#039;applied_at&#039;: datetime.utcnow().isoformat(),<br \/>\n                &#039;reduction_percentage&#039;: self._calculate_reduction(<br \/>\n                    original_response, transformed_data<br \/>\n                )<br \/>\n            },<br \/>\n            &#039;original_data&#039;: original_response if kwargs.get(&#039;include_original&#039;) else None<br \/>\n        }<\/p>\n<p>        return NonAuthoritativeResponse.create_response(<br \/>\n            data&#061;response_data,<br \/>\n            transformed_by&#061;f&#039;content-transformer-{transformation_type}&#039;<br \/>\n        )<\/p>\n<p>    def _compress_content(self, data, **kwargs):<br \/>\n        &#034;&#034;&#034;\u538b\u7f29\u5185\u5bb9&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n        # \u5b9e\u9645\u4e2d\u4f1a\u4f7f\u7528gzip\u6216\u5176\u4ed6\u538b\u7f29<br \/>\n        import json<br \/>\n        return json.dumps(data, separators&#061;(&#039;,&#039;, &#039;:&#039;))  # \u6700\u5c0f\u5316JSON<\/p>\n<p>    def _filter_content(self, data, fields&#061;None, **kwargs):<br \/>\n        &#034;&#034;&#034;\u8fc7\u6ee4\u5185\u5bb9\u5b57\u6bb5&#034;&#034;&#034;<br \/>\n        if not fields or not isinstance(data, dict):<br \/>\n            return data<\/p>\n<p>        filtered &#061; {}<br \/>\n        for field in fields:<br \/>\n            if field in data:<br \/>\n                filtered[field] &#061; data[field]<\/p>\n<p>        return filtered<\/p>\n<p>    def _annotate_content(self, data, **kwargs):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u6ce8\u91ca&#034;&#034;&#034;<br \/>\n        annotated &#061; dict(data)<br \/>\n        annotated[&#039;_annotations&#039;] &#061; {<br \/>\n            &#039;processed&#039;: True,<br \/>\n            &#039;processor&#039;: &#039;annotation-service&#039;,<br \/>\n            &#039;version&#039;: &#039;1.0&#039;,<br \/>\n            &#039;timestamp&#039;: datetime.utcnow().isoformat()<br \/>\n        }<br \/>\n        return annotated<\/p>\n<p>    def _translate_content(self, data, target_lang&#061;&#039;en&#039;, **kwargs):<br \/>\n        &#034;&#034;&#034;\u7ffb\u8bd1\u5185\u5bb9&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n        # \u5b9e\u9645\u4e2d\u4f1a\u8c03\u7528\u7ffb\u8bd1\u670d\u52a1<br \/>\n        translated &#061; dict(data)<br \/>\n        if &#039;name&#039; in translated:<br \/>\n            translated[&#039;name&#039;] &#061; f&#034;[{target_lang}] {translated[&#039;name&#039;]}&#034;<br \/>\n        return translated<\/p>\n<p>    def _calculate_reduction(self, original, transformed):<br \/>\n        &#034;&#034;&#034;\u8ba1\u7b97\u5927\u5c0f\u51cf\u5c11\u767e\u5206\u6bd4&#034;&#034;&#034;<br \/>\n        orig_size &#061; len(str(original))<br \/>\n        trans_size &#061; len(str(transformed))<\/p>\n<p>        if orig_size &#061;&#061; 0:<br \/>\n            return 0<\/p>\n<p>        reduction &#061; (orig_size &#8211; trans_size) \/ orig_size * 100<br \/>\n        return round(reduction, 2)<\/p>\n<p># \u4f7f\u7528\u793a\u4f8b<br \/>\ntransformer &#061; ContentTransformer()<\/p>\n<p>&#064;app.route(&#039;\/api\/transform\/&lt;transformation&gt;&#039;)<br \/>\ndef transform_content(transformation):<br \/>\n    &#034;&#034;&#034;\u5185\u5bb9\u8f6c\u6362\u7aef\u70b9&#034;&#034;&#034;<\/p>\n<p>    # \u539f\u59cb\u6570\u636e<br \/>\n    original_data &#061; {<br \/>\n        &#039;id&#039;: 456,<br \/>\n        &#039;name&#039;: &#039;\u793a\u4f8b\u8d44\u6e90&#039;,<br \/>\n        &#039;description&#039;: &#039;\u8fd9\u662f\u4e00\u4e2a\u9700\u8981\u8f6c\u6362\u7684\u793a\u4f8b\u8d44\u6e90&#039;,<br \/>\n        &#039;content&#039;: &#039;\u8fd9\u91cc\u662f\u8be6\u7ec6\u7684\u5185\u5bb9\u4fe1\u606f&#xff0c;\u53ef\u80fd\u4f1a\u5f88\u957f&#8230;&#039;,<br \/>\n        &#039;metadata&#039;: {<br \/>\n            &#039;created&#039;: &#039;2023-01-01&#039;,<br \/>\n            &#039;updated&#039;: &#039;2023-01-23&#039;,<br \/>\n            &#039;tags&#039;: [&#039;example&#039;, &#039;test&#039;, &#039;data&#039;]<br \/>\n        }<br \/>\n    }<\/p>\n<p>    # \u5e94\u7528\u8f6c\u6362<br \/>\n    transformed_response &#061; transformer.transform_response(<br \/>\n        original_data,<br \/>\n        transformation,<br \/>\n        include_original&#061;False<br \/>\n    )<\/p>\n<p>    return transformed_response<\/p>\n<p># \u7f13\u5b58\u4ee3\u7406\u5b9e\u73b0<br \/>\nclass CachingProxy:<br \/>\n    &#034;&#034;&#034;\u7f13\u5b58\u4ee3\u7406\u8fd4\u56de203\u54cd\u5e94&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, cache_duration&#061;300):<br \/>\n        self.cache &#061; {}<br \/>\n        self.cache_duration &#061; cache_duration<\/p>\n<p>    def get_cached_response(self, request_url, force_refresh&#061;False):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u83b7\u53d6\u7f13\u5b58\u54cd\u5e94<\/p>\n<p>        Args:<br \/>\n            request_url: \u8bf7\u6c42URL<br \/>\n            force_refresh: \u662f\u5426\u5f3a\u5236\u5237\u65b0\u7f13\u5b58<\/p>\n<p>        Returns:<br \/>\n            \u7f13\u5b58\u54cd\u5e94&#xff08;203&#xff09;\u6216\u65b0\u54cd\u5e94<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        cache_key &#061; hashlib.md5(request_url.encode()).hexdigest()<br \/>\n        now &#061; datetime.utcnow()<\/p>\n<p>        # \u68c0\u67e5\u7f13\u5b58<br \/>\n        if not force_refresh and cache_key in self.cache:<br \/>\n            cache_entry &#061; self.cache[cache_key]<\/p>\n<p>            # \u68c0\u67e5\u662f\u5426\u8fc7\u671f<br \/>\n            age &#061; (now &#8211; cache_entry[&#039;cached_at&#039;]).total_seconds()<br \/>\n            if age &lt; self.cache_duration:<br \/>\n                # \u8fd4\u56de\u7f13\u5b58\u54cd\u5e94&#xff08;203&#xff09;<br \/>\n                return self._create_cached_response(<br \/>\n                    cache_entry[&#039;data&#039;],<br \/>\n                    cache_entry[&#039;original_headers&#039;],<br \/>\n                    age<br \/>\n                )<\/p>\n<p>        # \u83b7\u53d6\u65b0\u6570\u636e<br \/>\n        new_data &#061; self._fetch_from_upstream(request_url)<br \/>\n        original_headers &#061; self._get_upstream_headers(request_url)<\/p>\n<p>        # \u66f4\u65b0\u7f13\u5b58<br \/>\n        self.cache[cache_key] &#061; {<br \/>\n            &#039;data&#039;: new_data,<br \/>\n            &#039;original_headers&#039;: original_headers,<br \/>\n            &#039;cached_at&#039;: now<br \/>\n        }<\/p>\n<p>        # \u5982\u679c\u662f\u5f3a\u5236\u5237\u65b0\u6216\u7b2c\u4e00\u6b21\u83b7\u53d6&#xff0c;\u8fd4\u56de200<br \/>\n        return jsonify(new_data), 200<\/p>\n<p>    def _fetch_from_upstream(self, url):<br \/>\n        &#034;&#034;&#034;\u4ece\u4e0a\u6e38\u83b7\u53d6\u6570\u636e&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n        # \u5b9e\u9645\u4e2d\u4f1a\u8fdb\u884cHTTP\u8bf7\u6c42<br \/>\n        return {<br \/>\n            &#039;url&#039;: url,<br \/>\n            &#039;data&#039;: &#039;Fresh data from upstream&#039;,<br \/>\n            &#039;timestamp&#039;: datetime.utcnow().isoformat(),<br \/>\n            &#039;source&#039;: &#039;upstream-server&#039;<br \/>\n        }<\/p>\n<p>    def _get_upstream_headers(self, url):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u4e0a\u6e38\u5934\u90e8&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n        return {<br \/>\n            &#039;Server&#039;: &#039;Upstream\/1.0&#039;,<br \/>\n            &#039;X-Request-ID&#039;: &#039;req_123456&#039;<br \/>\n        }<\/p>\n<p>    def _create_cached_response(self, data, original_headers, cache_age):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u7f13\u5b58\u54cd\u5e94&#xff08;203&#xff09;&#034;&#034;&#034;<\/p>\n<p>        response_data &#061; {<br \/>\n            **data,<br \/>\n            &#039;_cache_info&#039;: {<br \/>\n                &#039;cached&#039;: True,<br \/>\n                &#039;cache_age_seconds&#039;: cache_age,<br \/>\n                &#039;max_age&#039;: self.cache_duration,<br \/>\n                &#039;stale&#039;: cache_age &gt; self.cache_duration * 0.8  # 80%\u8fc7\u671f<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return NonAuthoritativeResponse.create_response(<br \/>\n            data&#061;response_data,<br \/>\n            original_headers&#061;original_headers,<br \/>\n            transformed_by&#061;&#039;caching-proxy&#039;<br \/>\n        )<\/p>\n<p># \u7f13\u5b58\u4ee3\u7406\u7aef\u70b9<br \/>\ncaching_proxy &#061; CachingProxy(cache_duration&#061;300)<\/p>\n<p>&#064;app.route(&#039;\/cached\/&lt;path:url&gt;&#039;)<br \/>\ndef cached_proxy(url):<br \/>\n    &#034;&#034;&#034;\u7f13\u5b58\u4ee3\u7406\u7aef\u70b9&#034;&#034;&#034;<br \/>\n    full_url &#061; f&#039;https:\/\/example.com\/{url}&#039;<br \/>\n    force_refresh &#061; request.args.get(&#039;refresh&#039;, &#039;false&#039;).lower() &#061;&#061; &#039;true&#039;<\/p>\n<p>    return caching_proxy.get_cached_response(full_url, force_refresh)<\/p>\n<p>if __name__ &#061;&#061; &#039;__main__&#039;:<br \/>\n    app.run(debug&#061;True, port&#061;5001)<\/p>\n<h4 style=\"background-color:transparent\">9.1.3 \u5ba2\u6237\u7aef\u5904\u7406<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5ba2\u6237\u7aef\u5904\u7406203\u54cd\u5e94\u7684\u793a\u4f8b<br \/>\nclass NonAuthoritativeResponseHandler {<br \/>\n    \/**<br \/>\n     * \u5904\u7406203 Non-Authoritative Information\u54cd\u5e94<br \/>\n     *\/<\/p>\n<p>    constructor() {<br \/>\n        this.warningHandlers &#061; {<br \/>\n            &#039;110&#039;: this.handleResponseIsStale,<br \/>\n            &#039;111&#039;: this.handleRevalidationFailed,<br \/>\n            &#039;112&#039;: this.handleDisconnectedOperation,<br \/>\n            &#039;113&#039;: this.handleHeuristicExpiration,<br \/>\n            &#039;199&#039;: this.handleMiscellaneousWarning,<br \/>\n            &#039;214&#039;: this.handleTransformationApplied,<br \/>\n            &#039;299&#039;: this.handleMiscellaneousPersistentWarning<br \/>\n        };<br \/>\n    }<\/p>\n<p>    async handle203Response(response) {<br \/>\n        console.log(&#039;Received 203 Non-Authoritative Information response&#039;);<\/p>\n<p>        \/\/ \u89e3\u6790\u54cd\u5e94<br \/>\n        const data &#061; await response.json();<\/p>\n<p>        \/\/ \u68c0\u67e5Warning\u5934\u90e8<br \/>\n        const warningHeader &#061; response.headers.get(&#039;Warning&#039;);<br \/>\n        if (warningHeader) {<br \/>\n            await this.handleWarnings(warningHeader, response);<br \/>\n        }<\/p>\n<p>        \/\/ \u68c0\u67e5Via\u5934\u90e8<br \/>\n        const viaHeader &#061; response.headers.get(&#039;Via&#039;);<br \/>\n        if (viaHeader) {<br \/>\n            console.log(&#096;Response came via: ${viaHeader}&#096;);<br \/>\n        }<\/p>\n<p>        \/\/ \u68c0\u67e5X-\u5934\u90e8<br \/>\n        const infoSource &#061; response.headers.get(&#039;X-Information-Source&#039;);<br \/>\n        if (infoSource &#061;&#061;&#061; &#039;Non-authoritative&#039;) {<br \/>\n            console.warn(&#039;This response contains non-authoritative information&#039;);<\/p>\n<p>            \/\/ \u6839\u636e\u4e1a\u52a1\u903b\u8f91\u51b3\u5b9a\u662f\u5426\u4f7f\u7528<br \/>\n            if (this.requiresAuthoritativeData()) {<br \/>\n                throw new Error(&#039;Authoritative data required, but received non-authoritative&#039;);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/\/ \u68c0\u67e5\u7f13\u5b58\u4fe1\u606f<br \/>\n        if (data._cache_info) {<br \/>\n            await this.handleCacheInfo(data._cache_info);<br \/>\n        }<\/p>\n<p>        return {<br \/>\n            data: data,<br \/>\n            isAuthoritative: false,<br \/>\n            warnings: this.parseWarnings(warningHeader),<br \/>\n            via: viaHeader,<br \/>\n            metadata: {<br \/>\n                etag: response.headers.get(&#039;ETag&#039;),<br \/>\n                lastModified: response.headers.get(&#039;Last-Modified&#039;),<br \/>\n                transformedBy: response.headers.get(&#039;X-Transformed-By&#039;)<br \/>\n            }<br \/>\n        };<br \/>\n    }<\/p>\n<p>    parseWarnings(warningHeader) {<br \/>\n        if (!warningHeader) return [];<\/p>\n<p>        \/\/ Warning: 214 Transformation applied, 315 Response is stale<br \/>\n        const warnings &#061; [];<br \/>\n        const warningParts &#061; warningHeader.split(&#039;,&#039;);<\/p>\n<p>        for (const part of warningParts) {<br \/>\n            const match &#061; part.trim().match(\/^(\\\\d{3})\\\\s&#043;(.&#043;)$\/);<br \/>\n            if (match) {<br \/>\n                const [_, code, text] &#061; match;<br \/>\n                warnings.push({<br \/>\n                    code: parseInt(code, 10),<br \/>\n                    text: text.trim(),<br \/>\n                    agent: this.extractAgent(part)<br \/>\n                });<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return warnings;<br \/>\n    }<\/p>\n<p>    extractAgent(warningText) {<br \/>\n        \/\/ \u63d0\u53d6\u4ee3\u7406\u4fe1\u606f&#xff0c;\u4f8b\u5982&#xff1a;214 Transformation applied &#034;proxy.example.com&#034;<br \/>\n        const match &#061; warningText.match(\/&#034;([^&#034;]&#043;)&#034;\/);<br \/>\n        return match ? match[1] : null;<br \/>\n    }<\/p>\n<p>    async handleWarnings(warningHeader, response) {<br \/>\n        const warnings &#061; this.parseWarnings(warningHeader);<\/p>\n<p>        for (const warning of warnings) {<br \/>\n            const handler &#061; this.warningHandlers[warning.code];<br \/>\n            if (handler) {<br \/>\n                await handler.call(this, warning, response);<br \/>\n            } else {<br \/>\n                console.warn(&#096;Unhandled warning ${warning.code}: ${warning.text}&#096;);<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    handleResponseIsStale(warning) {<br \/>\n        console.warn(&#039;Response is stale:&#039;, warning.text);<br \/>\n        \/\/ \u53ef\u4ee5\u8003\u8651\u91cd\u65b0\u9a8c\u8bc1<br \/>\n    }<\/p>\n<p>    handleRevalidationFailed(warning) {<br \/>\n        console.warn(&#039;Revalidation failed:&#039;, warning.text);<br \/>\n        \/\/ \u65e0\u6cd5\u91cd\u65b0\u9a8c\u8bc1&#xff0c;\u4f7f\u7528\u53ef\u80fd\u8fc7\u671f\u7684\u6570\u636e<br \/>\n    }<\/p>\n<p>    handleTransformationApplied(warning) {<br \/>\n        console.log(&#039;Transformation applied by:&#039;, warning.agent);<br \/>\n        \/\/ \u54cd\u5e94\u5df2\u88ab\u8f6c\u6362<br \/>\n    }<\/p>\n<p>    requiresAuthoritativeData() {<br \/>\n        \/\/ \u6839\u636e\u5e94\u7528\u9700\u6c42\u51b3\u5b9a\u662f\u5426\u9700\u8981\u6743\u5a01\u6570\u636e<br \/>\n        \/\/ \u4f8b\u5982&#xff1a;\u91d1\u878d\u4ea4\u6613\u9700\u8981\u6743\u5a01\u6570\u636e&#xff0c;\u800c\u65b0\u95fb\u6587\u7ae0\u53ef\u80fd\u4e0d\u9700\u8981<br \/>\n        return false; \/\/ \u9ed8\u8ba4\u4e0d\u9700\u8981<br \/>\n    }<\/p>\n<p>    async handleCacheInfo(cacheInfo) {<br \/>\n        console.log(&#039;Cache information:&#039;, cacheInfo);<\/p>\n<p>        if (cacheInfo.stale) {<br \/>\n            console.warn(&#039;Cache is stale, consider refreshing&#039;);<\/p>\n<p>            \/\/ \u5982\u679c\u6570\u636e\u8fc7\u4e8e\u9648\u65e7&#xff0c;\u53ef\u4ee5\u89e6\u53d1\u540e\u53f0\u5237\u65b0<br \/>\n            if (cacheInfo.cache_age_seconds &gt; 600) { \/\/ 10\u5206\u949f<br \/>\n                this.triggerBackgroundRefresh();<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    triggerBackgroundRefresh() {<br \/>\n        \/\/ \u5728\u540e\u53f0\u5237\u65b0\u6570\u636e<br \/>\n        console.log(&#039;Triggering background refresh&#8230;&#039;);<br \/>\n        \/\/ \u5b9e\u9645\u5b9e\u73b0\u4f1a\u53d1\u9001\u8bf7\u6c42\u5e76\u5728\u66f4\u65b0\u540e\u901a\u77e5UI<br \/>\n    }<\/p>\n<p>    \/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\n    async fetchWith203Handling(url, options &#061; {}) {<br \/>\n        try {<br \/>\n            const response &#061; await fetch(url, options);<\/p>\n<p>            if (response.status &#061;&#061;&#061; 203) {<br \/>\n                const handler &#061; new NonAuthoritativeResponseHandler();<br \/>\n                return await handler.handle203Response(response);<br \/>\n            } else if (response.ok) {<br \/>\n                \/\/ \u6b63\u5e38\u54cd\u5e94<br \/>\n                const data &#061; await response.json();<br \/>\n                return {<br \/>\n                    data: data,<br \/>\n                    isAuthoritative: true<br \/>\n                };<br \/>\n            } else {<br \/>\n                throw new Error(&#096;HTTP ${response.status}&#096;);<br \/>\n            }<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Fetch failed:&#039;, error);<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\ndocument.getElementById(&#039;load-data&#039;).addEventListener(&#039;click&#039;, async () &#061;&gt; {<br \/>\n    const handler &#061; new NonAuthoritativeResponseHandler();<\/p>\n<p>    try {<br \/>\n        const result &#061; await handler.fetchWith203Handling(&#039;\/proxy\/api\/data&#039;);<\/p>\n<p>        if (!result.isAuthoritative) {<br \/>\n            \/\/ \u663e\u793a\u975e\u6743\u5a01\u6570\u636e\u7684\u8b66\u544a<br \/>\n            document.getElementById(&#039;warning-banner&#039;).style.display &#061; &#039;block&#039;;<br \/>\n            document.getElementById(&#039;warning-banner&#039;).textContent &#061;<br \/>\n                &#039;\u663e\u793a\u7684\u6570\u636e\u53ef\u80fd\u4e0d\u662f\u6700\u65b0\u7684&#xff0c;\u6765\u81ea\u7f13\u5b58\u6216\u8f6c\u6362\u670d\u52a1&#039;;<br \/>\n        }<\/p>\n<p>        \/\/ \u4f7f\u7528\u6570\u636e<br \/>\n        displayData(result.data);<\/p>\n<p>    } catch (error) {<br \/>\n        console.error(&#039;Failed to load data:&#039;, error);<br \/>\n        alert(&#039;\u52a0\u8f7d\u6570\u636e\u5931\u8d25&#039;);<br \/>\n    }<br \/>\n});<\/p>\n<p>function displayData(data) {<br \/>\n    \/\/ \u663e\u793a\u6570\u636e\u7684\u903b\u8f91<br \/>\n    const container &#061; document.getElementById(&#039;data-container&#039;);<br \/>\n    container.innerHTML &#061; JSON.stringify(data, null, 2);<br \/>\n}<\/p>\n<h3>9.2 205 Reset Content<\/h3>\n<h4>9.2.1 \u8bed\u4e49\u6df1\u5ea6\u89e3\u6790<\/h4>\n<p>\u6839\u636eRFC 7231\u7b2c6.3.6\u8282&#xff0c;205 Reset Content\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;<\/p>\n<p>205 (Reset Content)\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u5df2\u6210\u529f\u5904\u7406\u8bf7\u6c42&#xff0c;\u5e76\u4e14\u7528\u6237\u4ee3\u7406\u5e94\u8be5\u91cd\u7f6e\u53d1\u9001\u6b64\u8bf7\u6c42\u7684\u6587\u6863\u89c6\u56fe\u3002\u8fd9\u4e2a\u54cd\u5e94\u4e3b\u8981\u662f\u4e3a\u4e86\u652f\u6301\u6570\u636e\u8f93\u5165&#xff08;\u5982\u8868\u5355&#xff09;\u7684\u8f93\u5165\u6e05\u7406&#xff0c;\u4ee5\u4fbf\u7528\u6237\u53ef\u4ee5\u8f7b\u677e\u5730\u5f00\u59cb\u53e6\u4e00\u6b21\u8f93\u5165\u3002<\/p>\n<h5>\u6838\u5fc3\u8bed\u4e49\u7279\u5f81<\/h5>\n<li>\n<p>\u89c6\u56fe\u91cd\u7f6e&#xff1a;\u5ba2\u6237\u7aef\u5e94\u91cd\u7f6e\u5bfc\u81f4\u8bf7\u6c42\u7684\u6587\u6863\u89c6\u56fe<\/p>\n<\/li>\n<li>\n<p>\u65e0\u5185\u5bb9\u8fd4\u56de&#xff1a;\u54cd\u5e94\u4e0d\u80fd\u5305\u542b\u5b9e\u4f53\u4e3b\u4f53<\/p>\n<\/li>\n<li>\n<p>\u7528\u6237\u754c\u9762\u5bfc\u5411&#xff1a;\u4e3b\u8981\u7528\u4e8e\u6539\u5584\u7528\u6237\u4f53\u9a8c<\/p>\n<\/li>\n<li>\n<p>\u8868\u5355\u5904\u7406&#xff1a;\u7279\u522b\u9002\u5408\u8868\u5355\u63d0\u4ea4\u540e\u7684\u6e05\u7406<\/p>\n<\/li>\n<h4>9.2.2 \u9002\u7528\u573a\u666f\u4e0e\u5b9e\u73b0<\/h4>\n<p>python<\/p>\n<p># 205 Reset Content\u670d\u52a1\u5668\u5b9e\u73b0<br \/>\nfrom flask import Flask, request, jsonify, Response, render_template_string<br \/>\nfrom flask_wtf import FlaskForm<br \/>\nfrom wtforms import StringField, TextAreaField, SubmitField<br \/>\nfrom wtforms.validators import DataRequired<br \/>\nimport datetime<\/p>\n<p>app &#061; Flask(__name__)<br \/>\napp.config[&#039;SECRET_KEY&#039;] &#061; &#039;your-secret-key-here&#039;<\/p>\n<p># \u8868\u5355\u5b9a\u4e49<br \/>\nclass ContactForm(FlaskForm):<br \/>\n    name &#061; StringField(&#039;Name&#039;, validators&#061;[DataRequired()])<br \/>\n    email &#061; StringField(&#039;Email&#039;, validators&#061;[DataRequired()])<br \/>\n    message &#061; TextAreaField(&#039;Message&#039;, validators&#061;[DataRequired()])<br \/>\n    submit &#061; SubmitField(&#039;Submit&#039;)<\/p>\n<p># HTML\u6a21\u677f<br \/>\nFORM_TEMPLATE &#061; &#039;&#039;&#039;<br \/>\n&lt;!DOCTYPE html&gt;<br \/>\n&lt;html&gt;<br \/>\n&lt;head&gt;<br \/>\n    &lt;title&gt;Contact Form&lt;\/title&gt;<br \/>\n    &lt;style&gt;<br \/>\n        body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }<br \/>\n        .form-group { margin-bottom: 15px; }<br \/>\n        label { display: block; margin-bottom: 5px; font-weight: bold; }<br \/>\n        input, textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }<br \/>\n        .error { color: #dc3545; font-size: 0.9em; margin-top: 5px; }<br \/>\n        .success { background: #d4edda; color: #155724; padding: 10px; border-radius: 4px; margin-bottom: 20px; }<br \/>\n        .btn { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }<br \/>\n        .btn:hover { background: #0056b3; }<br \/>\n    &lt;\/style&gt;<br \/>\n&lt;\/head&gt;<br \/>\n&lt;body&gt;<br \/>\n    &lt;h1&gt;Contact Us&lt;\/h1&gt;<\/p>\n<p>    {% if success %}<br \/>\n    &lt;div class&#061;&#034;success&#034; id&#061;&#034;success-message&#034;&gt;<br \/>\n        Thank you for your message! The form has been reset for your next message.<br \/>\n    &lt;\/div&gt;<br \/>\n    {% endif %}<\/p>\n<p>    &lt;form method&#061;&#034;POST&#034; id&#061;&#034;contact-form&#034;&gt;<br \/>\n        {{ form.hidden_tag() }}<\/p>\n<p>        &lt;div class&#061;&#034;form-group&#034;&gt;<br \/>\n            {{ form.name.label }}<br \/>\n            {{ form.name(size&#061;32) }}<br \/>\n            {% for error in form.name.errors %}<br \/>\n            &lt;div class&#061;&#034;error&#034;&gt;{{ error }}&lt;\/div&gt;<br \/>\n            {% endfor %}<br \/>\n        &lt;\/div&gt;<\/p>\n<p>        &lt;div class&#061;&#034;form-group&#034;&gt;<br \/>\n            {{ form.email.label }}<br \/>\n            {{ form.email(size&#061;32) }}<br \/>\n            {% for error in form.email.errors %}<br \/>\n            &lt;div class&#061;&#034;error&#034;&gt;{{ error }}&lt;\/div&gt;<br \/>\n            {% endfor %}<br \/>\n        &lt;\/div&gt;<\/p>\n<p>        &lt;div class&#061;&#034;form-group&#034;&gt;<br \/>\n            {{ form.message.label }}<br \/>\n            {{ form.message(rows&#061;5) }}<br \/>\n            {% for error in form.message.errors %}<br \/>\n            &lt;div class&#061;&#034;error&#034;&gt;{{ error }}&lt;\/div&gt;<br \/>\n            {% endfor %}<br \/>\n        &lt;\/div&gt;<\/p>\n<p>        &lt;div class&#061;&#034;form-group&#034;&gt;<br \/>\n            {{ form.submit(class&#061;&#034;btn&#034;) }}<br \/>\n        &lt;\/div&gt;<br \/>\n    &lt;\/form&gt;<\/p>\n<p>    &lt;script&gt;<br \/>\n        \/\/ \u5982\u679c\u6536\u5230205\u54cd\u5e94&#xff0c;\u91cd\u7f6e\u8868\u5355<br \/>\n        document.getElementById(&#039;contact-form&#039;).addEventListener(&#039;submit&#039;, async function(e) {<br \/>\n            e.preventDefault();<\/p>\n<p>            const formData &#061; new FormData(this);<\/p>\n<p>            try {<br \/>\n                const response &#061; await fetch(this.action, {<br \/>\n                    method: this.method,<br \/>\n                    body: formData,<br \/>\n                    headers: {<br \/>\n                        &#039;X-Requested-With&#039;: &#039;XMLHttpRequest&#039;<br \/>\n                    }<br \/>\n                });<\/p>\n<p>                if (response.status &#061;&#061;&#061; 205) {<br \/>\n                    \/\/ \u91cd\u7f6e\u8868\u5355<br \/>\n                    this.reset();<\/p>\n<p>                    \/\/ \u663e\u793a\u6210\u529f\u6d88\u606f<br \/>\n                    const successDiv &#061; document.createElement(&#039;div&#039;);<br \/>\n                    successDiv.className &#061; &#039;success&#039;;<br \/>\n                    successDiv.id &#061; &#039;success-message&#039;;<br \/>\n                    successDiv.textContent &#061; &#039;Thank you for your message! The form has been reset for your next message.&#039;;<\/p>\n<p>                    const existingSuccess &#061; document.getElementById(&#039;success-message&#039;);<br \/>\n                    if (existingSuccess) {<br \/>\n                        existingSuccess.replaceWith(successDiv);<br \/>\n                    } else {<br \/>\n                        this.parentNode.insertBefore(successDiv, this);<br \/>\n                    }<\/p>\n<p>                    \/\/ 5\u79d2\u540e\u9690\u85cf\u6210\u529f\u6d88\u606f<br \/>\n                    setTimeout(() &#061;&gt; {<br \/>\n                        successDiv.style.transition &#061; &#039;opacity 0.5s&#039;;<br \/>\n                        successDiv.style.opacity &#061; &#039;0&#039;;<br \/>\n                        setTimeout(() &#061;&gt; successDiv.remove(), 500);<br \/>\n                    }, 5000);<\/p>\n<p>                } else {<br \/>\n                    \/\/ \u5904\u7406\u5176\u4ed6\u54cd\u5e94<br \/>\n                    const data &#061; await response.json();<br \/>\n                    if (data.errors) {<br \/>\n                        \/\/ \u663e\u793a\u9519\u8bef<br \/>\n                        alert(&#039;Error: &#039; &#043; JSON.stringify(data.errors));<br \/>\n                    }<br \/>\n                }<\/p>\n<p>            } catch (error) {<br \/>\n                console.error(&#039;Submission error:&#039;, error);<br \/>\n                alert(&#039;An error occurred. Please try again.&#039;);<br \/>\n            }<br \/>\n        });<br \/>\n    &lt;\/script&gt;<br \/>\n&lt;\/body&gt;<br \/>\n&lt;\/html&gt;<br \/>\n&#039;&#039;&#039;<\/p>\n<p>&#064;app.route(&#039;\/contact&#039;, methods&#061;[&#039;GET&#039;, &#039;POST&#039;])<br \/>\ndef contact():<br \/>\n    form &#061; ContactForm()<\/p>\n<p>    if request.method &#061;&#061; &#039;POST&#039;:<br \/>\n        # \u68c0\u67e5\u662f\u5426\u662fAJAX\u8bf7\u6c42<br \/>\n        is_ajax &#061; request.headers.get(&#039;X-Requested-With&#039;) &#061;&#061; &#039;XMLHttpRequest&#039;<\/p>\n<p>        if form.validate():<br \/>\n            # \u5904\u7406\u8868\u5355\u6570\u636e&#xff08;\u4f8b\u5982\u4fdd\u5b58\u5230\u6570\u636e\u5e93&#xff09;<br \/>\n            form_data &#061; {<br \/>\n                &#039;name&#039;: form.name.data,<br \/>\n                &#039;email&#039;: form.email.data,<br \/>\n                &#039;message&#039;: form.message.data,<br \/>\n                &#039;timestamp&#039;: datetime.datetime.utcnow().isoformat(),<br \/>\n                &#039;ip_address&#039;: request.remote_addr<br \/>\n            }<\/p>\n<p>            # \u6a21\u62df\u4fdd\u5b58\u5230\u6570\u636e\u5e93<br \/>\n            save_contact_message(form_data)<\/p>\n<p>            if is_ajax:<br \/>\n                # \u5bf9\u4e8eAJAX\u8bf7\u6c42&#xff0c;\u8fd4\u56de205 Reset Content<br \/>\n                response &#061; Response(status&#061;205)<br \/>\n                response.headers[&#039;X-Form-Processed&#039;] &#061; &#039;true&#039;<br \/>\n                response.headers[&#039;X-Message-ID&#039;] &#061; &#039;msg_&#039; &#043; str(hash(str(form_data)))<br \/>\n                return response<br \/>\n            else:<br \/>\n                # \u5bf9\u4e8e\u4f20\u7edf\u8868\u5355\u63d0\u4ea4&#xff0c;\u91cd\u5b9a\u5411\u5230\u76f8\u540c\u9875\u9762\u5e76\u663e\u793a\u6210\u529f\u6d88\u606f<br \/>\n                return render_template_string(FORM_TEMPLATE, form&#061;ContactForm(), success&#061;True)<br \/>\n        else:<br \/>\n            # \u9a8c\u8bc1\u5931\u8d25<br \/>\n            if is_ajax:<br \/>\n                return jsonify({<br \/>\n                    &#039;success&#039;: False,<br \/>\n                    &#039;errors&#039;: form.errors<br \/>\n                }), 400<br \/>\n            else:<br \/>\n                # \u91cd\u65b0\u663e\u793a\u5e26\u6709\u9519\u8bef\u4fe1\u606f\u7684\u8868\u5355<br \/>\n                return render_template_string(FORM_TEMPLATE, form&#061;form, success&#061;False)<\/p>\n<p>    # GET\u8bf7\u6c42&#xff1a;\u663e\u793a\u8868\u5355<br \/>\n    return render_template_string(FORM_TEMPLATE, form&#061;form, success&#061;False)<\/p>\n<p>def save_contact_message(data):<br \/>\n    &#034;&#034;&#034;\u4fdd\u5b58\u8054\u7cfb\u6d88\u606f&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n    # \u5b9e\u9645\u4e2d\u4f1a\u4fdd\u5b58\u5230\u6570\u636e\u5e93<br \/>\n    print(f&#034;Saving contact message: {data}&#034;)<\/p>\n<p># \u91cd\u7f6e\u6e38\u620f\u72b6\u6001\u793a\u4f8b<br \/>\n&#064;app.route(&#039;\/api\/game\/reset&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef reset_game():<br \/>\n    &#034;&#034;&#034;<br \/>\n    \u91cd\u7f6e\u6e38\u620f\u72b6\u6001<\/p>\n<p>    \u5178\u578b\u573a\u666f&#xff1a;<br \/>\n    1. \u6e38\u620f\u7ed3\u675f\u540e\u91cd\u7f6e<br \/>\n    2. \u7528\u6237\u60f3\u8981\u91cd\u65b0\u5f00\u59cb<br \/>\n    3. \u6e05\u9664\u6240\u6709\u8fdb\u5ea6<br \/>\n    &#034;&#034;&#034;<\/p>\n<p>    # \u9a8c\u8bc1\u7528\u6237\u8eab\u4efd<br \/>\n    user_id &#061; request.headers.get(&#039;X-User-ID&#039;)<br \/>\n    if not user_id:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Authentication required&#039;}), 401<\/p>\n<p>    # \u91cd\u7f6e\u6e38\u620f\u72b6\u6001&#xff08;\u6a21\u62df&#xff09;<br \/>\n    reset_user_game_state(user_id)<\/p>\n<p>    # \u8fd4\u56de205 Reset Content<br \/>\n    response &#061; Response(status&#061;205)<br \/>\n    response.headers[&#039;X-Game-Reset&#039;] &#061; &#039;true&#039;<br \/>\n    response.headers[&#039;X-Reset-Timestamp&#039;] &#061; datetime.datetime.utcnow().isoformat()<br \/>\n    response.headers[&#039;X-User-ID&#039;] &#061; user_id<\/p>\n<p>    return response<\/p>\n<p>def reset_user_game_state(user_id):<br \/>\n    &#034;&#034;&#034;\u91cd\u7f6e\u7528\u6237\u6e38\u620f\u72b6\u6001&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n    # \u5b9e\u9645\u4e2d\u4f1a\u66f4\u65b0\u6570\u636e\u5e93<br \/>\n    print(f&#034;Resetting game state for user: {user_id}&#034;)<\/p>\n<p># \u753b\u5e03\u91cd\u7f6e\u793a\u4f8b<br \/>\n&#064;app.route(&#039;\/api\/canvas\/reset&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef reset_canvas():<br \/>\n    &#034;&#034;&#034;<br \/>\n    \u91cd\u7f6e\u753b\u5e03<\/p>\n<p>    \u5178\u578b\u573a\u666f&#xff1a;<br \/>\n    1. \u7ed8\u56fe\u5e94\u7528\u4e2d\u7684\u6e05\u7a7a\u753b\u5e03<br \/>\n    2. \u767d\u677f\u5e94\u7528\u4e2d\u7684\u91cd\u7f6e<br \/>\n    3. \u56fe\u8868\u7f16\u8f91\u5668\u4e2d\u7684\u6e05\u9664<br \/>\n    &#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u753b\u5e03ID<br \/>\n    canvas_id &#061; request.json.get(&#039;canvas_id&#039;)<br \/>\n    if not canvas_id:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Canvas ID required&#039;}), 400<\/p>\n<p>    # \u91cd\u7f6e\u753b\u5e03&#xff08;\u6a21\u62df&#xff09;<br \/>\n    clear_canvas_data(canvas_id)<\/p>\n<p>    # \u8fd4\u56de205 Reset Content<br \/>\n    response &#061; Response(status&#061;205)<br \/>\n    response.headers[&#039;X-Canvas-Reset&#039;] &#061; &#039;true&#039;<br \/>\n    response.headers[&#039;X-Canvas-ID&#039;] &#061; canvas_id<br \/>\n    response.headers[&#039;X-Reset-At&#039;] &#061; datetime.datetime.utcnow().isoformat()<\/p>\n<p>    return response<\/p>\n<p>def clear_canvas_data(canvas_id):<br \/>\n    &#034;&#034;&#034;\u6e05\u9664\u753b\u5e03\u6570\u636e&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n    # \u5b9e\u9645\u4e2d\u4f1a\u6e05\u9664\u6570\u636e\u5e93\u4e2d\u7684\u753b\u5e03\u6570\u636e<br \/>\n    print(f&#034;Clearing canvas data for: {canvas_id}&#034;)<\/p>\n<p># \u6279\u91cf\u8868\u5355\u91cd\u7f6e<br \/>\n&#064;app.route(&#039;\/api\/forms\/batch-reset&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef batch_reset_forms():<br \/>\n    &#034;&#034;&#034;<br \/>\n    \u6279\u91cf\u91cd\u7f6e\u591a\u4e2a\u8868\u5355<\/p>\n<p>    \u5178\u578b\u573a\u666f&#xff1a;<br \/>\n    1. \u591a\u6b65\u9aa4\u5411\u5bfc\u4e2d\u7684\u91cd\u7f6e<br \/>\n    2. \u5305\u542b\u591a\u4e2a\u8868\u5355\u7684\u9875\u9762<br \/>\n    3. \u95ee\u5377\u5e94\u7528\u4e2d\u7684\u6e05\u9664\u6240\u6709\u7b54\u6848<br \/>\n    &#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u8981\u91cd\u7f6e\u7684\u8868\u5355ID\u5217\u8868<br \/>\n    form_ids &#061; request.json.get(&#039;form_ids&#039;, [])<\/p>\n<p>    if not form_ids:<br \/>\n        return jsonify({&#039;error&#039;: &#039;No form IDs provided&#039;}), 400<\/p>\n<p>    # \u91cd\u7f6e\u6bcf\u4e2a\u8868\u5355&#xff08;\u6a21\u62df&#xff09;<br \/>\n    reset_results &#061; []<br \/>\n    for form_id in form_ids:<br \/>\n        try:<br \/>\n            reset_form_data(form_id)<br \/>\n            reset_results.append({<br \/>\n                &#039;form_id&#039;: form_id,<br \/>\n                &#039;status&#039;: &#039;reset&#039;,<br \/>\n                &#039;timestamp&#039;: datetime.datetime.utcnow().isoformat()<br \/>\n            })<br \/>\n        except Exception as e:<br \/>\n            reset_results.append({<br \/>\n                &#039;form_id&#039;: form_id,<br \/>\n                &#039;status&#039;: &#039;failed&#039;,<br \/>\n                &#039;error&#039;: str(e)<br \/>\n            })<\/p>\n<p>    # \u8fd4\u56de205 Reset Content&#xff08;\u5373\u4f7f\u90e8\u5206\u5931\u8d25\u4e5f\u91cd\u7f6e\u5ba2\u6237\u7aef\u89c6\u56fe&#xff09;<br \/>\n    response &#061; Response(status&#061;205)<br \/>\n    response.headers[&#039;X-Batch-Reset&#039;] &#061; &#039;true&#039;<br \/>\n    response.headers[&#039;X-Reset-Count&#039;] &#061; str(len([r for r in reset_results if r[&#039;status&#039;] &#061;&#061; &#039;reset&#039;]))<br \/>\n    response.headers[&#039;X-Failed-Count&#039;] &#061; str(len([r for r in reset_results if r[&#039;status&#039;] &#061;&#061; &#039;failed&#039;]))<\/p>\n<p>    return response<\/p>\n<p>def reset_form_data(form_id):<br \/>\n    &#034;&#034;&#034;\u91cd\u7f6e\u8868\u5355\u6570\u636e&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n    # \u5b9e\u9645\u4e2d\u4f1a\u6e05\u9664\u8868\u5355\u6570\u636e<br \/>\n    print(f&#034;Resetting form data for: {form_id}&#034;)<\/p>\n<p>if __name__ &#061;&#061; &#039;__main__&#039;:<br \/>\n    app.run(debug&#061;True, port&#061;5002)<\/p>\n<h4>9.2.3 \u5ba2\u6237\u7aef\u5904\u7406<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5ba2\u6237\u7aef\u5904\u7406205\u54cd\u5e94\u7684\u793a\u4f8b<br \/>\nclass ResetContentHandler {<br \/>\n    \/**<br \/>\n     * \u5904\u7406205 Reset Content\u54cd\u5e94<br \/>\n     *\/<\/p>\n<p>    constructor() {<br \/>\n        this.formSelectors &#061; [<br \/>\n            &#039;form[data-reset-on-205]&#039;,<br \/>\n            &#039;form.reset-on-success&#039;,<br \/>\n            &#039;.resettable-form&#039;<br \/>\n        ];<\/p>\n<p>        this.initializeEventListeners();<br \/>\n    }<\/p>\n<p>    initializeEventListeners() {<br \/>\n        \/\/ \u76d1\u542c\u6240\u6709\u8868\u5355\u7684\u63d0\u4ea4\u4e8b\u4ef6<br \/>\n        document.querySelectorAll(&#039;form&#039;).forEach(form &#061;&gt; {<br \/>\n            form.addEventListener(&#039;submit&#039;, (e) &#061;&gt; this.handleFormSubmit(e));<br \/>\n        });<\/p>\n<p>        \/\/ \u76d1\u542c\u81ea\u5b9a\u4e49\u91cd\u7f6e\u4e8b\u4ef6<br \/>\n        document.addEventListener(&#039;resetContentRequired&#039;, (e) &#061;&gt; {<br \/>\n            this.resetContent(e.detail.element, e.detail.options);<br \/>\n        });<br \/>\n    }<\/p>\n<p>    async handleFormSubmit(event) {<br \/>\n        const form &#061; event.target;<\/p>\n<p>        \/\/ \u68c0\u67e5\u662f\u5426\u5e94\u8be5\u62e6\u622a\u63d0\u4ea4<br \/>\n        if (!this.shouldInterceptForm(form)) {<br \/>\n            return; \/\/ \u8ba9\u8868\u5355\u6b63\u5e38\u63d0\u4ea4<br \/>\n        }<\/p>\n<p>        event.preventDefault();<\/p>\n<p>        try {<br \/>\n            const response &#061; await this.submitForm(form);<\/p>\n<p>            if (response.status &#061;&#061;&#061; 205) {<br \/>\n                \/\/ \u91cd\u7f6e\u8868\u5355<br \/>\n                this.resetForm(form, response);<\/p>\n<p>                \/\/ \u663e\u793a\u6210\u529f\u6d88\u606f<br \/>\n                this.showSuccessMessage(form, response);<\/p>\n<p>                \/\/ \u89e6\u53d1\u81ea\u5b9a\u4e49\u4e8b\u4ef6<br \/>\n                this.triggerResetComplete(form, response);<\/p>\n<p>            } else if (response.ok) {<br \/>\n                \/\/ \u5176\u4ed6\u6210\u529f\u54cd\u5e94<br \/>\n                const data &#061; await response.json();<br \/>\n                this.handleSuccessResponse(form, data);<br \/>\n            } else {<br \/>\n                \/\/ \u9519\u8bef\u54cd\u5e94<br \/>\n                const error &#061; await response.json();<br \/>\n                this.handleErrorResponse(form, error);<br \/>\n            }<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;Form submission failed:&#039;, error);<br \/>\n            this.showError(form, &#039;Network error. Please try again.&#039;);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    shouldInterceptForm(form) {<br \/>\n        \/\/ \u68c0\u67e5\u8868\u5355\u662f\u5426\u5e94\u8be5\u88ab\u62e6\u622a<br \/>\n        return form.hasAttribute(&#039;data-ajax-submit&#039;) ||<br \/>\n               form.classList.contains(&#039;ajax-form&#039;) ||<br \/>\n               this.formSelectors.some(selector &#061;&gt; form.matches(selector));<br \/>\n    }<\/p>\n<p>    async submitForm(form) {<br \/>\n        const formData &#061; new FormData(form);<br \/>\n        const method &#061; form.method.toUpperCase();<br \/>\n        const url &#061; form.action;<\/p>\n<p>        \/\/ \u5bf9\u4e8eGET\u8bf7\u6c42&#xff0c;\u6784\u5efa\u67e5\u8be2\u5b57\u7b26\u4e32<br \/>\n        if (method &#061;&#061;&#061; &#039;GET&#039;) {<br \/>\n            const params &#061; new URLSearchParams(formData);<br \/>\n            const fullUrl &#061; url &#043; (url.includes(&#039;?&#039;) ? &#039;&amp;&#039; : &#039;?&#039;) &#043; params.toString();<br \/>\n            return fetch(fullUrl, { method: &#039;GET&#039; });<br \/>\n        }<\/p>\n<p>        \/\/ \u5bf9\u4e8ePOST\/PUT\/PATCH\u8bf7\u6c42<br \/>\n        return fetch(url, {<br \/>\n            method: method,<br \/>\n            body: method &#061;&#061;&#061; &#039;POST&#039; ? formData : JSON.stringify(Object.fromEntries(formData)),<br \/>\n            headers: {<br \/>\n                &#039;X-Requested-With&#039;: &#039;XMLHttpRequest&#039;,<br \/>\n                &#8230;(method !&#061;&#061; &#039;POST&#039; &amp;&amp; { &#039;Content-Type&#039;: &#039;application\/json&#039; })<br \/>\n            }<br \/>\n        });<br \/>\n    }<\/p>\n<p>    resetForm(form, response) {<br \/>\n        \/\/ \u91cd\u7f6e\u8868\u5355\u5b57\u6bb5<br \/>\n        form.reset();<\/p>\n<p>        \/\/ \u6e05\u9664\u81ea\u5b9a\u4e49\u5b57\u6bb5\u72b6\u6001<br \/>\n        this.clearCustomFieldStates(form);<\/p>\n<p>        \/\/ \u91cd\u7f6e\u9a8c\u8bc1\u72b6\u6001<br \/>\n        this.resetValidation(form);<\/p>\n<p>        \/\/ \u5982\u679c\u6709\u6587\u4ef6\u8f93\u5165&#xff0c;\u6e05\u9664\u6587\u4ef6<br \/>\n        this.clearFileInputs(form);<\/p>\n<p>        \/\/ \u89e6\u53d1\u8868\u5355\u91cd\u7f6e\u4e8b\u4ef6<br \/>\n        form.dispatchEvent(new Event(&#039;reset&#039;, { bubbles: true }));<\/p>\n<p>        \/\/ \u6dfb\u52a0\u89c6\u89c9\u53cd\u9988<br \/>\n        this.addResetAnimation(form);<br \/>\n    }<\/p>\n<p>    clearCustomFieldStates(form) {<br \/>\n        \/\/ \u6e05\u9664\u81ea\u5b9a\u4e49\u5b57\u6bb5&#xff08;\u5982\u5bcc\u6587\u672c\u7f16\u8f91\u5668\u3001\u65e5\u671f\u9009\u62e9\u5668\u7b49&#xff09;<br \/>\n        form.querySelectorAll(&#039;[data-custom-field]&#039;).forEach(field &#061;&gt; {<br \/>\n            if (field.type &#061;&#061;&#061; &#039;textarea&#039; &amp;&amp; field.dataset.richText) {<br \/>\n                \/\/ \u91cd\u7f6e\u5bcc\u6587\u672c\u7f16\u8f91\u5668<br \/>\n                if (window.tinymce &amp;&amp; window.tinymce.get(field.id)) {<br \/>\n                    window.tinymce.get(field.id).setContent(&#039;&#039;);<br \/>\n                }<br \/>\n            }<\/p>\n<p>            if (field.type &#061;&#061;&#061; &#039;select-one&#039; &amp;&amp; field.dataset.select2) {<br \/>\n                \/\/ \u91cd\u7f6eSelect2<br \/>\n                if ($ &amp;&amp; $(field).data(&#039;select2&#039;)) {<br \/>\n                    $(field).val(null).trigger(&#039;change&#039;);<br \/>\n                }<br \/>\n            }<br \/>\n        });<br \/>\n    }<\/p>\n<p>    resetValidation(form) {<br \/>\n        \/\/ \u6e05\u9664\u9a8c\u8bc1\u9519\u8bef<br \/>\n        form.querySelectorAll(&#039;.error, .is-invalid&#039;).forEach(el &#061;&gt; {<br \/>\n            el.classList.remove(&#039;error&#039;, &#039;is-invalid&#039;);<br \/>\n        });<\/p>\n<p>        \/\/ \u6e05\u9664\u9519\u8bef\u6d88\u606f<br \/>\n        form.querySelectorAll(&#039;.error-message, .invalid-feedback&#039;).forEach(el &#061;&gt; {<br \/>\n            el.remove();<br \/>\n        });<br \/>\n    }<\/p>\n<p>    clearFileInputs(form) {<br \/>\n        \/\/ \u6e05\u9664\u6587\u4ef6\u8f93\u5165<br \/>\n        form.querySelectorAll(&#039;input[type&#061;&#034;file&#034;]&#039;).forEach(input &#061;&gt; {<br \/>\n            input.value &#061; &#039;&#039;;<\/p>\n<p>            \/\/ \u89e6\u53d1\u66f4\u6539\u4e8b\u4ef6<br \/>\n            input.dispatchEvent(new Event(&#039;change&#039;, { bubbles: true }));<br \/>\n        });<br \/>\n    }<\/p>\n<p>    addResetAnimation(form) {<br \/>\n        \/\/ \u6dfb\u52a0\u91cd\u7f6e\u52a8\u753b<br \/>\n        form.style.transition &#061; &#039;all 0.3s&#039;;<br \/>\n        form.style.backgroundColor &#061; &#039;#f8f9fa&#039;;<\/p>\n<p>        setTimeout(() &#061;&gt; {<br \/>\n            form.style.backgroundColor &#061; &#039;&#039;;<br \/>\n        }, 300);<br \/>\n    }<\/p>\n<p>    showSuccessMessage(form, response) {<br \/>\n        \/\/ \u663e\u793a\u6210\u529f\u6d88\u606f<br \/>\n        const successDiv &#061; document.createElement(&#039;div&#039;);<br \/>\n        successDiv.className &#061; &#039;alert alert-success success-message&#039;;<br \/>\n        successDiv.setAttribute(&#039;role&#039;, &#039;alert&#039;);<\/p>\n<p>        \/\/ \u4ece\u54cd\u5e94\u5934\u83b7\u53d6\u6d88\u606f<br \/>\n        const message &#061; response.headers.get(&#039;X-Success-Message&#039;) ||<br \/>\n                       &#039;Form submitted successfully! The form has been reset.&#039;;<\/p>\n<p>        successDiv.textContent &#061; message;<\/p>\n<p>        \/\/ \u63d2\u5165\u5230\u8868\u5355\u524d<br \/>\n        form.parentNode.insertBefore(successDiv, form);<\/p>\n<p>        \/\/ 5\u79d2\u540e\u81ea\u52a8\u9690\u85cf<br \/>\n        setTimeout(() &#061;&gt; {<br \/>\n            successDiv.style.opacity &#061; &#039;0&#039;;<br \/>\n            successDiv.style.transition &#061; &#039;opacity 0.5s&#039;;<\/p>\n<p>            setTimeout(() &#061;&gt; {<br \/>\n                successDiv.remove();<br \/>\n            }, 500);<br \/>\n        }, 5000);<br \/>\n    }<\/p>\n<p>    triggerResetComplete(form, response) {<br \/>\n        \/\/ \u89e6\u53d1\u81ea\u5b9a\u4e49\u4e8b\u4ef6<br \/>\n        const event &#061; new CustomEvent(&#039;formResetComplete&#039;, {<br \/>\n            detail: {<br \/>\n                form: form,<br \/>\n                response: response,<br \/>\n                timestamp: new Date().toISOString(),<br \/>\n                headers: Object.fromEntries(response.headers.entries())<br \/>\n            },<br \/>\n            bubbles: true<br \/>\n        });<\/p>\n<p>        form.dispatchEvent(event);<br \/>\n    }<\/p>\n<p>    handleSuccessResponse(form, data) {<br \/>\n        \/\/ \u5904\u7406\u5176\u4ed6\u6210\u529f\u54cd\u5e94<br \/>\n        console.log(&#039;Form submitted successfully:&#039;, data);<\/p>\n<p>        \/\/ \u663e\u793a\u6d88\u606f<br \/>\n        this.showSuccessMessage(form, {<br \/>\n            headers: new Headers({ &#039;X-Success-Message&#039;: data.message || &#039;Success!&#039; })<br \/>\n        });<br \/>\n    }<\/p>\n<p>    handleErrorResponse(form, error) {<br \/>\n        \/\/ \u663e\u793a\u9519\u8bef<br \/>\n        console.error(&#039;Form submission error:&#039;, error);<\/p>\n<p>        \/\/ \u663e\u793a\u9519\u8bef\u6d88\u606f<br \/>\n        const errorDiv &#061; document.createElement(&#039;div&#039;);<br \/>\n        errorDiv.className &#061; &#039;alert alert-danger&#039;;<br \/>\n        errorDiv.textContent &#061; error.message || &#039;An error occurred. Please check your input.&#039;;<\/p>\n<p>        form.parentNode.insertBefore(errorDiv, form);<\/p>\n<p>        \/\/ 3\u79d2\u540e\u81ea\u52a8\u9690\u85cf<br \/>\n        setTimeout(() &#061;&gt; {<br \/>\n            errorDiv.remove();<br \/>\n        }, 3000);<br \/>\n    }<\/p>\n<p>    showError(form, message) {<br \/>\n        \/\/ \u663e\u793a\u901a\u7528\u9519\u8bef<br \/>\n        alert(message); \/\/ \u5728\u5b9e\u9645\u5e94\u7528\u4e2d\u66ff\u6362\u4e3a\u66f4\u597d\u7684UI<br \/>\n    }<\/p>\n<p>    \/\/ \u901a\u7528\u5185\u5bb9\u91cd\u7f6e\u65b9\u6cd5<br \/>\n    resetContent(element, options &#061; {}) {<br \/>\n        const defaults &#061; {<br \/>\n            clearInputs: true,<br \/>\n            resetSelects: true,<br \/>\n            clearTextareas: true,<br \/>\n            removeDynamicContent: true,<br \/>\n            animation: true<br \/>\n        };<\/p>\n<p>        const settings &#061; { &#8230;defaults, &#8230;options };<\/p>\n<p>        \/\/ \u91cd\u7f6e\u8868\u5355\u5143\u7d20<br \/>\n        if (element.tagName &#061;&#061;&#061; &#039;FORM&#039;) {<br \/>\n            element.reset();<\/p>\n<p>            if (settings.clearInputs) {<br \/>\n                element.querySelectorAll(&#039;input:not([type&#061;&#034;hidden&#034;])&#039;).forEach(input &#061;&gt; {<br \/>\n                    if (input.type !&#061;&#061; &#039;checkbox&#039; &amp;&amp; input.type !&#061;&#061; &#039;radio&#039;) {<br \/>\n                        input.value &#061; &#039;&#039;;<br \/>\n                    } else {<br \/>\n                        input.checked &#061; false;<br \/>\n                    }<br \/>\n                });<br \/>\n            }<\/p>\n<p>            if (settings.resetSelects) {<br \/>\n                element.querySelectorAll(&#039;select&#039;).forEach(select &#061;&gt; {<br \/>\n                    select.selectedIndex &#061; 0;<br \/>\n                });<br \/>\n            }<\/p>\n<p>            if (settings.clearTextareas) {<br \/>\n                element.querySelectorAll(&#039;textarea&#039;).forEach(textarea &#061;&gt; {<br \/>\n                    textarea.value &#061; &#039;&#039;;<br \/>\n                });<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/\/ \u91cd\u7f6e\u52a8\u6001\u5185\u5bb9<br \/>\n        if (settings.removeDynamicContent) {<br \/>\n            element.querySelectorAll(&#039;[data-dynamic]&#039;).forEach(el &#061;&gt; {<br \/>\n                el.remove();<br \/>\n            });<br \/>\n        }<\/p>\n<p>        \/\/ \u6dfb\u52a0\u52a8\u753b\u6548\u679c<br \/>\n        if (settings.animation) {<br \/>\n            element.style.transition &#061; &#039;all 0.3s&#039;;<br \/>\n            element.style.opacity &#061; &#039;0.8&#039;;<\/p>\n<p>            setTimeout(() &#061;&gt; {<br \/>\n                element.style.opacity &#061; &#039;1&#039;;<br \/>\n            }, 300);<br \/>\n        }<\/p>\n<p>        \/\/ \u89e6\u53d1\u91cd\u7f6e\u4e8b\u4ef6<br \/>\n        element.dispatchEvent(new CustomEvent(&#039;contentReset&#039;, {<br \/>\n            detail: { settings, timestamp: new Date().toISOString() }<br \/>\n        }));<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\ndocument.addEventListener(&#039;DOMContentLoaded&#039;, () &#061;&gt; {<br \/>\n    \/\/ \u521d\u59cb\u5316\u5904\u7406\u5668<br \/>\n    const resetHandler &#061; new ResetContentHandler();<\/p>\n<p>    \/\/ \u91cd\u7f6e\u753b\u5e03\u793a\u4f8b<br \/>\n    document.getElementById(&#039;reset-canvas-btn&#039;).addEventListener(&#039;click&#039;, async () &#061;&gt; {<br \/>\n        try {<br \/>\n            const response &#061; await fetch(&#039;\/api\/canvas\/reset&#039;, {<br \/>\n                method: &#039;POST&#039;,<br \/>\n                headers: {<br \/>\n                    &#039;Content-Type&#039;: &#039;application\/json&#039;<br \/>\n                },<br \/>\n                body: JSON.stringify({<br \/>\n                    canvas_id: &#039;canvas-123&#039;<br \/>\n                })<br \/>\n            });<\/p>\n<p>            if (response.status &#061;&#061;&#061; 205) {<br \/>\n                \/\/ \u6e05\u7a7a\u753b\u5e03<br \/>\n                const canvas &#061; document.getElementById(&#039;drawing-canvas&#039;);<br \/>\n                const ctx &#061; canvas.getContext(&#039;2d&#039;);<br \/>\n                ctx.clearRect(0, 0, canvas.width, canvas.height);<\/p>\n<p>                \/\/ \u663e\u793a\u6d88\u606f<br \/>\n                alert(&#039;Canvas cleared successfully!&#039;);<br \/>\n            }<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Failed to reset canvas:&#039;, error);<br \/>\n        }<br \/>\n    });<\/p>\n<p>    \/\/ \u91cd\u7f6e\u6e38\u620f\u793a\u4f8b<br \/>\n    document.getElementById(&#039;reset-game-btn&#039;).addEventListener(&#039;click&#039;, async () &#061;&gt; {<br \/>\n        if (!confirm(&#039;Are you sure you want to reset the game? All progress will be lost.&#039;)) {<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        try {<br \/>\n            const response &#061; await fetch(&#039;\/api\/game\/reset&#039;, {<br \/>\n                method: &#039;POST&#039;,<br \/>\n                headers: {<br \/>\n                    &#039;X-User-ID&#039;: &#039;user-123&#039;<br \/>\n                }<br \/>\n            });<\/p>\n<p>            if (response.status &#061;&#061;&#061; 205) {<br \/>\n                \/\/ \u91cd\u7f6e\u6e38\u620f\u754c\u9762<br \/>\n                resetHandler.resetContent(document.getElementById(&#039;game-container&#039;), {<br \/>\n                    clearInputs: true,<br \/>\n                    animation: true<br \/>\n                });<\/p>\n<p>                \/\/ \u663e\u793a\u91cd\u7f6e\u6d88\u606f<br \/>\n                document.getElementById(&#039;game-message&#039;).textContent &#061; &#039;Game reset! Ready to start new game.&#039;;<br \/>\n            }<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Failed to reset game:&#039;, error);<br \/>\n        }<br \/>\n    });<\/p>\n<p>    \/\/ \u6279\u91cf\u91cd\u7f6e\u793a\u4f8b<br \/>\n    document.getElementById(&#039;reset-all-forms&#039;).addEventListener(&#039;click&#039;, () &#061;&gt; {<br \/>\n        \/\/ \u89e6\u53d1\u81ea\u5b9a\u4e49\u91cd\u7f6e\u4e8b\u4ef6<br \/>\n        document.dispatchEvent(new CustomEvent(&#039;resetContentRequired&#039;, {<br \/>\n            detail: {<br \/>\n                element: document.body,<br \/>\n                options: {<br \/>\n                    clearInputs: true,<br \/>\n                    resetSelects: true,<br \/>\n                    animation: true<br \/>\n                }<br \/>\n            }<br \/>\n        }));<br \/>\n    });<br \/>\n});<\/p>\n<h3>9.3 207 Multi-Status<\/h3>\n<h4>9.3.1 \u8bed\u4e49\u6df1\u5ea6\u89e3\u6790<\/h4>\n<p>\u6839\u636eRFC 4918&#xff08;WebDAV&#xff09;\u7b2c11.1\u8282&#xff0c;207 Multi-Status\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;<\/p>\n<p>207 (Multi-Status)\u72b6\u6001\u7801\u63d0\u4f9b\u6709\u5173\u591a\u4e2a\u8d44\u6e90\u7684\u72b6\u6001\u4fe1\u606f&#xff0c;\u9002\u7528\u4e8e\u9700\u8981\u591a\u4e2a\u72ec\u7acb\u72b6\u6001\u7801\u7684\u60c5\u51b5\u3002\u54cd\u5e94\u4f53\u662f\u4e00\u4e2aXML\u6587\u6863&#xff0c;\u5305\u542b\u591a\u4e2a&#034;response&#034;\u5143\u7d20&#xff0c;\u6bcf\u4e2a\u5143\u7d20\u63cf\u8ff0\u4e00\u4e2a\u72ec\u7acb\u8d44\u6e90\u8bf7\u6c42\u7684\u7ed3\u679c\u3002<\/p>\n<h5>\u6838\u5fc3\u8bed\u4e49\u7279\u5f81<\/h5>\n<li>\n<p>\u591a\u8d44\u6e90\u72b6\u6001&#xff1a;\u5355\u4e2a\u54cd\u5e94\u5305\u542b\u591a\u4e2a\u72ec\u7acb\u72b6\u6001<\/p>\n<\/li>\n<li>\n<p>XML\u683c\u5f0f&#xff1a;\u54cd\u5e94\u4f53\u5fc5\u987b\u662fXML\u683c\u5f0f<\/p>\n<\/li>\n<li>\n<p>WebDAV\u6838\u5fc3&#xff1a;\u4e3b\u8981\u7528\u4e8eWebDAV\u534f\u8bae<\/p>\n<\/li>\n<li>\n<p>\u6279\u91cf\u64cd\u4f5c&#xff1a;\u9002\u5408\u6279\u91cf\u521b\u5efa\u3001\u66f4\u65b0\u3001\u5220\u9664\u6216\u67e5\u8be2<\/p>\n<\/li>\n<h4>9.3.2 \u9002\u7528\u573a\u666f\u4e0e\u5b9e\u73b0<\/h4>\n<p>python<\/p>\n<p># 207 Multi-Status\u670d\u52a1\u5668\u5b9e\u73b0<br \/>\nfrom flask import Flask, request, Response<br \/>\nimport xml.etree.ElementTree as ET<br \/>\nfrom xml.dom import minidom<br \/>\nfrom datetime import datetime<br \/>\nfrom typing import List, Dict, Any<br \/>\nimport json<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p>class MultiStatusResponse:<br \/>\n    &#034;&#034;&#034;207 Multi-Status\u54cd\u5e94\u751f\u6210\u5668&#034;&#034;&#034;<\/p>\n<p>    DAV_NAMESPACE &#061; &#039;DAV:&#039;<br \/>\n    DAV_PREFIX &#061; &#039;d&#039;<\/p>\n<p>    def __init__(self):<br \/>\n        # \u6ce8\u518c\u547d\u540d\u7a7a\u95f4<br \/>\n        ET.register_namespace(self.DAV_PREFIX, self.DAV_NAMESPACE)<\/p>\n<p>    def create_response(self, responses: List[Dict[str, Any]]) -&gt; Response:<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u521b\u5efa207 Multi-Status\u54cd\u5e94<\/p>\n<p>        Args:<br \/>\n            responses: \u54cd\u5e94\u5217\u8868&#xff0c;\u6bcf\u4e2a\u5143\u7d20\u5305\u542b:<br \/>\n                &#8211; href: \u8d44\u6e90URL<br \/>\n                &#8211; status: HTTP\u72b6\u6001\u7801<br \/>\n                &#8211; message: \u72b6\u6001\u6d88\u606f<br \/>\n                &#8211; props: \u5c5e\u6027\u5b57\u5178&#xff08;\u53ef\u9009&#xff09;<br \/>\n                &#8211; error: \u9519\u8bef\u4fe1\u606f&#xff08;\u53ef\u9009&#xff09;<\/p>\n<p>        Returns:<br \/>\n            Flask Response\u5bf9\u8c61<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        # \u521b\u5efa\u6839\u5143\u7d20<br \/>\n        root &#061; ET.Element(f&#039;{{{self.DAV_NAMESPACE}}}multistatus&#039;)<\/p>\n<p>        for resp in responses:<br \/>\n            response_elem &#061; self._create_response_element(resp)<br \/>\n            root.append(response_elem)<\/p>\n<p>        # \u8f6c\u6362\u4e3a\u683c\u5f0f\u5316\u7684XML<br \/>\n        xml_str &#061; self._prettify_xml(root)<\/p>\n<p>        # \u521b\u5efa\u54cd\u5e94<br \/>\n        return Response(<br \/>\n            xml_str,<br \/>\n            status&#061;207,<br \/>\n            content_type&#061;&#039;application\/xml; charset&#061;&#034;utf-8&#034;&#039;,<br \/>\n            headers&#061;{<br \/>\n                &#039;Date&#039;: datetime.utcnow().strftime(&#039;%a, %d %b %Y %H:%M:%S GMT&#039;),<br \/>\n                &#039;Server&#039;: &#039;WebDAV-Server\/1.0&#039;<br \/>\n            }<br \/>\n        )<\/p>\n<p>    def _create_response_element(self, response_info: Dict[str, Any]) -&gt; ET.Element:<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u5355\u4e2aresponse\u5143\u7d20&#034;&#034;&#034;<br \/>\n        response_elem &#061; ET.Element(f&#039;{{{self.DAV_NAMESPACE}}}response&#039;)<\/p>\n<p>        # href\u5143\u7d20<br \/>\n        href_elem &#061; ET.SubElement(response_elem, f&#039;{{{self.DAV_NAMESPACE}}}href&#039;)<br \/>\n        href_elem.text &#061; response_info.get(&#039;href&#039;, &#039;&#039;)<\/p>\n<p>        # \u72b6\u6001\u5143\u7d20<br \/>\n        if &#039;status&#039; in response_info:<br \/>\n            status_elem &#061; ET.SubElement(response_elem, f&#039;{{{self.DAV_NAMESPACE}}}status&#039;)<br \/>\n            status_code &#061; response_info[&#039;status&#039;]<br \/>\n            status_msg &#061; response_info.get(&#039;message&#039;, &#039;OK&#039;)<br \/>\n            status_elem.text &#061; f&#039;HTTP\/1.1 {status_code} {status_msg}&#039;<\/p>\n<p>        # \u5c5e\u6027\u5143\u7d20&#xff08;\u5982\u679c\u6709&#xff09;<br \/>\n        if &#039;props&#039; in response_info:<br \/>\n            propstat_elem &#061; ET.SubElement(response_elem, f&#039;{{{self.DAV_NAMESPACE}}}propstat&#039;)<\/p>\n<p>            # prop\u5143\u7d20<br \/>\n            prop_elem &#061; ET.SubElement(propstat_elem, f&#039;{{{self.DAV_NAMESPACE}}}prop&#039;)<br \/>\n            for prop_name, prop_value in response_info[&#039;props&#039;].items():<br \/>\n                prop_item &#061; ET.SubElement(prop_elem, f&#039;{{{self.DAV_NAMESPACE}}}{prop_name}&#039;)<br \/>\n                prop_item.text &#061; str(prop_value)<\/p>\n<p>            # \u5c5e\u6027\u72b6\u6001<br \/>\n            prop_status &#061; ET.SubElement(propstat_elem, f&#039;{{{self.DAV_NAMESPACE}}}status&#039;)<br \/>\n            prop_status.text &#061; f&#039;HTTP\/1.1 {response_info.get(&#034;prop_status&#034;, 200)} OK&#039;<\/p>\n<p>        # \u9519\u8bef\u5143\u7d20&#xff08;\u5982\u679c\u6709&#xff09;<br \/>\n        if &#039;error&#039; in response_info:<br \/>\n            error_elem &#061; ET.SubElement(response_elem, f&#039;{{{self.DAV_NAMESPACE}}}error&#039;)<br \/>\n            error_desc &#061; ET.SubElement(error_elem, f&#039;{{{self.DAV_NAMESPACE}}}description&#039;)<br \/>\n            error_desc.text &#061; response_info[&#039;error&#039;]<\/p>\n<p>        return response_elem<\/p>\n<p>    def _prettify_xml(self, elem: ET.Element) -&gt; str:<br \/>\n        &#034;&#034;&#034;\u683c\u5f0f\u5316XML&#034;&#034;&#034;<br \/>\n        rough_string &#061; ET.tostring(elem, &#039;utf-8&#039;)<br \/>\n        parsed &#061; minidom.parseString(rough_string)<br \/>\n        return parsed.toprettyxml(indent&#061;&#039;  &#039;)<\/p>\n<p>    def parse_request(self, xml_body: str) -&gt; List[Dict[str, Any]]:<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u89e3\u6790Multi-Status\u8bf7\u6c42\u4f53<\/p>\n<p>        Args:<br \/>\n            xml_body: XML\u8bf7\u6c42\u4f53<\/p>\n<p>        Returns:<br \/>\n            \u89e3\u6790\u540e\u7684\u8bf7\u6c42\u5217\u8868<br \/>\n        &#034;&#034;&#034;<br \/>\n        try:<br \/>\n            root &#061; ET.fromstring(xml_body)<br \/>\n            requests &#061; []<\/p>\n<p>            # \u67e5\u627e\u6240\u6709href\u5143\u7d20<br \/>\n            for href_elem in root.findall(f&#039;.\/\/{{{self.DAV_NAMESPACE}}}href&#039;):<br \/>\n                request_info &#061; {<br \/>\n                    &#039;href&#039;: href_elem.text,<br \/>\n                    &#039;method&#039;: self._extract_method(href_elem),<br \/>\n                    &#039;properties&#039;: self._extract_properties(href_elem)<br \/>\n                }<br \/>\n                requests.append(request_info)<\/p>\n<p>            return requests<\/p>\n<p>        except ET.ParseError as e:<br \/>\n            raise ValueError(f&#039;Invalid XML: {e}&#039;)<\/p>\n<p>    def _extract_method(self, href_elem: ET.Element) -&gt; str:<br \/>\n        &#034;&#034;&#034;\u63d0\u53d6\u8bf7\u6c42\u65b9\u6cd5&#034;&#034;&#034;<br \/>\n        # \u5728\u5b9e\u9645WebDAV\u4e2d&#xff0c;\u8fd9\u53ef\u80fd\u6765\u81ea\u4e0d\u540c\u7684\u4f4d\u7f6e<br \/>\n        parent &#061; href_elem.getparent()<br \/>\n        if parent is not None:<br \/>\n            method_elem &#061; parent.find(f&#039;{{{self.DAV_NAMESPACE}}}method&#039;)<br \/>\n            if method_elem is not None:<br \/>\n                return method_elem.text<br \/>\n        return &#039;GET&#039;  # \u9ed8\u8ba4<\/p>\n<p>    def _extract_properties(self, href_elem: ET.Element) -&gt; Dict[str, str]:<br \/>\n        &#034;&#034;&#034;\u63d0\u53d6\u8bf7\u6c42\u5c5e\u6027&#034;&#034;&#034;<br \/>\n        properties &#061; {}<br \/>\n        parent &#061; href_elem.getparent()<\/p>\n<p>        if parent is not None:<br \/>\n            prop_elem &#061; parent.find(f&#039;{{{self.DAV_NAMESPACE}}}prop&#039;)<br \/>\n            if prop_elem is not None:<br \/>\n                for child in prop_elem:<br \/>\n                    # \u79fb\u9664\u547d\u540d\u7a7a\u95f4\u524d\u7f00<br \/>\n                    tag &#061; child.tag<br \/>\n                    if &#039;}&#039; in tag:<br \/>\n                        tag &#061; tag.split(&#039;}&#039;, 1)[1]<br \/>\n                    properties[tag] &#061; child.text<\/p>\n<p>        return properties<\/p>\n<p># \u521b\u5efa\u54cd\u5e94\u751f\u6210\u5668\u5b9e\u4f8b<br \/>\nmultistatus &#061; MultiStatusResponse()<\/p>\n<p># WebDAV PROPFIND\u65b9\u6cd5\u5b9e\u73b0<br \/>\n&#064;app.route(&#039;\/dav\/&lt;path:path&gt;&#039;, methods&#061;[&#039;PROPFIND&#039;])<br \/>\ndef webdav_propfind(path):<br \/>\n    &#034;&#034;&#034;<br \/>\n    WebDAV PROPFIND\u65b9\u6cd5<\/p>\n<p>    \u7528\u4e8e\u68c0\u7d22\u8d44\u6e90\u7684\u5c5e\u6027<br \/>\n    &#034;&#034;&#034;<\/p>\n<p>    # \u89e3\u6790\u8bf7\u6c42\u6df1\u5ea6<br \/>\n    depth &#061; request.headers.get(&#039;Depth&#039;, &#039;1&#039;)<\/p>\n<p>    # \u6784\u5efa\u54cd\u5e94\u5217\u8868<br \/>\n    responses &#061; []<\/p>\n<p>    # \u6dfb\u52a0\u8bf7\u6c42\u7684\u8d44\u6e90<br \/>\n    base_url &#061; f&#039;\/dav\/{path}&#039;<br \/>\n    responses.append({<br \/>\n        &#039;href&#039;: base_url,<br \/>\n        &#039;status&#039;: 200,<br \/>\n        &#039;message&#039;: &#039;OK&#039;,<br \/>\n        &#039;props&#039;: {<br \/>\n            &#039;displayname&#039;: path.split(&#039;\/&#039;)[-1] or &#039;Root&#039;,<br \/>\n            &#039;resourcetype&#039;: &#039;&lt;collection\/&gt;&#039; if path.endswith(&#039;\/&#039;) else &#039;&#039;,<br \/>\n            &#039;getcontentlength&#039;: &#039;1024&#039;,  # \u793a\u4f8b\u5927\u5c0f<br \/>\n            &#039;getlastmodified&#039;: datetime.utcnow().strftime(&#039;%a, %d %b %Y %H:%M:%S GMT&#039;),<br \/>\n            &#039;creationdate&#039;: &#039;2023-01-01T00:00:00Z&#039;<br \/>\n        }<br \/>\n    })<\/p>\n<p>    # \u5982\u679c\u6df1\u5ea6\u5927\u4e8e0&#xff0c;\u6dfb\u52a0\u5b50\u8d44\u6e90<br \/>\n    if depth !&#061; &#039;0&#039;:<br \/>\n        # \u6a21\u62df\u5b50\u8d44\u6e90<br \/>\n        for i in range(3):<br \/>\n            child_path &#061; f&#039;{base_url}child{i}&#039;<br \/>\n            responses.append({<br \/>\n                &#039;href&#039;: child_path,<br \/>\n                &#039;status&#039;: 200,<br \/>\n                &#039;message&#039;: &#039;OK&#039;,<br \/>\n                &#039;props&#039;: {<br \/>\n                    &#039;displayname&#039;: f&#039;child{i}&#039;,<br \/>\n                    &#039;resourcetype&#039;: &#039;&#039;,<br \/>\n                    &#039;getcontentlength&#039;: &#039;512&#039;,<br \/>\n                    &#039;getlastmodified&#039;: datetime.utcnow().strftime(&#039;%a, %d %b %Y %H:%M:%S GMT&#039;)<br \/>\n                }<br \/>\n            })<\/p>\n<p>    # \u8fd4\u56de207\u54cd\u5e94<br \/>\n    return multistatus.create_response(responses)<\/p>\n<p># \u6279\u91cf\u5220\u9664\u5b9e\u73b0<br \/>\n&#064;app.route(&#039;\/api\/resources\/batch-delete&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef batch_delete():<br \/>\n    &#034;&#034;&#034;<br \/>\n    \u6279\u91cf\u5220\u9664\u8d44\u6e90<\/p>\n<p>    \u8fd4\u56de207 Multi-Status\u54cd\u5e94&#xff0c;\u5305\u542b\u6bcf\u4e2a\u8d44\u6e90\u7684\u5220\u9664\u7ed3\u679c<br \/>\n    &#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u8981\u5220\u9664\u7684\u8d44\u6e90ID\u5217\u8868<br \/>\n    resource_ids &#061; request.json.get(&#039;resources&#039;, [])<\/p>\n<p>    if not resource_ids:<br \/>\n        return jsonify({&#039;error&#039;: &#039;No resources specified&#039;}), 400<\/p>\n<p>    responses &#061; []<\/p>\n<p>    for resource_id in resource_ids:<br \/>\n        try:<br \/>\n            # \u5c1d\u8bd5\u5220\u9664\u8d44\u6e90<br \/>\n            success &#061; delete_resource(resource_id)<\/p>\n<p>            if success:<br \/>\n                responses.append({<br \/>\n                    &#039;href&#039;: f&#039;\/api\/resources\/{resource_id}&#039;,<br \/>\n                    &#039;status&#039;: 200,<br \/>\n                    &#039;message&#039;: &#039;OK&#039;<br \/>\n                })<br \/>\n            else:<br \/>\n                responses.append({<br \/>\n                    &#039;href&#039;: f&#039;\/api\/resources\/{resource_id}&#039;,<br \/>\n                    &#039;status&#039;: 404,<br \/>\n                    &#039;message&#039;: &#039;Not Found&#039;,<br \/>\n                    &#039;error&#039;: f&#039;Resource {resource_id} not found&#039;<br \/>\n                })<\/p>\n<p>        except Exception as e:<br \/>\n            responses.append({<br \/>\n                &#039;href&#039;: f&#039;\/api\/resources\/{resource_id}&#039;,<br \/>\n                &#039;status&#039;: 500,<br \/>\n                &#039;message&#039;: &#039;Internal Server Error&#039;,<br \/>\n                &#039;error&#039;: str(e)<br \/>\n            })<\/p>\n<p>    # \u8fd4\u56de207\u54cd\u5e94<br \/>\n    return multistatus.create_response(responses)<\/p>\n<p>def delete_resource(resource_id):<br \/>\n    &#034;&#034;&#034;\u5220\u9664\u8d44\u6e90&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n    # \u5b9e\u9645\u4e2d\u4f1a\u4ece\u6570\u636e\u5e93\u5220\u9664<br \/>\n    print(f&#034;Deleting resource: {resource_id}&#034;)<br \/>\n    return True  # \u6a21\u62df\u6210\u529f<\/p>\n<p># \u6279\u91cf\u521b\u5efa\u5b9e\u73b0<br \/>\n&#064;app.route(&#039;\/api\/resources\/batch-create&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef batch_create():<br \/>\n    &#034;&#034;&#034;<br \/>\n    \u6279\u91cf\u521b\u5efa\u8d44\u6e90<\/p>\n<p>    \u8fd4\u56de207 Multi-Status\u54cd\u5e94&#xff0c;\u5305\u542b\u6bcf\u4e2a\u8d44\u6e90\u7684\u521b\u5efa\u7ed3\u679c<br \/>\n    &#034;&#034;&#034;<\/p>\n<p>    resources &#061; request.json.get(&#039;resources&#039;, [])<\/p>\n<p>    if not resources:<br \/>\n        return jsonify({&#039;error&#039;: &#039;No resources specified&#039;}), 400<\/p>\n<p>    responses &#061; []<\/p>\n<p>    for resource_data in resources:<br \/>\n        try:<br \/>\n            # \u9a8c\u8bc1\u8d44\u6e90\u6570\u636e<br \/>\n            validation_result &#061; validate_resource(resource_data)<\/p>\n<p>            if not validation_result[&#039;valid&#039;]:<br \/>\n                responses.append({<br \/>\n                    &#039;href&#039;: &#039;\/api\/resources&#039;,<br \/>\n                    &#039;status&#039;: 400,<br \/>\n                    &#039;message&#039;: &#039;Bad Request&#039;,<br \/>\n                    &#039;error&#039;: validation_result[&#039;errors&#039;]<br \/>\n                })<br \/>\n                continue<\/p>\n<p>            # \u521b\u5efa\u8d44\u6e90<br \/>\n            created_resource &#061; create_resource(resource_data)<\/p>\n<p>            responses.append({<br \/>\n                &#039;href&#039;: f&#039;\/api\/resources\/{created_resource[&#034;id&#034;]}&#039;,<br \/>\n                &#039;status&#039;: 201,<br \/>\n                &#039;message&#039;: &#039;Created&#039;,<br \/>\n                &#039;props&#039;: created_resource<br \/>\n            })<\/p>\n<p>        except Exception as e:<br \/>\n            responses.append({<br \/>\n                &#039;href&#039;: &#039;\/api\/resources&#039;,<br \/>\n                &#039;status&#039;: 500,<br \/>\n                &#039;message&#039;: &#039;Internal Server Error&#039;,<br \/>\n                &#039;error&#039;: str(e)<br \/>\n            })<\/p>\n<p>    # \u8fd4\u56de207\u54cd\u5e94<br \/>\n    return multistatus.create_response(responses)<\/p>\n<p>def validate_resource(data):<br \/>\n    &#034;&#034;&#034;\u9a8c\u8bc1\u8d44\u6e90\u6570\u636e&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n    errors &#061; []<\/p>\n<p>    if not data.get(&#039;name&#039;):<br \/>\n        errors.append(&#039;Name is required&#039;)<\/p>\n<p>    return {<br \/>\n        &#039;valid&#039;: len(errors) &#061;&#061; 0,<br \/>\n        &#039;errors&#039;: errors<br \/>\n    }<\/p>\n<p>def create_resource(data):<br \/>\n    &#034;&#034;&#034;\u521b\u5efa\u8d44\u6e90&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n    import uuid<br \/>\n    resource_id &#061; str(uuid.uuid4())[:8]<\/p>\n<p>    return {<br \/>\n        &#039;id&#039;: resource_id,<br \/>\n        &#039;name&#039;: data.get(&#039;name&#039;),<br \/>\n        &#039;created_at&#039;: datetime.utcnow().isoformat(),<br \/>\n        &#039;status&#039;: &#039;active&#039;<br \/>\n    }<\/p>\n<p># \u6279\u91cf\u66f4\u65b0\u5b9e\u73b0<br \/>\n&#064;app.route(&#039;\/api\/resources\/batch-update&#039;, methods&#061;[&#039;PATCH&#039;])<br \/>\ndef batch_update():<br \/>\n    &#034;&#034;&#034;<br \/>\n    \u6279\u91cf\u66f4\u65b0\u8d44\u6e90<\/p>\n<p>    \u4f7f\u7528JSON Patch\u683c\u5f0f\u66f4\u65b0\u591a\u4e2a\u8d44\u6e90<br \/>\n    &#034;&#034;&#034;<\/p>\n<p>    updates &#061; request.json.get(&#039;updates&#039;, [])<\/p>\n<p>    if not updates:<br \/>\n        return jsonify({&#039;error&#039;: &#039;No updates specified&#039;}), 400<\/p>\n<p>    responses &#061; []<\/p>\n<p>    for update in updates:<br \/>\n        resource_id &#061; update.get(&#039;id&#039;)<br \/>\n        patch_operations &#061; update.get(&#039;patch&#039;, [])<\/p>\n<p>        if not resource_id or not patch_operations:<br \/>\n            responses.append({<br \/>\n                &#039;href&#039;: &#039;\/api\/resources&#039;,<br \/>\n                &#039;status&#039;: 400,<br \/>\n                &#039;message&#039;: &#039;Bad Request&#039;,<br \/>\n                &#039;error&#039;: &#039;Missing id or patch operations&#039;<br \/>\n            })<br \/>\n            continue<\/p>\n<p>        try:<br \/>\n            # \u5e94\u7528JSON Patch<br \/>\n            updated_resource &#061; apply_patch(resource_id, patch_operations)<\/p>\n<p>            responses.append({<br \/>\n                &#039;href&#039;: f&#039;\/api\/resources\/{resource_id}&#039;,<br \/>\n                &#039;status&#039;: 200,<br \/>\n                &#039;message&#039;: &#039;OK&#039;,<br \/>\n                &#039;props&#039;: {<br \/>\n                    &#039;id&#039;: resource_id,<br \/>\n                    &#039;updated&#039;: True,<br \/>\n                    &#039;updated_at&#039;: datetime.utcnow().isoformat()<br \/>\n                }<br \/>\n            })<\/p>\n<p>        except ResourceNotFoundError:<br \/>\n            responses.append({<br \/>\n                &#039;href&#039;: f&#039;\/api\/resources\/{resource_id}&#039;,<br \/>\n                &#039;status&#039;: 404,<br \/>\n                &#039;message&#039;: &#039;Not Found&#039;,<br \/>\n                &#039;error&#039;: f&#039;Resource {resource_id} not found&#039;<br \/>\n            })<\/p>\n<p>        except PatchError as e:<br \/>\n            responses.append({<br \/>\n                &#039;href&#039;: f&#039;\/api\/resources\/{resource_id}&#039;,<br \/>\n                &#039;status&#039;: 422,<br \/>\n                &#039;message&#039;: &#039;Unprocessable Entity&#039;,<br \/>\n                &#039;error&#039;: str(e)<br \/>\n            })<\/p>\n<p>        except Exception as e:<br \/>\n            responses.append({<br \/>\n                &#039;href&#039;: f&#039;\/api\/resources\/{resource_id}&#039;,<br \/>\n                &#039;status&#039;: 500,<br \/>\n                &#039;message&#039;: &#039;Internal Server Error&#039;,<br \/>\n                &#039;error&#039;: str(e)<br \/>\n            })<\/p>\n<p>    return multistatus.create_response(responses)<\/p>\n<p>class ResourceNotFoundError(Exception):<br \/>\n    pass<\/p>\n<p>class PatchError(Exception):<br \/>\n    pass<\/p>\n<p>def apply_patch(resource_id, patch_operations):<br \/>\n    &#034;&#034;&#034;\u5e94\u7528JSON Patch&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n    # \u5b9e\u9645\u4e2d\u4f1a\u66f4\u65b0\u6570\u636e\u5e93<br \/>\n    print(f&#034;Applying patch to {resource_id}: {patch_operations}&#034;)<\/p>\n<p>    # \u6a21\u62df\u9a8c\u8bc1<br \/>\n    for op in patch_operations:<br \/>\n        if op.get(&#039;op&#039;) not in [&#039;add&#039;, &#039;remove&#039;, &#039;replace&#039;, &#039;move&#039;, &#039;copy&#039;]:<br \/>\n            raise PatchError(f&#039;Invalid operation: {op.get(&#034;op&#034;)}&#039;)<\/p>\n<p>    return True<\/p>\n<p>if __name__ &#061;&#061; &#039;__main__&#039;:<br \/>\n    app.run(debug&#061;True, port&#061;5003)<\/p>\n<h4>9.3.3 \u5ba2\u6237\u7aef\u5904\u7406<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5ba2\u6237\u7aef\u5904\u7406207\u54cd\u5e94\u7684\u793a\u4f8b<br \/>\nclass MultiStatusResponseHandler {<br \/>\n    \/**<br \/>\n     * \u5904\u7406207 Multi-Status\u54cd\u5e94<br \/>\n     *\/<\/p>\n<p>    constructor() {<br \/>\n        this.parser &#061; new DOMParser();<br \/>\n    }<\/p>\n<p>    async handle207Response(response) {<br \/>\n        \/\/ \u68c0\u67e5\u5185\u5bb9\u7c7b\u578b<br \/>\n        const contentType &#061; response.headers.get(&#039;Content-Type&#039;);<br \/>\n        if (!contentType.includes(&#039;application\/xml&#039;)) {<br \/>\n            throw new Error(&#039;Expected XML response for 207 status&#039;);<br \/>\n        }<\/p>\n<p>        \/\/ \u83b7\u53d6XML\u6587\u672c<br \/>\n        const xmlText &#061; await response.text();<\/p>\n<p>        \/\/ \u89e3\u6790XML<br \/>\n        const xmlDoc &#061; this.parser.parseFromString(xmlText, &#039;application\/xml&#039;);<\/p>\n<p>        \/\/ \u68c0\u67e5\u89e3\u6790\u9519\u8bef<br \/>\n        const parserError &#061; xmlDoc.querySelector(&#039;parsererror&#039;);<br \/>\n        if (parserError) {<br \/>\n            throw new Error(&#039;Failed to parse XML response&#039;);<br \/>\n        }<\/p>\n<p>        \/\/ \u63d0\u53d6\u6240\u6709\u54cd\u5e94<br \/>\n        const responses &#061; this.extractResponses(xmlDoc);<\/p>\n<p>        \/\/ \u5206\u6790\u54cd\u5e94\u7ed3\u679c<br \/>\n        const analysis &#061; this.analyzeResponses(responses);<\/p>\n<p>        return {<br \/>\n            responses: responses,<br \/>\n            analysis: analysis,<br \/>\n            originalXml: xmlText,<br \/>\n            success: analysis.successCount &gt; 0 || analysis.partialSuccess<br \/>\n        };<br \/>\n    }<\/p>\n<p>    extractResponses(xmlDoc) {<br \/>\n        const responses &#061; [];<\/p>\n<p>        \/\/ \u67e5\u627e\u6240\u6709response\u5143\u7d20<br \/>\n        const responseElements &#061; xmlDoc.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;response&#039;);<\/p>\n<p>        for (const respElem of responseElements) {<br \/>\n            const response &#061; {<br \/>\n                href: this.extractHref(respElem),<br \/>\n                status: this.extractStatus(respElem),<br \/>\n                properties: this.extractProperties(respElem),<br \/>\n                error: this.extractError(respElem)<br \/>\n            };<\/p>\n<p>            responses.push(response);<br \/>\n        }<\/p>\n<p>        return responses;<br \/>\n    }<\/p>\n<p>    extractHref(responseElem) {<br \/>\n        const hrefElem &#061; responseElem.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;href&#039;)[0];<br \/>\n        return hrefElem ? hrefElem.textContent : null;<br \/>\n    }<\/p>\n<p>    extractStatus(responseElem) {<br \/>\n        const statusElem &#061; responseElem.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;status&#039;)[0];<br \/>\n        if (!statusElem) return null;<\/p>\n<p>        const statusText &#061; statusElem.textContent;<br \/>\n        \/\/ \u683c\u5f0f: &#034;HTTP\/1.1 200 OK&#034;<br \/>\n        const match &#061; statusText.match(\/HTTP\\\\\/\\\\d\\\\.\\\\d\\\\s&#043;(\\\\d&#043;)\\\\s&#043;(.&#043;)\/);<\/p>\n<p>        if (match) {<br \/>\n            return {<br \/>\n                code: parseInt(match[1], 10),<br \/>\n                message: match[2],<br \/>\n                raw: statusText<br \/>\n            };<br \/>\n        }<\/p>\n<p>        return null;<br \/>\n    }<\/p>\n<p>    extractProperties(responseElem) {<br \/>\n        const properties &#061; {};<\/p>\n<p>        \/\/ \u67e5\u627epropstat\u5143\u7d20<br \/>\n        const propstatElems &#061; responseElem.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;propstat&#039;);<\/p>\n<p>        for (const propstatElem of propstatElems) {<br \/>\n            const propElem &#061; propstatElem.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;prop&#039;)[0];<br \/>\n            if (!propElem) continue;<\/p>\n<p>            \/\/ \u63d0\u53d6\u6240\u6709\u5c5e\u6027<br \/>\n            for (const child of propElem.children) {<br \/>\n                const tagName &#061; child.tagName;<br \/>\n                \/\/ \u79fb\u9664\u547d\u540d\u7a7a\u95f4<br \/>\n                const simpleName &#061; tagName.includes(&#039;:&#039;) ?<br \/>\n                    tagName.split(&#039;:&#039;)[1] : tagName;<\/p>\n<p>                properties[simpleName] &#061; child.textContent;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return properties;<br \/>\n    }<\/p>\n<p>    extractError(responseElem) {<br \/>\n        const errorElem &#061; responseElem.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;error&#039;)[0];<br \/>\n        if (!errorElem) return null;<\/p>\n<p>        const descElem &#061; errorElem.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;description&#039;)[0];<br \/>\n        return descElem ? descElem.textContent : &#039;Unknown error&#039;;<br \/>\n    }<\/p>\n<p>    analyzeResponses(responses) {<br \/>\n        const analysis &#061; {<br \/>\n            total: responses.length,<br \/>\n            successCount: 0,<br \/>\n            errorCount: 0,<br \/>\n            partialSuccess: false,<br \/>\n            statusCodes: {},<br \/>\n            errors: []<br \/>\n        };<\/p>\n<p>        for (const resp of responses) {<br \/>\n            if (resp.status) {<br \/>\n                const code &#061; resp.status.code;<\/p>\n<p>                \/\/ \u7edf\u8ba1\u72b6\u6001\u7801<br \/>\n                analysis.statusCodes[code] &#061; (analysis.statusCodes[code] || 0) &#043; 1;<\/p>\n<p>                \/\/ \u5224\u65ad\u6210\u529f\/\u5931\u8d25<br \/>\n                if (code &gt;&#061; 200 &amp;&amp; code &lt; 300) {<br \/>\n                    analysis.successCount&#043;&#043;;<br \/>\n                } else {<br \/>\n                    analysis.errorCount&#043;&#043;;<br \/>\n                    if (resp.error) {<br \/>\n                        analysis.errors.push({<br \/>\n                            href: resp.href,<br \/>\n                            error: resp.error,<br \/>\n                            status: resp.status<br \/>\n                        });<br \/>\n                    }<br \/>\n                }<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/\/ \u68c0\u67e5\u662f\u5426\u90e8\u5206\u6210\u529f<br \/>\n        analysis.partialSuccess &#061; analysis.successCount &gt; 0 &amp;&amp;<br \/>\n                                 analysis.errorCount &gt; 0;<\/p>\n<p>        return analysis;<br \/>\n    }<\/p>\n<p>    \/\/ \u6279\u91cf\u64cd\u4f5c\u793a\u4f8b<br \/>\n    async batchDelete(resourceIds) {<br \/>\n        try {<br \/>\n            const response &#061; await fetch(&#039;\/api\/resources\/batch-delete&#039;, {<br \/>\n                method: &#039;POST&#039;,<br \/>\n                headers: {<br \/>\n                    &#039;Content-Type&#039;: &#039;application\/json&#039;<br \/>\n                },<br \/>\n                body: JSON.stringify({<br \/>\n                    resources: resourceIds<br \/>\n                })<br \/>\n            });<\/p>\n<p>            if (response.status &#061;&#061;&#061; 207) {<br \/>\n                const result &#061; await this.handle207Response(response);<\/p>\n<p>                \/\/ \u66f4\u65b0UI<br \/>\n                this.updateUIAfterBatchDelete(result);<\/p>\n<p>                return result;<br \/>\n            } else {<br \/>\n                throw new Error(&#096;Unexpected status: ${response.status}&#096;);<br \/>\n            }<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;Batch delete failed:&#039;, error);<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    updateUIAfterBatchDelete(result) {<br \/>\n        const { analysis, responses } &#061; result;<\/p>\n<p>        \/\/ \u663e\u793a\u7ed3\u679c\u6458\u8981<br \/>\n        const summary &#061; &#096;<br \/>\n            Batch delete completed:<br \/>\n            Total: ${analysis.total}<br \/>\n            Successful: ${analysis.successCount}<br \/>\n            Failed: ${analysis.errorCount}<br \/>\n            ${analysis.partialSuccess ? &#039;(Partial success)&#039; : &#039;&#039;}<br \/>\n        &#096;;<\/p>\n<p>        console.log(summary);<\/p>\n<p>        \/\/ \u66f4\u65b0UI&#xff1a;\u79fb\u9664\u6210\u529f\u7684\u8d44\u6e90<br \/>\n        for (const resp of responses) {<br \/>\n            if (resp.status &amp;&amp; resp.status.code &#061;&#061;&#061; 200) {<br \/>\n                \/\/ \u4eceUI\u4e2d\u79fb\u9664\u8be5\u8d44\u6e90<br \/>\n                this.removeResourceFromUI(resp.href);<br \/>\n            } else if (resp.error) {<br \/>\n                \/\/ \u663e\u793a\u9519\u8bef<br \/>\n                this.showResourceError(resp.href, resp.error);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/\/ \u663e\u793a\u901a\u77e5<br \/>\n        if (analysis.partialSuccess) {<br \/>\n            this.showPartialSuccessNotification(analysis);<br \/>\n        } else if (analysis.successCount &#061;&#061;&#061; analysis.total) {<br \/>\n            this.showSuccessNotification(&#039;All resources deleted successfully&#039;);<br \/>\n        } else {<br \/>\n            this.showErrorNotification(&#039;Some resources failed to delete&#039;);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    removeResourceFromUI(resourceUrl) {<br \/>\n        \/\/ \u4eceURL\u4e2d\u63d0\u53d6ID<br \/>\n        const resourceId &#061; resourceUrl.split(&#039;\/&#039;).pop();<\/p>\n<p>        \/\/ \u4ece\u5217\u8868\u4e2d\u79fb\u9664<br \/>\n        const element &#061; document.querySelector(&#096;[data-resource-id&#061;&#034;${resourceId}&#034;]&#096;);<br \/>\n        if (element) {<br \/>\n            element.style.transition &#061; &#039;opacity 0.3s&#039;;<br \/>\n            element.style.opacity &#061; &#039;0&#039;;<\/p>\n<p>            setTimeout(() &#061;&gt; {<br \/>\n                element.remove();<\/p>\n<p>                \/\/ \u68c0\u67e5\u662f\u5426\u4e3a\u7a7a<br \/>\n                this.checkEmptyState();<br \/>\n            }, 300);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    showResourceError(resourceUrl, error) {<br \/>\n        const resourceId &#061; resourceUrl.split(&#039;\/&#039;).pop();<br \/>\n        const element &#061; document.querySelector(&#096;[data-resource-id&#061;&#034;${resourceId}&#034;]&#096;);<\/p>\n<p>        if (element) {<br \/>\n            const errorDiv &#061; document.createElement(&#039;div&#039;);<br \/>\n            errorDiv.className &#061; &#039;error-message&#039;;<br \/>\n            errorDiv.textContent &#061; &#096;Delete failed: ${error}&#096;;<\/p>\n<p>            element.appendChild(errorDiv);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    showPartialSuccessNotification(analysis) {<br \/>\n        \/\/ \u663e\u793a\u90e8\u5206\u6210\u529f\u901a\u77e5<br \/>\n        const notification &#061; document.createElement(&#039;div&#039;);<br \/>\n        notification.className &#061; &#039;notification warning&#039;;<br \/>\n        notification.innerHTML &#061; &#096;<br \/>\n            &lt;strong&gt;Partial Success&lt;\/strong&gt;<br \/>\n            &lt;p&gt;${analysis.successCount} of ${analysis.total} resources deleted successfully.&lt;\/p&gt;<br \/>\n            &lt;p&gt;${analysis.errorCount} resources failed.&lt;\/p&gt;<br \/>\n        &#096;;<\/p>\n<p>        this.showNotification(notification, 5000);<br \/>\n    }<\/p>\n<p>    showSuccessNotification(message) {<br \/>\n        const notification &#061; document.createElement(&#039;div&#039;);<br \/>\n        notification.className &#061; &#039;notification success&#039;;<br \/>\n        notification.textContent &#061; message;<\/p>\n<p>        this.showNotification(notification, 3000);<br \/>\n    }<\/p>\n<p>    showErrorNotification(message) {<br \/>\n        const notification &#061; document.createElement(&#039;div&#039;);<br \/>\n        notification.className &#061; &#039;notification error&#039;;<br \/>\n        notification.textContent &#061; message;<\/p>\n<p>        this.showNotification(notification, 5000);<br \/>\n    }<\/p>\n<p>    showNotification(element, duration) {<br \/>\n        const container &#061; document.getElementById(&#039;notification-container&#039;) ||<br \/>\n                          this.createNotificationContainer();<\/p>\n<p>        container.appendChild(element);<\/p>\n<p>        setTimeout(() &#061;&gt; {<br \/>\n            element.style.opacity &#061; &#039;0&#039;;<br \/>\n            element.style.transition &#061; &#039;opacity 0.5s&#039;;<\/p>\n<p>            setTimeout(() &#061;&gt; {<br \/>\n                element.remove();<br \/>\n            }, 500);<br \/>\n        }, duration);<br \/>\n    }<\/p>\n<p>    createNotificationContainer() {<br \/>\n        const container &#061; document.createElement(&#039;div&#039;);<br \/>\n        container.id &#061; &#039;notification-container&#039;;<br \/>\n        container.style.cssText &#061; &#096;<br \/>\n            position: fixed;<br \/>\n            top: 20px;<br \/>\n            right: 20px;<br \/>\n            z-index: 1000;<br \/>\n        &#096;;<\/p>\n<p>        document.body.appendChild(container);<br \/>\n        return container;<br \/>\n    }<\/p>\n<p>    checkEmptyState() {<br \/>\n        const resourceList &#061; document.getElementById(&#039;resource-list&#039;);<br \/>\n        if (resourceList &amp;&amp; resourceList.children.length &#061;&#061;&#061; 0) {<br \/>\n            resourceList.innerHTML &#061; &#096;<br \/>\n                &lt;div class&#061;&#034;empty-state&#034;&gt;<br \/>\n                    &lt;h3&gt;No resources found&lt;\/h3&gt;<br \/>\n                    &lt;p&gt;All resources have been deleted.&lt;\/p&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n            &#096;;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/\/ WebDAV PROPFIND\u793a\u4f8b<br \/>\n    async webdavPropfind(url, depth &#061; &#039;1&#039;) {<br \/>\n        try {<br \/>\n            const response &#061; await fetch(url, {<br \/>\n                method: &#039;PROPFIND&#039;,<br \/>\n                headers: {<br \/>\n                    &#039;Depth&#039;: depth,<br \/>\n                    &#039;Content-Type&#039;: &#039;application\/xml&#039;<br \/>\n                }<br \/>\n            });<\/p>\n<p>            if (response.status &#061;&#061;&#061; 207) {<br \/>\n                const result &#061; await this.handle207Response(response);<\/p>\n<p>                \/\/ \u5904\u7406WebDAV\u54cd\u5e94<br \/>\n                this.processWebDAVResponse(result);<\/p>\n<p>                return result;<br \/>\n            } else {<br \/>\n                throw new Error(&#096;Unexpected status: ${response.status}&#096;);<br \/>\n            }<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;PROPFIND failed:&#039;, error);<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    processWebDAVResponse(result) {<br \/>\n        \/\/ \u6784\u5efa\u6587\u4ef6\/\u6587\u4ef6\u5939\u6811<br \/>\n        const tree &#061; this.buildResourceTree(result.responses);<\/p>\n<p>        \/\/ \u663e\u793a\u8d44\u6e90\u5217\u8868<br \/>\n        this.displayResourceTree(tree);<\/p>\n<p>        return tree;<br \/>\n    }<\/p>\n<p>    buildResourceTree(responses) {<br \/>\n        const tree &#061; {<br \/>\n            &#039;\/&#039;: { name: &#039;Root&#039;, type: &#039;collection&#039;, children: [] }<br \/>\n        };<\/p>\n<p>        for (const resp of responses) {<br \/>\n            const href &#061; resp.href;<br \/>\n            const props &#061; resp.properties;<\/p>\n<p>            \/\/ \u89e3\u6790\u8def\u5f84<br \/>\n            const path &#061; href.replace(\/^\\\\\/dav\\\\\/\/, &#039;\/&#039;);<br \/>\n            const parts &#061; path.split(&#039;\/&#039;).filter(p &#061;&gt; p);<\/p>\n<p>            \/\/ \u6784\u5efa\u6811\u8282\u70b9<br \/>\n            let current &#061; tree[&#039;\/&#039;];<\/p>\n<p>            for (let i &#061; 0; i &lt; parts.length; i&#043;&#043;) {<br \/>\n                const part &#061; parts[i];<br \/>\n                const isLast &#061; i &#061;&#061;&#061; parts.length &#8211; 1;<br \/>\n                const fullPath &#061; &#039;\/&#039; &#043; parts.slice(0, i &#043; 1).join(&#039;\/&#039;);<\/p>\n<p>                if (!tree[fullPath]) {<br \/>\n                    tree[fullPath] &#061; {<br \/>\n                        name: part,<br \/>\n                        type: props.resourcetype ? &#039;collection&#039; : &#039;resource&#039;,<br \/>\n                        children: [],<br \/>\n                        properties: isLast ? props : {}<br \/>\n                    };<\/p>\n<p>                    current.children.push(tree[fullPath]);<br \/>\n                }<\/p>\n<p>                current &#061; tree[fullPath];<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return tree;<br \/>\n    }<\/p>\n<p>    displayResourceTree(tree) {<br \/>\n        const container &#061; document.getElementById(&#039;dav-explorer&#039;);<br \/>\n        if (!container) return;<\/p>\n<p>        container.innerHTML &#061; this.renderTree(tree[&#039;\/&#039;]);<br \/>\n    }<\/p>\n<p>    renderTree(node, depth &#061; 0) {<br \/>\n        const indent &#061; &#039;  &#039;.repeat(depth);<br \/>\n        let html &#061; &#096;${indent}&lt;div class&#061;&#034;dav-item&#034; data-type&#061;&#034;${node.type}&#034;&gt;&#096;;<\/p>\n<p>        \/\/ \u56fe\u6807<br \/>\n        const icon &#061; node.type &#061;&#061;&#061; &#039;collection&#039; ? &#039;&#x1f4c1;&#039; : &#039;&#x1f4c4;&#039;;<br \/>\n        html &#043;&#061; &#096;${icon} ${node.name}&#096;;<\/p>\n<p>        \/\/ \u663e\u793a\u5c5e\u6027&#xff08;\u5982\u679c\u5c55\u5f00&#xff09;<br \/>\n        if (node.properties &amp;&amp; Object.keys(node.properties).length &gt; 0) {<br \/>\n            html &#043;&#061; &#096;&lt;div class&#061;&#034;dav-properties&#034; style&#061;&#034;margin-left: 20px; font-size: 0.9em; color: #666;&#034;&gt;&#096;;<br \/>\n            for (const [key, value] of Object.entries(node.properties)) {<br \/>\n                if (value &amp;&amp; value.trim()) {<br \/>\n                    html &#043;&#061; &#096;&lt;div&gt;${key}: ${value}&lt;\/div&gt;&#096;;<br \/>\n                }<br \/>\n            }<br \/>\n            html &#043;&#061; &#096;&lt;\/div&gt;&#096;;<br \/>\n        }<\/p>\n<p>        \/\/ \u5b50\u8282\u70b9<br \/>\n        if (node.children.length &gt; 0) {<br \/>\n            html &#043;&#061; &#096;&lt;div class&#061;&#034;dav-children&#034;&gt;&#096;;<br \/>\n            for (const child of node.children) {<br \/>\n                html &#043;&#061; this.renderTree(child, depth &#043; 1);<br \/>\n            }<br \/>\n            html &#043;&#061; &#096;&lt;\/div&gt;&#096;;<br \/>\n        }<\/p>\n<p>        html &#043;&#061; &#096;&lt;\/div&gt;&#096;;<br \/>\n        return html;<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\ndocument.addEventListener(&#039;DOMContentLoaded&#039;, () &#061;&gt; {<br \/>\n    const handler &#061; new MultiStatusResponseHandler();<\/p>\n<p>    \/\/ \u6279\u91cf\u5220\u9664\u6309\u94ae<br \/>\n    document.getElementById(&#039;batch-delete-btn&#039;).addEventListener(&#039;click&#039;, async () &#061;&gt; {<br \/>\n        \/\/ \u83b7\u53d6\u9009\u4e2d\u7684\u8d44\u6e90ID<br \/>\n        const selectedIds &#061; Array.from(<br \/>\n            document.querySelectorAll(&#039;input[name&#061;&#034;resource&#034;]:checked&#039;)<br \/>\n        ).map(input &#061;&gt; input.value);<\/p>\n<p>        if (selectedIds.length &#061;&#061;&#061; 0) {<br \/>\n            alert(&#039;Please select at least one resource&#039;);<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        if (!confirm(&#096;Delete ${selectedIds.length} resources?&#096;)) {<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        try {<br \/>\n            await handler.batchDelete(selectedIds);<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Batch delete failed:&#039;, error);<br \/>\n            alert(&#039;Failed to delete resources&#039;);<br \/>\n        }<br \/>\n    });<\/p>\n<p>    \/\/ WebDAV\u6d4f\u89c8\u5668<br \/>\n    document.getElementById(&#039;load-dav-btn&#039;).addEventListener(&#039;click&#039;, async () &#061;&gt; {<br \/>\n        const url &#061; document.getElementById(&#039;dav-url&#039;).value;<\/p>\n<p>        if (!url) {<br \/>\n            alert(&#039;Please enter a WebDAV URL&#039;);<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        try {<br \/>\n            await handler.webdavPropfind(url);<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Failed to load WebDAV:&#039;, error);<br \/>\n            alert(&#039;Failed to load WebDAV contents&#039;);<br \/>\n        }<br \/>\n    });<\/p>\n<p>    \/\/ \u6279\u91cf\u521b\u5efa\u793a\u4f8b<br \/>\n    document.getElementById(&#039;batch-create-form&#039;).addEventListener(&#039;submit&#039;, async (e) &#061;&gt; {<br \/>\n        e.preventDefault();<\/p>\n<p>        const resources &#061; [];<br \/>\n        const resourceInputs &#061; document.querySelectorAll(&#039;.resource-input&#039;);<\/p>\n<p>        resourceInputs.forEach(input &#061;&gt; {<br \/>\n            if (input.value.trim()) {<br \/>\n                resources.push({<br \/>\n                    name: input.value.trim(),<br \/>\n                    type: input.dataset.type || &#039;default&#039;<br \/>\n                });<br \/>\n            }<br \/>\n        });<\/p>\n<p>        if (resources.length &#061;&#061;&#061; 0) {<br \/>\n            alert(&#039;Please enter at least one resource&#039;);<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        try {<br \/>\n            const response &#061; await fetch(&#039;\/api\/resources\/batch-create&#039;, {<br \/>\n                method: &#039;POST&#039;,<br \/>\n                headers: {<br \/>\n                    &#039;Content-Type&#039;: &#039;application\/json&#039;<br \/>\n                },<br \/>\n                body: JSON.stringify({ resources })<br \/>\n            });<\/p>\n<p>            if (response.status &#061;&#061;&#061; 207) {<br \/>\n                const result &#061; await handler.handle207Response(response);<\/p>\n<p>                \/\/ \u663e\u793a\u7ed3\u679c<br \/>\n                const successCount &#061; result.analysis.successCount;<br \/>\n                const errorCount &#061; result.analysis.errorCount;<\/p>\n<p>                alert(&#096;Created ${successCount} resources successfully. ${errorCount} failed.&#096;);<\/p>\n<p>                \/\/ \u5237\u65b0\u5217\u8868<br \/>\n                location.reload();<br \/>\n            } else {<br \/>\n                throw new Error(&#096;Unexpected status: ${response.status}&#096;);<br \/>\n            }<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;Batch create failed:&#039;, error);<br \/>\n            alert(&#039;Failed to create resources&#039;);<br \/>\n        }<br \/>\n    });<br \/>\n});<\/p>\n<h3>9.4 208 Already Reported<\/h3>\n<h4>9.4.1 \u8bed\u4e49\u6df1\u5ea6\u89e3\u6790<\/h4>\n<p>\u6839\u636eRFC 5842&#xff08;WebDAV\u7ed1\u5b9a\u6269\u5c55&#xff09;\u7b2c7.1\u8282&#xff0c;208 Already Reported\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;<\/p>\n<p>208 (Already Reported)\u72b6\u6001\u7801\u5728\u54cd\u5e94WebDAV PROPFIND\u8bf7\u6c42\u65f6\u4f7f\u7528&#xff0c;\u7528\u4e8e\u907f\u514d\u91cd\u590d\u679a\u4e3e\u7ed1\u5b9a\u96c6\u5408\u7684\u5185\u90e8\u6210\u5458\u3002\u5f53\u5ba2\u6237\u7aef\u8bf7\u6c42\u4e00\u4e2a\u7ed1\u5b9a\u7684\u96c6\u5408&#xff08;\u5373\u4e00\u4e2a\u8d44\u6e90\u6709\u591a\u4e2aURI&#xff09;\u65f6&#xff0c;\u670d\u52a1\u5668\u53ef\u4ee5\u4f7f\u7528208\u54cd\u5e94\u6765\u6307\u793a\u8be5\u8d44\u6e90\u5df2\u7ecf\u5728\u524d\u9762\u7684\u54cd\u5e94\u4e2d\u62a5\u544a\u8fc7&#xff0c;\u4ece\u800c\u907f\u514d\u5728\u54cd\u5e94\u4e2d\u591a\u6b21\u8fd4\u56de\u540c\u4e00\u4e2a\u8d44\u6e90\u3002<\/p>\n<h5>\u6838\u5fc3\u8bed\u4e49\u7279\u5f81<\/h5>\n<li>\n<p>\u907f\u514d\u91cd\u590d\u679a\u4e3e&#xff1a;\u9632\u6b62\u5728WebDAV\u7ed1\u5b9a\u96c6\u5408\u4e2d\u591a\u6b21\u62a5\u544a\u540c\u4e00\u8d44\u6e90<\/p>\n<\/li>\n<li>\n<p>\u5185\u90e8\u4f7f\u7528&#xff1a;\u5728207 Multi-Status\u54cd\u5e94\u5185\u90e8\u4f7f\u7528<\/p>\n<\/li>\n<li>\n<p>WebDAV\u7ed1\u5b9a&#xff1a;\u4e13\u95e8\u7528\u4e8e\u5904\u7406WebDAV\u7ed1\u5b9a\u7684\u573a\u666f<\/p>\n<\/li>\n<li>\n<p>\u4f18\u5316\u6027\u80fd&#xff1a;\u51cf\u5c11\u54cd\u5e94\u5927\u5c0f&#xff0c;\u907f\u514d\u5ba2\u6237\u7aef\u5904\u7406\u91cd\u590d\u8d44\u6e90<\/p>\n<\/li>\n<h4>9.4.2 \u9002\u7528\u573a\u666f\u4e0e\u5b9e\u73b0<\/h4>\n<p>python<\/p>\n<p># 208 Already Reported\u670d\u52a1\u5668\u5b9e\u73b0<br \/>\nfrom flask import Flask, request, Response<br \/>\nimport xml.etree.ElementTree as ET<br \/>\nfrom xml.dom import minidom<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p>class AlreadyReportedResponse:<br \/>\n    &#034;&#034;&#034;208 Already Reported\u54cd\u5e94\u751f\u6210\u5668&#034;&#034;&#034;<\/p>\n<p>    DAV_NAMESPACE &#061; &#039;DAV:&#039;<br \/>\n    DAV_PREFIX &#061; &#039;d&#039;<\/p>\n<p>    def __init__(self):<br \/>\n        ET.register_namespace(self.DAV_PREFIX, self.DAV_NAMESPACE)<\/p>\n<p>    def create_propfind_response(self, bindings):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u521b\u5efaPROPFIND\u54cd\u5e94&#xff0c;\u4f7f\u7528208\u907f\u514d\u91cd\u590d<\/p>\n<p>        Args:<br \/>\n            bindings: \u7ed1\u5b9a\u5217\u8868&#xff0c;\u6bcf\u4e2a\u5143\u7d20\u662f\u4e00\u4e2a\u5b57\u5178&#xff0c;\u5305\u542b&#xff1a;<br \/>\n                &#8211; href: \u8d44\u6e90URI<br \/>\n                &#8211; bindings: \u8be5\u8d44\u6e90\u7684\u6240\u6709\u7ed1\u5b9aURI\u5217\u8868<br \/>\n                &#8211; props: \u8d44\u6e90\u5c5e\u6027<\/p>\n<p>        Returns:<br \/>\n            207 Multi-Status\u54cd\u5e94&#xff0c;\u5185\u90e8\u53ef\u80fd\u5305\u542b208\u54cd\u5e94<br \/>\n        &#034;&#034;&#034;<br \/>\n        root &#061; ET.Element(f&#039;{{{self.DAV_NAMESPACE}}}multistatus&#039;)<\/p>\n<p>        # \u7528\u4e8e\u8ddf\u8e2a\u5df2\u7ecf\u62a5\u544a\u8fc7\u7684\u8d44\u6e90<br \/>\n        reported_resources &#061; set()<\/p>\n<p>        for binding in bindings:<br \/>\n            # \u83b7\u53d6\u8d44\u6e90\u7684\u89c4\u8303URI&#xff08;\u4f8b\u5982\u7b2c\u4e00\u4e2a\u7ed1\u5b9a&#xff09;<br \/>\n            canonical_href &#061; binding[&#039;bindings&#039;][0]<\/p>\n<p>            # \u5982\u679c\u5df2\u7ecf\u62a5\u544a\u8fc7&#xff0c;\u5219\u8df3\u8fc7<br \/>\n            if canonical_href in reported_resources:<br \/>\n                continue<\/p>\n<p>            # \u6807\u8bb0\u4e3a\u5df2\u62a5\u544a<br \/>\n            reported_resources.add(canonical_href)<\/p>\n<p>            # \u4e3a\u89c4\u8303URI\u521b\u5efa\u54cd\u5e94<br \/>\n            self._add_response(root, canonical_href, binding[&#039;props&#039;], 200)<\/p>\n<p>            # \u4e3a\u5176\u4ed6\u7ed1\u5b9aURI\u521b\u5efa208\u54cd\u5e94<br \/>\n            for alt_href in binding[&#039;bindings&#039;][1:]:<br \/>\n                self._add_response(root, alt_href, {}, 208)<\/p>\n<p>        # \u8f6c\u6362\u4e3aXML<br \/>\n        xml_str &#061; self._prettify_xml(root)<\/p>\n<p>        return Response(<br \/>\n            xml_str,<br \/>\n            status&#061;207,<br \/>\n            content_type&#061;&#039;application\/xml; charset&#061;&#034;utf-8&#034;&#039;<br \/>\n        )<\/p>\n<p>    def _add_response(self, parent, href, props, status_code):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u5355\u4e2aresponse\u5143\u7d20\u5230\u7236\u5143\u7d20&#034;&#034;&#034;<br \/>\n        response_elem &#061; ET.SubElement(parent, f&#039;{{{self.DAV_NAMESPACE}}}response&#039;)<\/p>\n<p>        # href<br \/>\n        href_elem &#061; ET.SubElement(response_elem, f&#039;{{{self.DAV_NAMESPACE}}}href&#039;)<br \/>\n        href_elem.text &#061; href<\/p>\n<p>        # propstat<br \/>\n        if props or status_code !&#061; 208:<br \/>\n            propstat_elem &#061; ET.SubElement(response_elem, f&#039;{{{self.DAV_NAMESPACE}}}propstat&#039;)<\/p>\n<p>            # prop<br \/>\n            prop_elem &#061; ET.SubElement(propstat_elem, f&#039;{{{self.DAV_NAMESPACE}}}prop&#039;)<br \/>\n            for key, value in props.items():<br \/>\n                prop_item &#061; ET.SubElement(prop_elem, f&#039;{{{self.DAV_NAMESPACE}}}{key}&#039;)<br \/>\n                prop_item.text &#061; value<\/p>\n<p>            # status<br \/>\n            status_elem &#061; ET.SubElement(propstat_elem, f&#039;{{{self.DAV_NAMESPACE}}}status&#039;)<br \/>\n            if status_code &#061;&#061; 200:<br \/>\n                status_elem.text &#061; &#039;HTTP\/1.1 200 OK&#039;<br \/>\n            elif status_code &#061;&#061; 208:<br \/>\n                status_elem.text &#061; &#039;HTTP\/1.1 208 Already Reported&#039;<\/p>\n<p>        # \u5bf9\u4e8e208&#xff0c;\u4e5f\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528status\u5143\u7d20&#xff08;\u4e0d\u5728propstat\u5185&#xff09;<br \/>\n        else:<br \/>\n            status_elem &#061; ET.SubElement(response_elem, f&#039;{{{self.DAV_NAMESPACE}}}status&#039;)<br \/>\n            status_elem.text &#061; &#039;HTTP\/1.1 208 Already Reported&#039;<\/p>\n<p>    def _prettify_xml(self, elem):<br \/>\n        &#034;&#034;&#034;\u683c\u5f0f\u5316XML&#034;&#034;&#034;<br \/>\n        rough_string &#061; ET.tostring(elem, &#039;utf-8&#039;)<br \/>\n        parsed &#061; minidom.parseString(rough_string)<br \/>\n        return parsed.toprettyxml(indent&#061;&#039;  &#039;)<\/p>\n<p># \u6a21\u62df\u7ed1\u5b9a\u96c6\u5408\u7684\u6570\u636e<br \/>\ndef get_binding_data():<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u7ed1\u5b9a\u96c6\u5408\u6570\u636e&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<\/p>\n<p>    # \u5047\u8bbe\u6709\u4e24\u4e2a\u8d44\u6e90&#xff0c;\u6bcf\u4e2a\u8d44\u6e90\u6709\u591a\u4e2a\u7ed1\u5b9a&#xff08;URI&#xff09;<br \/>\n    return [<br \/>\n        {<br \/>\n            &#039;bindings&#039;: [<br \/>\n                &#039;\/dav\/files\/doc1&#039;,          # \u89c4\u8303URI<br \/>\n                &#039;\/dav\/aliases\/doc1-alias1&#039;, # \u522b\u540d1<br \/>\n                &#039;\/dav\/aliases\/doc1-alias2&#039;  # \u522b\u540d2<br \/>\n            ],<br \/>\n            &#039;props&#039;: {<br \/>\n                &#039;displayname&#039;: &#039;document1.pdf&#039;,<br \/>\n                &#039;getcontentlength&#039;: &#039;1048576&#039;,<br \/>\n                &#039;getcontenttype&#039;: &#039;application\/pdf&#039;,<br \/>\n                &#039;getlastmodified&#039;: &#039;Wed, 25 Jan 2023 10:30:00 GMT&#039;<br \/>\n            }<br \/>\n        },<br \/>\n        {<br \/>\n            &#039;bindings&#039;: [<br \/>\n                &#039;\/dav\/files\/doc2&#039;,<br \/>\n                &#039;\/dav\/aliases\/doc2-alias1&#039;<br \/>\n            ],<br \/>\n            &#039;props&#039;: {<br \/>\n                &#039;displayname&#039;: &#039;document2.txt&#039;,<br \/>\n                &#039;getcontentlength&#039;: &#039;2048&#039;,<br \/>\n                &#039;getcontenttype&#039;: &#039;text\/plain&#039;,<br \/>\n                &#039;getlastmodified&#039;: &#039;Wed, 25 Jan 2023 11:45:00 GMT&#039;<br \/>\n            }<br \/>\n        },<br \/>\n        {<br \/>\n            &#039;bindings&#039;: [<br \/>\n                &#039;\/dav\/files\/doc3&#039;<br \/>\n            ],<br \/>\n            &#039;props&#039;: {<br \/>\n                &#039;displayname&#039;: &#039;document3.jpg&#039;,<br \/>\n                &#039;getcontentlength&#039;: &#039;524288&#039;,<br \/>\n                &#039;getcontenttype&#039;: &#039;image\/jpeg&#039;,<br \/>\n                &#039;getlastmodified&#039;: &#039;Wed, 25 Jan 2023 12:00:00 GMT&#039;<br \/>\n            }<br \/>\n        }<br \/>\n    ]<\/p>\n<p># WebDAV PROPFIND\u7aef\u70b9<br \/>\n&#064;app.route(&#039;\/dav\/&lt;path:path&gt;&#039;, methods&#061;[&#039;PROPFIND&#039;])<br \/>\ndef webdav_propfind_with_bindings(path):<br \/>\n    &#034;&#034;&#034;\u5904\u7406PROPFIND\u8bf7\u6c42&#xff0c;\u652f\u6301\u7ed1\u5b9a\u96c6\u5408&#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u7ed1\u5b9a\u6570\u636e<br \/>\n    bindings &#061; get_binding_data()<\/p>\n<p>    # \u521b\u5efa\u54cd\u5e94<br \/>\n    response_builder &#061; AlreadyReportedResponse()<br \/>\n    response &#061; response_builder.create_propfind_response(bindings)<\/p>\n<p>    return response<\/p>\n<p>if __name__ &#061;&#061; &#039;__main__&#039;:<br \/>\n    app.run(debug&#061;True, port&#061;5004)<\/p>\n<h4>9.4.3 \u5ba2\u6237\u7aef\u5904\u7406<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5ba2\u6237\u7aef\u5904\u7406208\u54cd\u5e94\u7684\u793a\u4f8b<br \/>\nclass AlreadyReportedHandler {<br \/>\n    \/**<br \/>\n     * \u5904\u7406WebDAV\u54cd\u5e94\u4e2d\u7684208 Already Reported\u72b6\u6001<br \/>\n     *\/<\/p>\n<p>    constructor() {<br \/>\n        this.parser &#061; new DOMParser();<br \/>\n    }<\/p>\n<p>    async processPropfindResponse(url) {<br \/>\n        try {<br \/>\n            const response &#061; await fetch(url, {<br \/>\n                method: &#039;PROPFIND&#039;,<br \/>\n                headers: {<br \/>\n                    &#039;Depth&#039;: &#039;1&#039;,<br \/>\n                    &#039;Content-Type&#039;: &#039;application\/xml&#039;<br \/>\n                }<br \/>\n            });<\/p>\n<p>            if (response.status &#061;&#061;&#061; 207) {<br \/>\n                const xmlText &#061; await response.text();<br \/>\n                const xmlDoc &#061; this.parser.parseFromString(xmlText, &#039;application\/xml&#039;);<\/p>\n<p>                \/\/ \u63d0\u53d6\u6240\u6709\u54cd\u5e94<br \/>\n                const responses &#061; this.extractResponses(xmlDoc);<\/p>\n<p>                \/\/ \u8fc7\u6ee4\u6389208\u54cd\u5e94&#xff08;\u5b83\u4eec\u53ea\u662f\u6307\u793a\u91cd\u590d&#xff0c;\u4e0d\u5305\u542b\u65b0\u4fe1\u606f&#xff09;<br \/>\n                const uniqueResources &#061; this.filterDuplicateResources(responses);<\/p>\n<p>                \/\/ \u663e\u793a\u8d44\u6e90<br \/>\n                this.displayResources(uniqueResources);<\/p>\n<p>                return uniqueResources;<br \/>\n            } else {<br \/>\n                throw new Error(&#096;Unexpected status: ${response.status}&#096;);<br \/>\n            }<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;PROPFIND failed:&#039;, error);<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    extractResponses(xmlDoc) {<br \/>\n        const responses &#061; [];<br \/>\n        const responseElements &#061; xmlDoc.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;response&#039;);<\/p>\n<p>        for (const respElem of responseElements) {<br \/>\n            const href &#061; this.getHref(respElem);<br \/>\n            const status &#061; this.getStatus(respElem);<br \/>\n            const props &#061; this.getProperties(respElem);<\/p>\n<p>            responses.push({<br \/>\n                href: href,<br \/>\n                status: status,<br \/>\n                properties: props<br \/>\n            });<br \/>\n        }<\/p>\n<p>        return responses;<br \/>\n    }<\/p>\n<p>    getHref(responseElem) {<br \/>\n        const hrefElem &#061; responseElem.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;href&#039;)[0];<br \/>\n        return hrefElem ? hrefElem.textContent : null;<br \/>\n    }<\/p>\n<p>    getStatus(responseElem) {<br \/>\n        \/\/ \u67e5\u627estatus\u5143\u7d20<br \/>\n        const statusElems &#061; responseElem.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;status&#039;);<br \/>\n        if (statusElems.length &#061;&#061;&#061; 0) return null;<\/p>\n<p>        const statusText &#061; statusElems[0].textContent;<br \/>\n        const match &#061; statusText.match(\/HTTP\\\\\/\\\\d\\\\.\\\\d\\\\s&#043;(\\\\d&#043;)\\\\s&#043;(.&#043;)\/);<\/p>\n<p>        if (match) {<br \/>\n            return {<br \/>\n                code: parseInt(match[1], 10),<br \/>\n                message: match[2]<br \/>\n            };<br \/>\n        }<\/p>\n<p>        return null;<br \/>\n    }<\/p>\n<p>    getProperties(responseElem) {<br \/>\n        const properties &#061; {};<br \/>\n        const propstatElems &#061; responseElem.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;propstat&#039;);<\/p>\n<p>        for (const propstatElem of propstatElems) {<br \/>\n            const propElem &#061; propstatElem.getElementsByTagNameNS(&#039;DAV:&#039;, &#039;prop&#039;)[0];<br \/>\n            if (!propElem) continue;<\/p>\n<p>            for (const child of propElem.children) {<br \/>\n                const tagName &#061; child.tagName;<br \/>\n                const simpleName &#061; tagName.includes(&#039;:&#039;) ?<br \/>\n                    tagName.split(&#039;:&#039;)[1] : tagName;<\/p>\n<p>                properties[simpleName] &#061; child.textContent;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return properties;<br \/>\n    }<\/p>\n<p>    filterDuplicateResources(responses) {<br \/>\n        \/\/ \u7528\u4e8e\u8ddf\u8e2a\u5df2\u7ecf\u770b\u5230\u7684\u8d44\u6e90&#xff08;\u57fa\u4e8e\u5c5e\u6027&#xff09;<br \/>\n        const seenResources &#061; new Map();<br \/>\n        const uniqueResources &#061; [];<\/p>\n<p>        for (const resp of responses) {<br \/>\n            \/\/ \u8df3\u8fc7208\u54cd\u5e94<br \/>\n            if (resp.status &amp;&amp; resp.status.code &#061;&#061;&#061; 208) {<br \/>\n                console.log(&#096;Skipping already reported resource: ${resp.href}&#096;);<br \/>\n                continue;<br \/>\n            }<\/p>\n<p>            \/\/ \u751f\u6210\u8d44\u6e90\u6807\u8bc6\u7b26&#xff08;\u57fa\u4e8e\u5c5e\u6027&#xff0c;\u800c\u4e0d\u662fhref&#xff09;<br \/>\n            const resourceId &#061; this.generateResourceId(resp.properties);<\/p>\n<p>            if (resourceId &amp;&amp; seenResources.has(resourceId)) {<br \/>\n                console.log(&#096;Duplicate resource detected: ${resp.href}&#096;);<br \/>\n                \/\/ \u53ef\u4ee5\u9009\u62e9\u4fdd\u7559\u4e00\u4e2a&#xff08;\u4f8b\u5982&#xff0c;\u89c4\u8303URI&#xff09;<br \/>\n                continue;<br \/>\n            }<\/p>\n<p>            if (resourceId) {<br \/>\n                seenResources.set(resourceId, resp);<br \/>\n            }<\/p>\n<p>            uniqueResources.push(resp);<br \/>\n        }<\/p>\n<p>        console.log(&#096;Found ${uniqueResources.length} unique resources (${responses.length &#8211; uniqueResources.length} duplicates skipped)&#096;);<br \/>\n        return uniqueResources;<br \/>\n    }<\/p>\n<p>    generateResourceId(properties) {<br \/>\n        \/\/ \u4f7f\u7528\u5173\u952e\u5c5e\u6027\u751f\u6210\u8d44\u6e90ID<br \/>\n        \/\/ \u5728\u5b9e\u9645\u5e94\u7528\u4e2d&#xff0c;\u53ef\u80fd\u9700\u8981\u6839\u636e\u670d\u52a1\u5668\u884c\u4e3a\u8c03\u6574<br \/>\n        const keys &#061; [&#039;displayname&#039;, &#039;getcontentlength&#039;, &#039;getcontenttype&#039;, &#039;getlastmodified&#039;];<br \/>\n        const values &#061; keys.map(key &#061;&gt; properties[key] || &#039;&#039;).join(&#039;|&#039;);<\/p>\n<p>        if (values.trim()) {<br \/>\n            return btoa(values); \/\/ \u7b80\u5355\u7f16\u7801<br \/>\n        }<\/p>\n<p>        return null;<br \/>\n    }<\/p>\n<p>    displayResources(resources) {<br \/>\n        const container &#061; document.getElementById(&#039;dav-resources&#039;);<br \/>\n        if (!container) return;<\/p>\n<p>        container.innerHTML &#061; &#039;&#039;;<\/p>\n<p>        resources.forEach(resource &#061;&gt; {<br \/>\n            const resourceElem &#061; document.createElement(&#039;div&#039;);<br \/>\n            resourceElem.className &#061; &#039;dav-resource&#039;;<\/p>\n<p>            const name &#061; resource.properties.displayname || resource.href.split(&#039;\/&#039;).pop();<br \/>\n            const type &#061; resource.properties.getcontenttype || &#039;unknown&#039;;<br \/>\n            const size &#061; resource.properties.getcontentlength || &#039;0&#039;;<\/p>\n<p>            resourceElem.innerHTML &#061; &#096;<br \/>\n                &lt;h3&gt;${name}&lt;\/h3&gt;<br \/>\n                &lt;p&gt;&lt;strong&gt;URI:&lt;\/strong&gt; ${resource.href}&lt;\/p&gt;<br \/>\n                &lt;p&gt;&lt;strong&gt;Type:&lt;\/strong&gt; ${type}&lt;\/p&gt;<br \/>\n                &lt;p&gt;&lt;strong&gt;Size:&lt;\/strong&gt; ${this.formatBytes(size)}&lt;\/p&gt;<br \/>\n                &lt;p&gt;&lt;strong&gt;Modified:&lt;\/strong&gt; ${resource.properties.getlastmodified || &#039;unknown&#039;}&lt;\/p&gt;<br \/>\n            &#096;;<\/p>\n<p>            container.appendChild(resourceElem);<br \/>\n        });<br \/>\n    }<\/p>\n<p>    formatBytes(bytes) {<br \/>\n        bytes &#061; parseInt(bytes, 10);<br \/>\n        if (bytes &#061;&#061;&#061; 0) return &#039;0 Bytes&#039;;<\/p>\n<p>        const k &#061; 1024;<br \/>\n        const sizes &#061; [&#039;Bytes&#039;, &#039;KB&#039;, &#039;MB&#039;, &#039;GB&#039;];<br \/>\n        const i &#061; Math.floor(Math.log(bytes) \/ Math.log(k));<\/p>\n<p>        return parseFloat((bytes \/ Math.pow(k, i)).toFixed(2)) &#043; &#039; &#039; &#043; sizes[i];<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\ndocument.addEventListener(&#039;DOMContentLoaded&#039;, () &#061;&gt; {<br \/>\n    const handler &#061; new AlreadyReportedHandler();<\/p>\n<p>    document.getElementById(&#039;load-dav-bindings&#039;).addEventListener(&#039;click&#039;, async () &#061;&gt; {<br \/>\n        const url &#061; document.getElementById(&#039;dav-url&#039;).value;<\/p>\n<p>        if (!url) {<br \/>\n            alert(&#039;Please enter a WebDAV URL&#039;);<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        try {<br \/>\n            await handler.processPropfindResponse(url);<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Failed to load DAV bindings:&#039;, error);<br \/>\n            alert(&#039;Failed to load resources&#039;);<br \/>\n        }<br \/>\n    });<br \/>\n});<\/p>\n<h3>9.5 226 IM Used<\/h3>\n<h4>9.5.1 \u8bed\u4e49\u6df1\u5ea6\u89e3\u6790<\/h4>\n<p>\u6839\u636eRFC 3229&#xff08;\u589e\u91cf\u7f16\u7801&#xff09;\u7b2c10.4.1\u8282&#xff0c;226 IM Used\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;<\/p>\n<p>226 (IM Used)\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u5df2\u6210\u529f\u5b8c\u6210\u5bf9\u8d44\u6e90\u7684\u8bf7\u6c42&#xff0c;\u5e76\u4e14\u54cd\u5e94\u662f\u5b9e\u4f8b\u64cd\u4f5c&#xff08;instance manipulation&#xff09;\u5e94\u7528\u4e8e\u5f53\u524d\u5b9e\u4f8b\u540e\u7684\u8868\u793a\u3002\u5b9e\u4f8b\u64cd\u4f5c\u7684\u793a\u4f8b\u5305\u62ec\u5dee\u5206\u7f16\u7801&#xff08;delta encoding&#xff09;\u3001\u538b\u7f29\u6216\u8f6c\u6362\u3002<\/p>\n<h5>\u6838\u5fc3\u8bed\u4e49\u7279\u5f81<\/h5>\n<li>\n<p>\u5b9e\u4f8b\u64cd\u4f5c\u5e94\u7528&#xff1a;\u54cd\u5e94\u4f53\u662f\u7ecf\u8fc7\u8f6c\u6362\u6216\u7f16\u7801\u7684<\/p>\n<\/li>\n<li>\n<p>\u589e\u91cf\u7f16\u7801&#xff1a;\u5e38\u7528\u4e8e\u652f\u6301RFC 3229\u7684\u589e\u91cf\u7f16\u7801<\/p>\n<\/li>\n<li>\n<p>\u5e26\u5bbd\u4f18\u5316&#xff1a;\u901a\u8fc7\u53ea\u53d1\u9001\u53d8\u5316\u90e8\u5206\u6765\u51cf\u5c11\u6570\u636e\u4f20\u8f93<\/p>\n<\/li>\n<li>\n<p>\u6761\u4ef6\u8bf7\u6c42&#xff1a;\u901a\u5e38\u4e0eDelta\u7f16\u7801\u548cIf-None-Match\u7b49\u5934\u90e8\u914d\u5408\u4f7f\u7528<\/p>\n<\/li>\n<h4>9.5.2 \u9002\u7528\u573a\u666f\u4e0e\u5b9e\u73b0<\/h4>\n<p>python<\/p>\n<p># 226 IM Used\u670d\u52a1\u5668\u5b9e\u73b0<br \/>\nfrom flask import Flask, request, Response<br \/>\nimport json<br \/>\nimport hashlib<br \/>\nfrom datetime import datetime<br \/>\nfrom typing import Dict, Any, Optional<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p>class DeltaEncoder:<br \/>\n    &#034;&#034;&#034;\u589e\u91cf\u7f16\u7801\u5668&#xff0c;\u7528\u4e8e\u751f\u6210226\u54cd\u5e94&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.resource_versions &#061; {}<\/p>\n<p>    def get_delta_response(self, resource_id: str, client_etag: Optional[str]) -&gt; Dict[str, Any]:<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u83b7\u53d6\u589e\u91cf\u54cd\u5e94<\/p>\n<p>        Args:<br \/>\n            resource_id: \u8d44\u6e90ID<br \/>\n            client_etag: \u5ba2\u6237\u7aef\u5f53\u524d\u7248\u672c\u7684ETag<\/p>\n<p>        Returns:<br \/>\n            \u54cd\u5e94\u6570\u636e\u548c\u72b6\u6001\u7801<br \/>\n        &#034;&#034;&#034;<br \/>\n        # \u83b7\u53d6\u5f53\u524d\u8d44\u6e90\u72b6\u6001<br \/>\n        current_version &#061; self.get_current_version(resource_id)<br \/>\n        current_etag &#061; self.generate_etag(current_version)<\/p>\n<p>        # \u5982\u679c\u5ba2\u6237\u7aef\u6ca1\u6709\u63d0\u4f9bETag&#xff0c;\u8fd4\u56de\u5b8c\u6574\u8d44\u6e90<br \/>\n        if not client_etag:<br \/>\n            return {<br \/>\n                &#039;status&#039;: 200,<br \/>\n                &#039;data&#039;: current_version,<br \/>\n                &#039;headers&#039;: {<br \/>\n                    &#039;ETag&#039;: current_etag,<br \/>\n                    &#039;IM&#039;: None  # \u6ca1\u6709\u4f7f\u7528\u5b9e\u4f8b\u64cd\u4f5c<br \/>\n                }<br \/>\n            }<\/p>\n<p>        # \u5982\u679c\u5ba2\u6237\u7aefETag\u4e0e\u5f53\u524dETag\u76f8\u540c&#xff0c;\u8fd4\u56de304<br \/>\n        if client_etag &#061;&#061; current_etag:<br \/>\n            return {<br \/>\n                &#039;status&#039;: 304,<br \/>\n                &#039;data&#039;: None,<br \/>\n                &#039;headers&#039;: {&#039;ETag&#039;: current_etag}<br \/>\n            }<\/p>\n<p>        # \u67e5\u627e\u5ba2\u6237\u7aef\u7248\u672c<br \/>\n        client_version &#061; self.find_version_by_etag(resource_id, client_etag)<\/p>\n<p>        if client_version:<br \/>\n            # \u8ba1\u7b97\u589e\u91cf<br \/>\n            delta &#061; self.compute_delta(client_version, current_version)<\/p>\n<p>            # \u5982\u679c\u589e\u91cf\u6bd4\u5b8c\u6574\u8d44\u6e90\u5c0f&#xff0c;\u8fd4\u56de226<br \/>\n            if self.is_delta_smaller(delta, current_version):<br \/>\n                return {<br \/>\n                    &#039;status&#039;: 226,<br \/>\n                    &#039;data&#039;: delta,<br \/>\n                    &#039;headers&#039;: {<br \/>\n                        &#039;ETag&#039;: current_etag,<br \/>\n                        &#039;IM&#039;: &#039;delta&#039;,  # \u4f7f\u7528\u4e86\u589e\u91cf\u7f16\u7801<br \/>\n                        &#039;Delta-Base&#039;: client_etag  # \u57fa\u4e8e\u54ea\u4e2a\u7248\u672c<br \/>\n                    }<br \/>\n                }<\/p>\n<p>        # \u56de\u9000\u5230\u5b8c\u6574\u8d44\u6e90<br \/>\n        return {<br \/>\n            &#039;status&#039;: 200,<br \/>\n            &#039;data&#039;: current_version,<br \/>\n            &#039;headers&#039;: {<br \/>\n                &#039;ETag&#039;: current_etag,<br \/>\n                &#039;IM&#039;: None<br \/>\n            }<br \/>\n        }<\/p>\n<p>    def get_current_version(self, resource_id: str) -&gt; Dict[str, Any]:<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u8d44\u6e90\u7684\u5f53\u524d\u7248\u672c&#034;&#034;&#034;<br \/>\n        # \u6a21\u62df\u6570\u636e &#8211; \u5b9e\u9645\u4e2d\u4ece\u6570\u636e\u5e93\u83b7\u53d6<br \/>\n        if resource_id not in self.resource_versions:<br \/>\n            self.resource_versions[resource_id] &#061; {<br \/>\n                &#039;versions&#039;: [],<br \/>\n                &#039;current_index&#039;: -1<br \/>\n            }<\/p>\n<p>        resource &#061; self.resource_versions[resource_id]<\/p>\n<p>        # \u5982\u679c\u6ca1\u6709\u7248\u672c&#xff0c;\u521b\u5efa\u521d\u59cb\u7248\u672c<br \/>\n        if resource[&#039;current_index&#039;] &lt; 0:<br \/>\n            initial_version &#061; self.create_initial_version(resource_id)<br \/>\n            resource[&#039;versions&#039;].append({<br \/>\n                &#039;data&#039;: initial_version,<br \/>\n                &#039;etag&#039;: self.generate_etag(initial_version),<br \/>\n                &#039;timestamp&#039;: datetime.utcnow().isoformat()<br \/>\n            })<br \/>\n            resource[&#039;current_index&#039;] &#061; 0<\/p>\n<p>        return resource[&#039;versions&#039;][resource[&#039;current_index&#039;]][&#039;data&#039;]<\/p>\n<p>    def create_initial_version(self, resource_id: str) -&gt; Dict[str, Any]:<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u521d\u59cb\u7248\u672c&#034;&#034;&#034;<br \/>\n        return {<br \/>\n            &#039;id&#039;: resource_id,<br \/>\n            &#039;title&#039;: f&#039;Resource {resource_id}&#039;,<br \/>\n            &#039;content&#039;: &#039;Initial content&#039;,<br \/>\n            &#039;version&#039;: 1,<br \/>\n            &#039;created_at&#039;: datetime.utcnow().isoformat(),<br \/>\n            &#039;updated_at&#039;: datetime.utcnow().isoformat()<br \/>\n        }<\/p>\n<p>    def update_resource(self, resource_id: str, updates: Dict[str, Any]) -&gt; Dict[str, Any]:<br \/>\n        &#034;&#034;&#034;\u66f4\u65b0\u8d44\u6e90&#xff0c;\u521b\u5efa\u65b0\u7248\u672c&#034;&#034;&#034;<br \/>\n        if resource_id not in self.resource_versions:<br \/>\n            self.get_current_version(resource_id)  # \u521d\u59cb\u5316<\/p>\n<p>        resource &#061; self.resource_versions[resource_id]<br \/>\n        current &#061; resource[&#039;versions&#039;][resource[&#039;current_index&#039;]][&#039;data&#039;].copy()<\/p>\n<p>        # \u5e94\u7528\u66f4\u65b0<br \/>\n        current.update(updates)<br \/>\n        current[&#039;version&#039;] &#043;&#061; 1<br \/>\n        current[&#039;updated_at&#039;] &#061; datetime.utcnow().isoformat()<\/p>\n<p>        # \u4fdd\u5b58\u65b0\u7248\u672c<br \/>\n        new_version &#061; {<br \/>\n            &#039;data&#039;: current,<br \/>\n            &#039;etag&#039;: self.generate_etag(current),<br \/>\n            &#039;timestamp&#039;: datetime.utcnow().isoformat()<br \/>\n        }<\/p>\n<p>        resource[&#039;versions&#039;].append(new_version)<br \/>\n        resource[&#039;current_index&#039;] &#043;&#061; 1<\/p>\n<p>        # \u9650\u5236\u7248\u672c\u5386\u53f2\u957f\u5ea6<br \/>\n        if len(resource[&#039;versions&#039;]) &gt; 10:<br \/>\n            resource[&#039;versions&#039;] &#061; resource[&#039;versions&#039;][-10:]<br \/>\n            resource[&#039;current_index&#039;] &#061; min(resource[&#039;current_index&#039;], 9)<\/p>\n<p>        return current<\/p>\n<p>    def generate_etag(self, data: Dict[str, Any]) -&gt; str:<br \/>\n        &#034;&#034;&#034;\u751f\u6210ETag&#034;&#034;&#034;<br \/>\n        content &#061; json.dumps(data, sort_keys&#061;True)<br \/>\n        return f&#039;&#034;{hashlib.md5(content.encode()).hexdigest()}&#034;&#039;<\/p>\n<p>    def find_version_by_etag(self, resource_id: str, etag: str) -&gt; Optional[Dict[str, Any]]:<br \/>\n        &#034;&#034;&#034;\u6839\u636eETag\u67e5\u627e\u7248\u672c&#034;&#034;&#034;<br \/>\n        if resource_id not in self.resource_versions:<br \/>\n            return None<\/p>\n<p>        for version in self.resource_versions[resource_id][&#039;versions&#039;]:<br \/>\n            if version[&#039;etag&#039;] &#061;&#061; etag:<br \/>\n                return version[&#039;data&#039;]<\/p>\n<p>        return None<\/p>\n<p>    def compute_delta(self, old_version: Dict[str, Any], new_version: Dict[str, Any]) -&gt; Dict[str, Any]:<br \/>\n        &#034;&#034;&#034;\u8ba1\u7b97\u4e24\u4e2a\u7248\u672c\u4e4b\u95f4\u7684\u5dee\u5f02&#034;&#034;&#034;<br \/>\n        delta &#061; {<br \/>\n            &#039;operation&#039;: &#039;patch&#039;,<br \/>\n            &#039;from_version&#039;: old_version.get(&#039;version&#039;),<br \/>\n            &#039;to_version&#039;: new_version.get(&#039;version&#039;),<br \/>\n            &#039;changes&#039;: []<br \/>\n        }<\/p>\n<p>        # \u7b80\u5355\u6bd4\u8f83&#xff1a;\u627e\u51fa\u53d8\u5316\u7684\u5b57\u6bb5<br \/>\n        all_keys &#061; set(old_version.keys()) | set(new_version.keys())<\/p>\n<p>        for key in all_keys:<br \/>\n            old_value &#061; old_version.get(key)<br \/>\n            new_value &#061; new_version.get(key)<\/p>\n<p>            if old_value !&#061; new_value:<br \/>\n                delta[&#039;changes&#039;].append({<br \/>\n                    &#039;field&#039;: key,<br \/>\n                    &#039;old&#039;: old_value,<br \/>\n                    &#039;new&#039;: new_value,<br \/>\n                    &#039;operation&#039;: &#039;replace&#039; if old_value and new_value else (&#039;add&#039; if new_value else &#039;remove&#039;)<br \/>\n                })<\/p>\n<p>        return delta<\/p>\n<p>    def is_delta_smaller(self, delta: Dict[str, Any], full_data: Dict[str, Any]) -&gt; bool:<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u589e\u91cf\u662f\u5426\u6bd4\u5b8c\u6574\u6570\u636e\u5c0f&#034;&#034;&#034;<br \/>\n        delta_size &#061; len(json.dumps(delta))<br \/>\n        full_size &#061; len(json.dumps(full_data))<\/p>\n<p>        # \u5982\u679c\u589e\u91cf\u5c0f\u4e8e\u5b8c\u6574\u6570\u636e\u768475%&#xff0c;\u5219\u8ba4\u4e3a\u5b83\u66f4\u5c0f<br \/>\n        return delta_size &lt; full_size * 0.75<\/p>\n<p># \u521b\u5efa\u7f16\u7801\u5668\u5b9e\u4f8b<br \/>\ndelta_encoder &#061; DeltaEncoder()<\/p>\n<p># \u652f\u6301\u589e\u91cf\u7f16\u7801\u7684\u8d44\u6e90\u7aef\u70b9<br \/>\n&#064;app.route(&#039;\/api\/delta\/&lt;resource_id&gt;&#039;, methods&#061;[&#039;GET&#039;])<br \/>\ndef get_resource_with_delta(resource_id):<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u8d44\u6e90&#xff0c;\u652f\u6301\u589e\u91cf\u7f16\u7801&#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u5ba2\u6237\u7aef\u7684ETag&#xff08;\u53ef\u80fd\u6765\u81eaIf-None-Match&#xff09;<br \/>\n    client_etag &#061; request.headers.get(&#039;If-None-Match&#039;)<\/p>\n<p>    # \u83b7\u53d6\u54cd\u5e94<br \/>\n    response_info &#061; delta_encoder.get_delta_response(resource_id, client_etag)<\/p>\n<p>    # \u6784\u5efa\u54cd\u5e94<br \/>\n    if response_info[&#039;status&#039;] &#061;&#061; 226:<br \/>\n        # 226 IM Used<br \/>\n        response &#061; jsonify({<br \/>\n            &#039;data&#039;: response_info[&#039;data&#039;],<br \/>\n            &#039;_delta&#039;: True,<br \/>\n            &#039;_base_version&#039;: request.headers.get(&#039;If-None-Match&#039;),<br \/>\n            &#039;_current_etag&#039;: response_info[&#039;headers&#039;][&#039;ETag&#039;]<br \/>\n        })<br \/>\n        response.status_code &#061; 226<\/p>\n<p>        # \u8bbe\u7f6e\u5934\u90e8<br \/>\n        for key, value in response_info[&#039;headers&#039;].items():<br \/>\n            if value is not None:<br \/>\n                response.headers[key] &#061; value<\/p>\n<p>        # \u6dfb\u52a0\u589e\u91cf\u7f16\u7801\u7279\u5b9a\u7684\u5934\u90e8<br \/>\n        response.headers[&#039;Vary&#039;] &#061; &#039;If-None-Match&#039;<br \/>\n        response.headers[&#039;Cache-Control&#039;] &#061; &#039;private, must-revalidate&#039;<\/p>\n<p>        return response<\/p>\n<p>    elif response_info[&#039;status&#039;] &#061;&#061; 304:<br \/>\n        # 304 Not Modified<br \/>\n        response &#061; Response(status&#061;304)<br \/>\n        response.headers[&#039;ETag&#039;] &#061; response_info[&#039;headers&#039;][&#039;ETag&#039;]<br \/>\n        return response<\/p>\n<p>    else:<br \/>\n        # 200 OK &#8211; \u5b8c\u6574\u8d44\u6e90<br \/>\n        response &#061; jsonify(response_info[&#039;data&#039;])<br \/>\n        response.headers[&#039;ETag&#039;] &#061; response_info[&#039;headers&#039;][&#039;ETag&#039;]<br \/>\n        response.headers[&#039;Cache-Control&#039;] &#061; &#039;private, must-revalidate&#039;<br \/>\n        return response<\/p>\n<p># \u66f4\u65b0\u8d44\u6e90\u7aef\u70b9<br \/>\n&#064;app.route(&#039;\/api\/delta\/&lt;resource_id&gt;&#039;, methods&#061;[&#039;PATCH&#039;])<br \/>\ndef update_resource(resource_id):<br \/>\n    &#034;&#034;&#034;\u66f4\u65b0\u8d44\u6e90&#xff0c;\u521b\u5efa\u65b0\u7248\u672c&#034;&#034;&#034;<\/p>\n<p>    updates &#061; request.json<br \/>\n    if not updates:<br \/>\n        return jsonify({&#039;error&#039;: &#039;No updates provided&#039;}), 400<\/p>\n<p>    # \u66f4\u65b0\u8d44\u6e90<br \/>\n    new_version &#061; delta_encoder.update_resource(resource_id, updates)<\/p>\n<p>    # \u8fd4\u56de\u65b0\u7248\u672c<br \/>\n    response &#061; jsonify(new_version)<br \/>\n    response.headers[&#039;ETag&#039;] &#061; delta_encoder.generate_etag(new_version)<br \/>\n    response.headers[&#039;Cache-Control&#039;] &#061; &#039;private, no-cache&#039;<\/p>\n<p>    return response<\/p>\n<p># \u83b7\u53d6\u8d44\u6e90\u7248\u672c\u5386\u53f2<br \/>\n&#064;app.route(&#039;\/api\/delta\/&lt;resource_id&gt;\/history&#039;, methods&#061;[&#039;GET&#039;])<br \/>\ndef get_resource_history(resource_id):<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u8d44\u6e90\u7684\u7248\u672c\u5386\u53f2&#034;&#034;&#034;<\/p>\n<p>    if resource_id not in delta_encoder.resource_versions:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Resource not found&#039;}), 404<\/p>\n<p>    resource &#061; delta_encoder.resource_versions[resource_id]<br \/>\n    history &#061; []<\/p>\n<p>    for i, version in enumerate(resource[&#039;versions&#039;]):<br \/>\n        history.append({<br \/>\n            &#039;version&#039;: version[&#039;data&#039;].get(&#039;version&#039;),<br \/>\n            &#039;etag&#039;: version[&#039;etag&#039;],<br \/>\n            &#039;timestamp&#039;: version[&#039;timestamp&#039;],<br \/>\n            &#039;is_current&#039;: i &#061;&#061; resource[&#039;current_index&#039;]<br \/>\n        })<\/p>\n<p>    return jsonify({<br \/>\n        &#039;resource_id&#039;: resource_id,<br \/>\n        &#039;current_version&#039;: resource[&#039;current_index&#039;] &#043; 1,<br \/>\n        &#039;history&#039;: history<br \/>\n    })<\/p>\n<p>if __name__ &#061;&#061; &#039;__main__&#039;:<br \/>\n    app.run(debug&#061;True, port&#061;5005)<\/p>\n<h4>9.5.3 \u5ba2\u6237\u7aef\u5904\u7406<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5ba2\u6237\u7aef\u5904\u7406226\u54cd\u5e94\u7684\u793a\u4f8b<br \/>\nclass DeltaEncodingClient {<br \/>\n    \/**<br \/>\n     * \u5904\u7406\u589e\u91cf\u7f16\u7801&#xff08;226 IM Used&#xff09;\u7684\u5ba2\u6237\u7aef<br \/>\n     *\/<\/p>\n<p>    constructor(resourceUrl) {<br \/>\n        this.resourceUrl &#061; resourceUrl;<br \/>\n        this.currentData &#061; null;<br \/>\n        this.currentEtag &#061; null;<br \/>\n        this.useDeltaEncoding &#061; true;<br \/>\n    }<\/p>\n<p>    async fetchResource(forceFull &#061; false) {<br \/>\n        try {<br \/>\n            const headers &#061; {};<\/p>\n<p>            \/\/ \u5982\u679c\u5df2\u6709ETag&#xff0c;\u5e76\u4e14\u4e0d\u5f3a\u5236\u83b7\u53d6\u5b8c\u6574\u8d44\u6e90&#xff0c;\u5219\u53d1\u9001If-None-Match<br \/>\n            if (this.currentEtag &amp;&amp; !forceFull &amp;&amp; this.useDeltaEncoding) {<br \/>\n                headers[&#039;If-None-Match&#039;] &#061; this.currentEtag;<br \/>\n            }<\/p>\n<p>            const response &#061; await fetch(this.resourceUrl, { headers });<\/p>\n<p>            if (response.status &#061;&#061;&#061; 226) {<br \/>\n                \/\/ \u589e\u91cf\u54cd\u5e94<br \/>\n                return await this.handle226Response(response);<br \/>\n            } else if (response.status &#061;&#061;&#061; 304) {<br \/>\n                \/\/ \u672a\u4fee\u6539<br \/>\n                console.log(&#039;Resource not modified&#039;);<br \/>\n                return this.currentData;<br \/>\n            } else if (response.ok) {<br \/>\n                \/\/ \u5b8c\u6574\u54cd\u5e94<br \/>\n                return await this.handle200Response(response);<br \/>\n            } else {<br \/>\n                throw new Error(&#096;HTTP ${response.status}&#096;);<br \/>\n            }<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Failed to fetch resource:&#039;, error);<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    async handle226Response(response) {<br \/>\n        console.log(&#039;Received 226 IM Used (delta encoded) response&#039;);<\/p>\n<p>        \/\/ \u83b7\u53d6\u589e\u91cf\u6570\u636e<br \/>\n        const deltaData &#061; await response.json();<br \/>\n        const delta &#061; deltaData.data;<br \/>\n        const newEtag &#061; response.headers.get(&#039;ETag&#039;);<br \/>\n        const imHeader &#061; response.headers.get(&#039;IM&#039;);<\/p>\n<p>        \/\/ \u9a8c\u8bc1\u8fd9\u662f\u5426\u771f\u7684\u662f\u589e\u91cf<br \/>\n        if (!delta || !delta._delta) {<br \/>\n            console.warn(&#039;Expected delta response but got full data&#039;);<br \/>\n            this.currentData &#061; deltaData;<br \/>\n            this.currentEtag &#061; newEtag;<br \/>\n            return this.currentData;<br \/>\n        }<\/p>\n<p>        \/\/ \u5e94\u7528\u589e\u91cf<br \/>\n        if (delta.operation &#061;&#061;&#061; &#039;patch&#039; &amp;&amp; Array.isArray(delta.changes)) {<br \/>\n            this.applyPatch(delta);<br \/>\n        } else {<br \/>\n            console.warn(&#039;Unknown delta format:&#039;, delta);<br \/>\n            \/\/ \u56de\u9000\u5230\u5b8c\u6574\u8bf7\u6c42<br \/>\n            return await this.fetchResource(true);<br \/>\n        }<\/p>\n<p>        \/\/ \u66f4\u65b0ETag<br \/>\n        this.currentEtag &#061; newEtag;<\/p>\n<p>        \/\/ \u8bb0\u5f55\u7edf\u8ba1\u4fe1\u606f<br \/>\n        const deltaSize &#061; JSON.stringify(delta).length;<br \/>\n        console.log(&#096;Applied delta: ${delta.changes.length} changes, delta size: ${deltaSize} bytes&#096;);<\/p>\n<p>        \/\/ \u89e6\u53d1\u66f4\u65b0\u4e8b\u4ef6<br \/>\n        this.triggerUpdateEvent(&#039;delta-applied&#039;, {<br \/>\n            changes: delta.changes.length,<br \/>\n            deltaSize,<br \/>\n            newEtag<br \/>\n        });<\/p>\n<p>        return this.currentData;<br \/>\n    }<\/p>\n<p>    applyPatch(delta) {<br \/>\n        if (!this.currentData) {<br \/>\n            console.error(&#039;Cannot apply patch without base data&#039;);<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        \/\/ \u5e94\u7528\u6bcf\u4e2a\u53d8\u66f4<br \/>\n        delta.changes.forEach(change &#061;&gt; {<br \/>\n            switch (change.operation) {<br \/>\n                case &#039;replace&#039;:<br \/>\n                    this.currentData[change.field] &#061; change.new;<br \/>\n                    break;<br \/>\n                case &#039;add&#039;:<br \/>\n                    this.currentData[change.field] &#061; change.new;<br \/>\n                    break;<br \/>\n                case &#039;remove&#039;:<br \/>\n                    delete this.currentData[change.field];<br \/>\n                    break;<br \/>\n                default:<br \/>\n                    console.warn(&#096;Unknown operation: ${change.operation}&#096;);<br \/>\n            }<br \/>\n        });<\/p>\n<p>        \/\/ \u66f4\u65b0\u7248\u672c\u4fe1\u606f<br \/>\n        if (delta.to_version !&#061;&#061; undefined) {<br \/>\n            this.currentData.version &#061; delta.to_version;<br \/>\n        }<\/p>\n<p>        this.currentData.updated_at &#061; new Date().toISOString();<br \/>\n    }<\/p>\n<p>    async handle200Response(response) {<br \/>\n        console.log(&#039;Received 200 OK (full resource) response&#039;);<\/p>\n<p>        this.currentData &#061; await response.json();<br \/>\n        this.currentEtag &#061; response.headers.get(&#039;ETag&#039;);<\/p>\n<p>        \/\/ \u89e6\u53d1\u66f4\u65b0\u4e8b\u4ef6<br \/>\n        this.triggerUpdateEvent(&#039;full-update&#039;, {<br \/>\n            dataSize: JSON.stringify(this.currentData).length,<br \/>\n            etag: this.currentEtag<br \/>\n        });<\/p>\n<p>        return this.currentData;<br \/>\n    }<\/p>\n<p>    triggerUpdateEvent(type, details) {<br \/>\n        const event &#061; new CustomEvent(&#039;resource-updated&#039;, {<br \/>\n            detail: {<br \/>\n                type: type,<br \/>\n                data: this.currentData,<br \/>\n                etag: this.currentEtag,<br \/>\n                timestamp: new Date().toISOString(),<br \/>\n                &#8230;details<br \/>\n            }<br \/>\n        });<\/p>\n<p>        \/\/ \u5728document\u4e0a\u89e6\u53d1\u4e8b\u4ef6&#xff0c;\u4ee5\u4fbf\u5176\u4ed6\u7ec4\u4ef6\u53ef\u4ee5\u76d1\u542c<br \/>\n        document.dispatchEvent(event);<br \/>\n    }<\/p>\n<p>    async updateResource(updates) {<br \/>\n        try {<br \/>\n            const response &#061; await fetch(this.resourceUrl, {<br \/>\n                method: &#039;PATCH&#039;,<br \/>\n                headers: {<br \/>\n                    &#039;Content-Type&#039;: &#039;application\/json&#039;<br \/>\n                },<br \/>\n                body: JSON.stringify(updates)<br \/>\n            });<\/p>\n<p>            if (response.ok) {<br \/>\n                const newData &#061; await response.json();<br \/>\n                const newEtag &#061; response.headers.get(&#039;ETag&#039;);<\/p>\n<p>                this.currentData &#061; newData;<br \/>\n                this.currentEtag &#061; newEtag;<\/p>\n<p>                \/\/ \u89e6\u53d1\u66f4\u65b0\u4e8b\u4ef6<br \/>\n                this.triggerUpdateEvent(&#039;local-update&#039;, {<br \/>\n                    updates,<br \/>\n                    newEtag<br \/>\n                });<\/p>\n<p>                return newData;<br \/>\n            } else {<br \/>\n                throw new Error(&#096;Update failed: HTTP ${response.status}&#096;);<br \/>\n            }<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Failed to update resource:&#039;, error);<br \/>\n            throw error;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/\/ \u624b\u52a8\u8bbe\u7f6e\u6570\u636e&#xff08;\u4f8b\u5982\u4ece\u7f13\u5b58\u52a0\u8f7d&#xff09;<br \/>\n    setData(data, etag) {<br \/>\n        this.currentData &#061; data;<br \/>\n        this.currentEtag &#061; etag;<br \/>\n    }<\/p>\n<p>    \/\/ \u542f\u7528\/\u7981\u7528\u589e\u91cf\u7f16\u7801<br \/>\n    setDeltaEncoding(enabled) {<br \/>\n        this.useDeltaEncoding &#061; enabled;<br \/>\n        console.log(&#096;Delta encoding ${enabled ? &#039;enabled&#039; : &#039;disabled&#039;}&#096;);<br \/>\n    }<\/p>\n<p>    \/\/ \u83b7\u53d6\u5f53\u524d\u72b6\u6001<br \/>\n    getStatus() {<br \/>\n        return {<br \/>\n            hasData: !!this.currentData,<br \/>\n            etag: this.currentEtag,<br \/>\n            usingDelta: this.useDeltaEncoding,<br \/>\n            dataSize: this.currentData ? JSON.stringify(this.currentData).length : 0<br \/>\n        };<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\ndocument.addEventListener(&#039;DOMContentLoaded&#039;, () &#061;&gt; {<br \/>\n    const client &#061; new DeltaEncodingClient(&#039;\/api\/delta\/document-123&#039;);<\/p>\n<p>    \/\/ \u76d1\u542c\u8d44\u6e90\u66f4\u65b0\u4e8b\u4ef6<br \/>\n    document.addEventListener(&#039;resource-updated&#039;, (event) &#061;&gt; {<br \/>\n        console.log(&#039;Resource updated:&#039;, event.detail.type);<\/p>\n<p>        \/\/ \u66f4\u65b0UI<br \/>\n        updateResourceDisplay(event.detail.data);<\/p>\n<p>        \/\/ \u663e\u793a\u901a\u77e5<br \/>\n        showUpdateNotification(event.detail);<br \/>\n    });<\/p>\n<p>    \/\/ \u521d\u59cb\u52a0\u8f7d<br \/>\n    client.fetchResource().then(data &#061;&gt; {<br \/>\n        console.log(&#039;Initial load complete:&#039;, data);<br \/>\n        updateResourceDisplay(data);<br \/>\n    }).catch(error &#061;&gt; {<br \/>\n        console.error(&#039;Initial load failed:&#039;, error);<br \/>\n    });<\/p>\n<p>    \/\/ \u5b9a\u671f\u5237\u65b0&#xff08;\u4f8b\u5982\u6bcf30\u79d2&#xff09;<br \/>\n    setInterval(() &#061;&gt; {<br \/>\n        client.fetchResource().catch(error &#061;&gt; {<br \/>\n            console.error(&#039;Background refresh failed:&#039;, error);<br \/>\n        });<br \/>\n    }, 30000);<\/p>\n<p>    \/\/ \u66f4\u65b0\u6309\u94ae<br \/>\n    document.getElementById(&#039;update-resource&#039;).addEventListener(&#039;click&#039;, async () &#061;&gt; {<br \/>\n        const newTitle &#061; prompt(&#039;Enter new title:&#039;);<br \/>\n        if (newTitle) {<br \/>\n            try {<br \/>\n                await client.updateResource({ title: newTitle });<br \/>\n            } catch (error) {<br \/>\n                alert(&#039;Update failed: &#039; &#043; error.message);<br \/>\n            }<br \/>\n        }<br \/>\n    });<\/p>\n<p>    \/\/ \u5207\u6362\u589e\u91cf\u7f16\u7801<br \/>\n    document.getElementById(&#039;toggle-delta&#039;).addEventListener(&#039;click&#039;, () &#061;&gt; {<br \/>\n        const enabled &#061; document.getElementById(&#039;toggle-delta&#039;).checked;<br \/>\n        client.setDeltaEncoding(enabled);<br \/>\n    });<br \/>\n});<\/p>\n<p>function updateResourceDisplay(data) {<br \/>\n    const container &#061; document.getElementById(&#039;resource-container&#039;);<br \/>\n    if (!container) return;<\/p>\n<p>    container.innerHTML &#061; &#096;<br \/>\n        &lt;h2&gt;${data.title || &#039;Untitled&#039;}&lt;\/h2&gt;<br \/>\n        &lt;p&gt;&lt;strong&gt;ID:&lt;\/strong&gt; ${data.id}&lt;\/p&gt;<br \/>\n        &lt;p&gt;&lt;strong&gt;Version:&lt;\/strong&gt; ${data.version || 1}&lt;\/p&gt;<br \/>\n        &lt;p&gt;&lt;strong&gt;Content:&lt;\/strong&gt; ${data.content || &#039;No content&#039;}&lt;\/p&gt;<br \/>\n        &lt;p&gt;&lt;strong&gt;Updated:&lt;\/strong&gt; ${new Date(data.updated_at).toLocaleString()}&lt;\/p&gt;<br \/>\n    &#096;;<br \/>\n}<\/p>\n<p>function showUpdateNotification(detail) {<br \/>\n    const notification &#061; document.createElement(&#039;div&#039;);<br \/>\n    notification.className &#061; &#039;notification&#039;;<\/p>\n<p>    let message &#061; &#039;&#039;;<br \/>\n    switch (detail.type) {<br \/>\n        case &#039;delta-applied&#039;:<br \/>\n            message &#061; &#096;Updated with ${detail.changes} changes (delta: ${detail.deltaSize} bytes)&#096;;<br \/>\n            notification.classList.add(&#039;info&#039;);<br \/>\n            break;<br \/>\n        case &#039;full-update&#039;:<br \/>\n            message &#061; &#096;Full update received (${detail.dataSize} bytes)&#096;;<br \/>\n            notification.classList.add(&#039;success&#039;);<br \/>\n            break;<br \/>\n        case &#039;local-update&#039;:<br \/>\n            message &#061; &#039;Local update successful&#039;;<br \/>\n            notification.classList.add(&#039;success&#039;);<br \/>\n            break;<br \/>\n        default:<br \/>\n            message &#061; &#039;Resource updated&#039;;<br \/>\n    }<\/p>\n<p>    notification.textContent &#061; message;<\/p>\n<p>    \/\/ \u6dfb\u52a0\u5230\u9875\u9762<br \/>\n    const container &#061; document.getElementById(&#039;notification-container&#039;) || createNotificationContainer();<br \/>\n    container.appendChild(notification);<\/p>\n<p>    \/\/ 3\u79d2\u540e\u81ea\u52a8\u79fb\u9664<br \/>\n    setTimeout(() &#061;&gt; {<br \/>\n        notification.style.opacity &#061; &#039;0&#039;;<br \/>\n        notification.style.transition &#061; &#039;opacity 0.5s&#039;;<\/p>\n<p>        setTimeout(() &#061;&gt; {<br \/>\n            notification.remove();<br \/>\n        }, 500);<br \/>\n    }, 3000);<br \/>\n}<\/p>\n<p>function createNotificationContainer() {<br \/>\n    const container &#061; document.createElement(&#039;div&#039;);<br \/>\n    container.id &#061; &#039;notification-container&#039;;<br \/>\n    container.style.cssText &#061; &#096;<br \/>\n        position: fixed;<br \/>\n        top: 20px;<br \/>\n        right: 20px;<br \/>\n        z-index: 1000;<br \/>\n    &#096;;<\/p>\n<p>    document.body.appendChild(container);<br \/>\n    return container;<br \/>\n}<\/p>\n<h3>9.6 \u603b\u7ed3\u4e0e\u5bf9\u6bd4\u5206\u6790<\/h3>\n<h4 style=\"background-color:transparent\">2xx\u72b6\u6001\u7801\u5bf9\u6bd4\u77e9\u9635<\/h4>\n<table>\n<tr>\u72b6\u6001\u7801\u540d\u79f0\u4e3b\u8981\u7528\u9014\u54cd\u5e94\u4f53\u7f13\u5b58\u884c\u4e3a\u9002\u7528\u573a\u666f<\/tr>\n<tbody>\n<tr>\n<td>200 OK<\/td>\n<td>\u6210\u529f<\/td>\n<td>\u6807\u51c6\u6210\u529f\u54cd\u5e94<\/td>\n<td>\u53ef\u9009<\/td>\n<td>\u53ef\u7f13\u5b58<\/td>\n<td>\u901a\u7528\u6210\u529f\u54cd\u5e94<\/td>\n<\/tr>\n<tr>\n<td>201 Created<\/td>\n<td>\u5df2\u521b\u5efa<\/td>\n<td>\u8d44\u6e90\u521b\u5efa\u6210\u529f<\/td>\n<td>\u63a8\u8350<\/td>\n<td>\u901a\u5e38\u4e0d\u7f13\u5b58<\/td>\n<td>RESTful API\u521b\u5efa\u64cd\u4f5c<\/td>\n<\/tr>\n<tr>\n<td>202 Accepted<\/td>\n<td>\u5df2\u63a5\u53d7<\/td>\n<td>\u5f02\u6b65\u8bf7\u6c42\u63a5\u53d7<\/td>\n<td>\u53ef\u9009<\/td>\n<td>\u4e0d\u7f13\u5b58<\/td>\n<td>\u957f\u65f6\u95f4\u8fd0\u884c\u4efb\u52a1<\/td>\n<\/tr>\n<tr>\n<td>203 Non-Authoritative<\/td>\n<td>\u975e\u6743\u5a01\u4fe1\u606f<\/td>\n<td>\u4ee3\u7406\u4fee\u6539\u7684\u54cd\u5e94<\/td>\n<td>\u53ef\u9009<\/td>\n<td>\u53ef\u7f13\u5b58<\/td>\n<td>\u4ee3\u7406\/\u7f13\u5b58\u670d\u52a1\u5668<\/td>\n<\/tr>\n<tr>\n<td>204 No Content<\/td>\n<td>\u65e0\u5185\u5bb9<\/td>\n<td>\u6210\u529f\u4f46\u65e0\u8fd4\u56de<\/td>\n<td>\u7981\u6b62<\/td>\n<td>\u901a\u5e38\u4e0d\u7f13\u5b58<\/td>\n<td>\u5220\u9664\u3001\u66f4\u65b0\u64cd\u4f5c<\/td>\n<\/tr>\n<tr>\n<td>205 Reset Content<\/td>\n<td>\u91cd\u7f6e\u5185\u5bb9<\/td>\n<td>\u91cd\u7f6e\u5ba2\u6237\u7aef\u89c6\u56fe<\/td>\n<td>\u7981\u6b62<\/td>\n<td>\u4e0d\u7f13\u5b58<\/td>\n<td>\u8868\u5355\u63d0\u4ea4\u540e\u91cd\u7f6e<\/td>\n<\/tr>\n<tr>\n<td>206 Partial Content<\/td>\n<td>\u90e8\u5206\u5185\u5bb9<\/td>\n<td>\u8303\u56f4\u8bf7\u6c42\u6210\u529f<\/td>\n<td>\u5fc5\u987b<\/td>\n<td>\u53ef\u7f13\u5b58<\/td>\n<td>\u5927\u6587\u4ef6\u4e0b\u8f7d\u3001\u6d41\u5a92\u4f53<\/td>\n<\/tr>\n<tr>\n<td>207 Multi-Status<\/td>\n<td>\u591a\u72b6\u6001<\/td>\n<td>\u6279\u91cf\u64cd\u4f5c\u7ed3\u679c<\/td>\n<td>XML\u5fc5\u987b<\/td>\n<td>\u4e0d\u7f13\u5b58<\/td>\n<td>WebDAV\u3001\u6279\u91cfAPI<\/td>\n<\/tr>\n<tr>\n<td>208 Already Reported<\/td>\n<td>\u5df2\u62a5\u544a<\/td>\n<td>\u907f\u514d\u91cd\u590d\u679a\u4e3e<\/td>\n<td>XML\u53ef\u9009<\/td>\n<td>\u4e0d\u7f13\u5b58<\/td>\n<td>WebDAV\u7ed1\u5b9a\u96c6\u5408<\/td>\n<\/tr>\n<tr>\n<td>226 IM Used<\/td>\n<td>IM\u5df2\u4f7f\u7528<\/td>\n<td>\u589e\u91cf\u7f16\u7801\u54cd\u5e94<\/td>\n<td>\u5fc5\u987b<\/td>\n<td>\u53ef\u7f13\u5b58<\/td>\n<td>\u589e\u91cf\u66f4\u65b0\u3001\u5dee\u5206\u7f16\u7801<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>\u9009\u62e9\u6307\u5357<\/h4>\n<p>python<\/p>\n<p># 2xx\u72b6\u6001\u7801\u9009\u62e9\u51b3\u7b56\u6811<br \/>\nclass StatusCodeSelector:<br \/>\n    &#034;&#034;&#034;2xx\u72b6\u6001\u7801\u9009\u62e9\u5668&#034;&#034;&#034;<\/p>\n<p>    &#064;staticmethod<br \/>\n    def select_status_code(scenario):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u6839\u636e\u573a\u666f\u9009\u62e9\u6700\u5408\u9002\u76842xx\u72b6\u6001\u7801<\/p>\n<p>        Args:<br \/>\n            scenario: \u5305\u542b\u4ee5\u4e0b\u5b57\u6bb5\u7684\u5b57\u5178<br \/>\n                &#8211; method: HTTP\u65b9\u6cd5<br \/>\n                &#8211; action: \u64cd\u4f5c\u7c7b\u578b&#xff08;create, read, update, delete, batch, etc.&#xff09;<br \/>\n                &#8211; needs_response_body: \u662f\u5426\u9700\u8981\u54cd\u5e94\u4f53<br \/>\n                &#8211; is_async: \u662f\u5426\u5f02\u6b65\u5904\u7406<br \/>\n                &#8211; is_partial: \u662f\u5426\u90e8\u5206\u5185\u5bb9<br \/>\n                &#8211; is_batch: \u662f\u5426\u6279\u91cf\u64cd\u4f5c<br \/>\n                &#8211; transformed: \u662f\u5426\u7ecf\u8fc7\u8f6c\u6362<br \/>\n                &#8211; client_should_reset: \u5ba2\u6237\u7aef\u662f\u5426\u5e94\u91cd\u7f6e<\/p>\n<p>        Returns:<br \/>\n            \u63a8\u8350\u7684\u72b6\u6001\u7801<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        method &#061; scenario.get(&#039;method&#039;, &#039;GET&#039;)<br \/>\n        action &#061; scenario.get(&#039;action&#039;)<\/p>\n<p>        # \u7279\u6b8a\u60c5\u51b5&#xff1a;\u8303\u56f4\u8bf7\u6c42<br \/>\n        if scenario.get(&#039;is_partial&#039;):<br \/>\n            return 206<\/p>\n<p>        # \u7279\u6b8a\u60c5\u51b5&#xff1a;\u6279\u91cf\u64cd\u4f5c&#xff08;WebDAV\u98ce\u683c&#xff09;<br \/>\n        if scenario.get(&#039;is_batch&#039;) and scenario.get(&#039;format&#039;) &#061;&#061; &#039;xml&#039;:<br \/>\n            return 207<\/p>\n<p>        # \u7279\u6b8a\u60c5\u51b5&#xff1a;\u4ee3\u7406\u8f6c\u6362\u7684\u54cd\u5e94<br \/>\n        if scenario.get(&#039;transformed&#039;):<br \/>\n            return 203<\/p>\n<p>        # \u7279\u6b8a\u60c5\u51b5&#xff1a;\u5ba2\u6237\u7aef\u9700\u8981\u91cd\u7f6e<br \/>\n        if scenario.get(&#039;client_should_reset&#039;):<br \/>\n            return 205<\/p>\n<p>        # \u6839\u636e\u65b9\u6cd5\u548c\u64cd\u4f5c\u9009\u62e9<br \/>\n        if action &#061;&#061; &#039;create&#039;:<br \/>\n            if scenario.get(&#039;is_async&#039;):<br \/>\n                return 202<br \/>\n            return 201<\/p>\n<p>        elif action &#061;&#061; &#039;delete&#039;:<br \/>\n            if scenario.get(&#039;needs_response_body&#039;):<br \/>\n                return 200<br \/>\n            return 204<\/p>\n<p>        elif action &#061;&#061; &#039;update&#039;:<br \/>\n            if scenario.get(&#039;needs_response_body&#039;):<br \/>\n                return 200<br \/>\n            return 204<\/p>\n<p>        elif action &#061;&#061; &#039;read&#039;:<br \/>\n            return 200<\/p>\n<p>        else:<br \/>\n            # \u9ed8\u8ba4<br \/>\n            return 200<\/p>\n<p>    &#064;staticmethod<br \/>\n    def get_best_practices(status_code):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u7279\u5b9a\u72b6\u6001\u7801\u7684\u6700\u4f73\u5b9e\u8df5&#034;&#034;&#034;<\/p>\n<p>        practices &#061; {<br \/>\n            200: {<br \/>\n                &#039;body&#039;: &#039;\u5e94\u8be5\u5305\u542b\u8bf7\u6c42\u7684\u8d44\u6e90\u8868\u793a\u6216\u64cd\u4f5c\u7ed3\u679c&#039;,<br \/>\n                &#039;headers&#039;: [&#039;Content-Type&#039;, &#039;Content-Length&#039;, &#039;ETag&#039;, &#039;Last-Modified&#039;],<br \/>\n                &#039;cache&#039;: &#039;\u9ed8\u8ba4\u53ef\u7f13\u5b58&#xff0c;\u8bbe\u7f6e\u9002\u5f53\u7684Cache-Control&#039;,<br \/>\n                &#039;clients&#039;: &#039;\u5e94\u8be5\u5904\u7406\u54cd\u5e94\u4f53\u5e76\u66f4\u65b0UI&#039;<br \/>\n            },<br \/>\n            201: {<br \/>\n                &#039;body&#039;: &#039;\u5e94\u8be5\u5305\u542b\u521b\u5efa\u7684\u8d44\u6e90\u8868\u793a&#039;,<br \/>\n                &#039;headers&#039;: [&#039;Location&#039;, &#039;Content-Type&#039;, &#039;Content-Length&#039;],<br \/>\n                &#039;cache&#039;: &#039;\u901a\u5e38\u4e0d\u7f13\u5b58\u6216\u77ed\u65f6\u95f4\u7f13\u5b58&#039;,<br \/>\n                &#039;clients&#039;: &#039;\u5e94\u8be5\u8ddf\u968fLocation\u5934\u90e8\u83b7\u53d6\u65b0\u8d44\u6e90&#039;<br \/>\n            },<br \/>\n            202: {<br \/>\n                &#039;body&#039;: &#039;\u5e94\u8be5\u5305\u542b\u4efb\u52a1\u72b6\u6001\u548c\u8ddf\u8e2a\u4fe1\u606f&#039;,<br \/>\n                &#039;headers&#039;: [&#039;Location&#039;, &#039;Retry-After&#039;, &#039;X-Task-ID&#039;],<br \/>\n                &#039;cache&#039;: &#039;\u4e0d\u7f13\u5b58&#039;,<br \/>\n                &#039;clients&#039;: &#039;\u5e94\u8be5\u8f6e\u8be2\u72b6\u6001\u6216\u7b49\u5f85\u56de\u8c03&#039;<br \/>\n            },<br \/>\n            203: {<br \/>\n                &#039;body&#039;: &#039;\u53ef\u9009&#xff0c;\u4f46\u901a\u5e38\u5305\u542b\u6570\u636e&#039;,<br \/>\n                &#039;headers&#039;: [&#039;Warning&#039;, &#039;Via&#039;, &#039;X-Transformed-By&#039;],<br \/>\n                &#039;cache&#039;: &#039;\u53ef\u7f13\u5b58&#xff0c;\u4f46\u6ce8\u610f\u975e\u6743\u5a01\u6027&#039;,<br \/>\n                &#039;clients&#039;: &#039;\u5e94\u8be5\u6ce8\u610f\u6570\u636e\u53ef\u80fd\u4e0d\u662f\u6700\u65b0\u7684&#039;<br \/>\n            },<br \/>\n            204: {<br \/>\n                &#039;body&#039;: &#039;\u7981\u6b62\u5305\u542b\u54cd\u5e94\u4f53&#039;,<br \/>\n                &#039;headers&#039;: [&#039;ETag&#039;, &#039;Last-Modified&#039;, &#039;X-Operation-Type&#039;],<br \/>\n                &#039;cache&#039;: &#039;\u901a\u5e38\u4e0d\u7f13\u5b58&#039;,<br \/>\n                &#039;clients&#039;: &#039;\u5e94\u8be5\u66f4\u65b0\u672c\u5730\u72b6\u6001\u548cUI&#039;<br \/>\n            },<br \/>\n            205: {<br \/>\n                &#039;body&#039;: &#039;\u7981\u6b62\u5305\u542b\u54cd\u5e94\u4f53&#039;,<br \/>\n                &#039;headers&#039;: [&#039;X-Reset-Type&#039;, &#039;X-Form-ID&#039;],<br \/>\n                &#039;cache&#039;: &#039;\u4e0d\u7f13\u5b58&#039;,<br \/>\n                &#039;clients&#039;: &#039;\u5e94\u8be5\u91cd\u7f6e\u8868\u5355\u6216\u89c6\u56fe\u72b6\u6001&#039;<br \/>\n            },<br \/>\n            206: {<br \/>\n                &#039;body&#039;: &#039;\u5fc5\u987b\u5305\u542b\u8bf7\u6c42\u7684\u8303\u56f4\u5185\u5bb9&#039;,<br \/>\n                &#039;headers&#039;: [&#039;Content-Range&#039;, &#039;Accept-Ranges&#039;, &#039;Content-Length&#039;],<br \/>\n                &#039;cache&#039;: &#039;\u53ef\u7f13\u5b58&#039;,<br \/>\n                &#039;clients&#039;: &#039;\u5e94\u8be5\u5408\u5e76\u591a\u4e2a\u8303\u56f4\u6216\u5904\u7406\u90e8\u5206\u5185\u5bb9&#039;<br \/>\n            },<br \/>\n            207: {<br \/>\n                &#039;body&#039;: &#039;\u5fc5\u987b\u662fXML\u683c\u5f0f&#xff0c;\u5305\u542b\u591a\u4e2aresponse\u5143\u7d20&#039;,<br \/>\n                &#039;headers&#039;: [&#039;Content-Type: application\/xml&#039;],<br \/>\n                &#039;cache&#039;: &#039;\u4e0d\u7f13\u5b58&#039;,<br \/>\n                &#039;clients&#039;: &#039;\u5e94\u8be5\u89e3\u6790XML\u5e76\u5904\u7406\u6bcf\u4e2a\u72ec\u7acb\u72b6\u6001&#039;<br \/>\n            },<br \/>\n            208: {<br \/>\n                &#039;body&#039;: &#039;XML\u683c\u5f0f&#xff0c;\u5728207\u54cd\u5e94\u5185\u90e8\u4f7f\u7528&#039;,<br \/>\n                &#039;headers&#039;: [],<br \/>\n                &#039;cache&#039;: &#039;\u4e0d\u9002\u7528&#039;,<br \/>\n                &#039;clients&#039;: &#039;\u5e94\u8be5\u8df3\u8fc7\u5df2\u62a5\u544a\u7684\u8d44\u6e90&#039;<br \/>\n            },<br \/>\n            226: {<br \/>\n                &#039;body&#039;: &#039;\u5fc5\u987b\u5305\u542b\u589e\u91cf\u6570\u636e&#039;,<br \/>\n                &#039;headers&#039;: [&#039;IM&#039;, &#039;Delta-Base&#039;, &#039;ETag&#039;],<br \/>\n                &#039;cache&#039;: &#039;\u53ef\u7f13\u5b58&#xff0c;\u4f46\u6ce8\u610f\u589e\u91cf\u7279\u6027&#039;,<br \/>\n                &#039;clients&#039;: &#039;\u5e94\u8be5\u5e94\u7528\u589e\u91cf\u5230\u73b0\u6709\u6570\u636e&#039;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return practices.get(status_code, {})<\/p>\n<h3>9.7 \u5b9e\u9645\u5e94\u7528\u6848\u4f8b<\/h3>\n<h4>\u6848\u4f8b1&#xff1a;\u7535\u5b50\u5546\u52a1\u8ba2\u5355\u7cfb\u7edf<\/h4>\n<p>python<\/p>\n<p># \u7535\u5b50\u5546\u52a1\u7cfb\u7edf\u4e2d\u76842xx\u72b6\u6001\u7801\u5e94\u7528<br \/>\nclass ECommerceOrderSystem:<br \/>\n    &#034;&#034;&#034;\u7535\u5b50\u5546\u52a1\u8ba2\u5355\u7cfb\u7edf&#034;&#034;&#034;<\/p>\n<p>    def process_order(self, order_data):<br \/>\n        &#034;&#034;&#034;\u5904\u7406\u8ba2\u5355 &#8211; \u4f7f\u7528202 Accepted\u8868\u793a\u5f02\u6b65\u5904\u7406&#034;&#034;&#034;<br \/>\n        # \u9a8c\u8bc1\u8ba2\u5355<br \/>\n        if not self.validate_order(order_data):<br \/>\n            return {&#039;status&#039;: 400, &#039;error&#039;: &#039;Invalid order&#039;}<\/p>\n<p>        # \u521b\u5efa\u5f02\u6b65\u4efb\u52a1<br \/>\n        task_id &#061; self.create_processing_task(order_data)<\/p>\n<p>        # \u8fd4\u56de202 Accepted<br \/>\n        return {<br \/>\n            &#039;status&#039;: 202,<br \/>\n            &#039;body&#039;: {<br \/>\n                &#039;task_id&#039;: task_id,<br \/>\n                &#039;message&#039;: &#039;Order processing started&#039;,<br \/>\n                &#039;status_url&#039;: f&#039;\/api\/orders\/status\/{task_id}&#039;,<br \/>\n                &#039;estimated_completion&#039;: &#039;10 minutes&#039;<br \/>\n            },<br \/>\n            &#039;headers&#039;: {<br \/>\n                &#039;Location&#039;: f&#039;\/api\/orders\/status\/{task_id}&#039;,<br \/>\n                &#039;Retry-After&#039;: &#039;30&#039;<br \/>\n            }<br \/>\n        }<\/p>\n<p>    def cancel_order(self, order_id):<br \/>\n        &#034;&#034;&#034;\u53d6\u6d88\u8ba2\u5355 &#8211; \u4f7f\u7528204 No Content&#034;&#034;&#034;<br \/>\n        success &#061; self.cancel_order_in_db(order_id)<\/p>\n<p>        if success:<br \/>\n            return {<br \/>\n                &#039;status&#039;: 204,<br \/>\n                &#039;headers&#039;: {<br \/>\n                    &#039;X-Order-Cancelled&#039;: order_id,<br \/>\n                    &#039;X-Cancelled-At&#039;: datetime.now().isoformat()<br \/>\n                }<br \/>\n            }<br \/>\n        else:<br \/>\n            return {&#039;status&#039;: 404, &#039;error&#039;: &#039;Order not found&#039;}<\/p>\n<p>    def update_order_status(self, order_id, status):<br \/>\n        &#034;&#034;&#034;\u66f4\u65b0\u8ba2\u5355\u72b6\u6001 &#8211; \u4f7f\u7528200 OK\u6216204 No Content&#034;&#034;&#034;<br \/>\n        updated_order &#061; self.update_status_in_db(order_id, status)<\/p>\n<p>        if updated_order:<br \/>\n            # \u5982\u679c\u9700\u8981\u8fd4\u56de\u66f4\u65b0\u540e\u7684\u8ba2\u5355&#xff0c;\u4f7f\u7528200<br \/>\n            return {<br \/>\n                &#039;status&#039;: 200,<br \/>\n                &#039;body&#039;: updated_order,<br \/>\n                &#039;headers&#039;: {&#039;ETag&#039;: self.generate_etag(updated_order)}<br \/>\n            }<br \/>\n        else:<br \/>\n            # \u5982\u679c\u53ea\u662f\u72b6\u6001\u66f4\u65b0&#xff0c;\u4f7f\u7528204<br \/>\n            return {<br \/>\n                &#039;status&#039;: 204,<br \/>\n                &#039;headers&#039;: {<br \/>\n                    &#039;X-Order-Updated&#039;: order_id,<br \/>\n                    &#039;X-New-Status&#039;: status<br \/>\n                }<br \/>\n            }<\/p>\n<h4>\u6848\u4f8b2&#xff1a;\u5185\u5bb9\u7ba1\u7406\u7cfb\u7edf<\/h4>\n<p>python<\/p>\n<p># CMS\u7cfb\u7edf\u4e2d\u76842xx\u72b6\u6001\u7801\u5e94\u7528<br \/>\nclass ContentManagementSystem:<br \/>\n    &#034;&#034;&#034;\u5185\u5bb9\u7ba1\u7406\u7cfb\u7edf&#034;&#034;&#034;<\/p>\n<p>    def publish_article(self, article_id):<br \/>\n        &#034;&#034;&#034;\u53d1\u5e03\u6587\u7ae0 &#8211; \u4f7f\u7528200 OK\u6216204 No Content&#034;&#034;&#034;<br \/>\n        article &#061; self.get_article(article_id)<\/p>\n<p>        if not article:<br \/>\n            return {&#039;status&#039;: 404, &#039;error&#039;: &#039;Article not found&#039;}<\/p>\n<p>        # \u5982\u679c\u5df2\u7ecf\u53d1\u5e03&#xff0c;\u8fd4\u56de204&#xff08;\u5e42\u7b49&#xff09;<br \/>\n        if article[&#039;published&#039;]:<br \/>\n            return {<br \/>\n                &#039;status&#039;: 204,<br \/>\n                &#039;headers&#039;: {<br \/>\n                    &#039;X-Article-Already-Published&#039;: article_id,<br \/>\n                    &#039;X-Published-At&#039;: article[&#039;published_at&#039;]<br \/>\n                }<br \/>\n            }<\/p>\n<p>        # \u53d1\u5e03\u6587\u7ae0<br \/>\n        published_article &#061; self.publish_in_db(article_id)<\/p>\n<p>        # \u8fd4\u56de200 OK\u5305\u542b\u5b8c\u6574\u6587\u7ae0<br \/>\n        return {<br \/>\n            &#039;status&#039;: 200,<br \/>\n            &#039;body&#039;: published_article,<br \/>\n            &#039;headers&#039;: {<br \/>\n                &#039;ETag&#039;: self.generate_etag(published_article),<br \/>\n                &#039;X-Published-At&#039;: published_article[&#039;published_at&#039;]<br \/>\n            }<br \/>\n        }<\/p>\n<p>    def batch_delete_comments(self, comment_ids):<br \/>\n        &#034;&#034;&#034;\u6279\u91cf\u5220\u9664\u8bc4\u8bba &#8211; \u4f7f\u7528207 Multi-Status&#034;&#034;&#034;<br \/>\n        results &#061; []<\/p>\n<p>        for comment_id in comment_ids:<br \/>\n            try:<br \/>\n                success &#061; self.delete_comment(comment_id)<\/p>\n<p>                results.append({<br \/>\n                    &#039;href&#039;: f&#039;\/api\/comments\/{comment_id}&#039;,<br \/>\n                    &#039;status&#039;: 200 if success else 404,<br \/>\n                    &#039;message&#039;: &#039;Deleted&#039; if success else &#039;Not found&#039;<br \/>\n                })<br \/>\n            except Exception as e:<br \/>\n                results.append({<br \/>\n                    &#039;href&#039;: f&#039;\/api\/comments\/{comment_id}&#039;,<br \/>\n                    &#039;status&#039;: 500,<br \/>\n                    &#039;error&#039;: str(e)<br \/>\n                })<\/p>\n<p>        # \u521b\u5efa207\u54cd\u5e94<br \/>\n        return {<br \/>\n            &#039;status&#039;: 207,<br \/>\n            &#039;body&#039;: self.create_multistatus_xml(results),<br \/>\n            &#039;headers&#039;: {&#039;Content-Type&#039;: &#039;application\/xml&#039;}<br \/>\n        }<\/p>\n<h3>9.8 \u76d1\u63a7\u548c\u8c03\u8bd5<\/h3>\n<h4>\u76d1\u63a7\u4eea\u8868\u677f<\/h4>\n<p>python<\/p>\n<p># 2xx\u72b6\u6001\u7801\u76d1\u63a7<br \/>\nclass StatusCodeMonitor:<br \/>\n    &#034;&#034;&#034;\u72b6\u6001\u7801\u76d1\u63a7\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.stats &#061; {<br \/>\n            200: 0, 201: 0, 202: 0, 203: 0, 204: 0,<br \/>\n            205: 0, 206: 0, 207: 0, 208: 0, 226: 0<br \/>\n        }<br \/>\n        self.timestamps &#061; []<br \/>\n        self.errors &#061; []<\/p>\n<p>    def record_response(self, status_code, endpoint, duration):<br \/>\n        &#034;&#034;&#034;\u8bb0\u5f55\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        if status_code in self.stats:<br \/>\n            self.stats[status_code] &#043;&#061; 1<\/p>\n<p>        self.timestamps.append({<br \/>\n            &#039;time&#039;: datetime.now(),<br \/>\n            &#039;status&#039;: status_code,<br \/>\n            &#039;endpoint&#039;: endpoint,<br \/>\n            &#039;duration&#039;: duration<br \/>\n        })<\/p>\n<p>        # \u4fdd\u7559\u6700\u8fd11000\u6761\u8bb0\u5f55<br \/>\n        if len(self.timestamps) &gt; 1000:<br \/>\n            self.timestamps &#061; self.timestamps[-1000:]<\/p>\n<p>    def get_statistics(self, time_window_minutes&#061;60):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u7edf\u8ba1\u4fe1\u606f&#034;&#034;&#034;<br \/>\n        cutoff &#061; datetime.now() &#8211; timedelta(minutes&#061;time_window_minutes)<br \/>\n        recent &#061; [t for t in self.timestamps if t[&#039;time&#039;] &gt; cutoff]<\/p>\n<p>        stats &#061; {<br \/>\n            &#039;total&#039;: len(recent),<br \/>\n            &#039;by_status&#039;: {},<br \/>\n            &#039;by_endpoint&#039;: {},<br \/>\n            &#039;avg_duration&#039;: 0,<br \/>\n            &#039;success_rate&#039;: 0<br \/>\n        }<\/p>\n<p>        # \u6309\u72b6\u6001\u7801\u7edf\u8ba1<br \/>\n        for status, count in self.stats.items():<br \/>\n            stats[&#039;by_status&#039;][status] &#061; count<\/p>\n<p>        # \u6309\u7aef\u70b9\u7edf\u8ba1<br \/>\n        for record in recent:<br \/>\n            endpoint &#061; record[&#039;endpoint&#039;]<br \/>\n            stats[&#039;by_endpoint&#039;][endpoint] &#061; stats[&#039;by_endpoint&#039;].get(endpoint, 0) &#043; 1<\/p>\n<p>        # \u5e73\u5747\u6301\u7eed\u65f6\u95f4<br \/>\n        if recent:<br \/>\n            avg_duration &#061; sum(r[&#039;duration&#039;] for r in recent) \/ len(recent)<br \/>\n            stats[&#039;avg_duration&#039;] &#061; avg_duration<\/p>\n<p>        # \u6210\u529f\u7387&#xff08;2xx\u72b6\u6001\u7801\u6bd4\u4f8b&#xff09;<br \/>\n        if recent:<br \/>\n            success_count &#061; sum(1 for r in recent if 200 &lt;&#061; r[&#039;status&#039;] &lt; 300)<br \/>\n            stats[&#039;success_rate&#039;] &#061; (success_count \/ len(recent)) * 100<\/p>\n<p>        return stats<\/p>\n<p>    def generate_alerts(self):<br \/>\n        &#034;&#034;&#034;\u751f\u6210\u544a\u8b66&#034;&#034;&#034;<br \/>\n        alerts &#061; []<br \/>\n        stats &#061; self.get_statistics(5)  # \u6700\u8fd15\u5206\u949f<\/p>\n<p>        # \u68c0\u67e5\u6210\u529f\u7387<br \/>\n        if stats[&#039;success_rate&#039;] &lt; 95:<br \/>\n            alerts.append({<br \/>\n                &#039;level&#039;: &#039;warning&#039;,<br \/>\n                &#039;message&#039;: f&#039;Low success rate: {stats[&#034;success_rate&#034;]:.1f}%&#039;,<br \/>\n                &#039;metric&#039;: &#039;success_rate&#039;,<br \/>\n                &#039;value&#039;: stats[&#039;success_rate&#039;]<br \/>\n            })<\/p>\n<p>        # \u68c0\u67e5\u5f02\u5e38\u72b6\u6001\u7801\u6bd4\u4f8b<br \/>\n        unexpected_codes &#061; [203, 205, 207, 208, 226]<br \/>\n        for code in unexpected_codes:<br \/>\n            count &#061; stats[&#039;by_status&#039;].get(code, 0)<br \/>\n            if count &gt; 10:  # \u77ed\u65f6\u95f4\u5185\u51fa\u73b0\u591a\u6b21<br \/>\n                alerts.append({<br \/>\n                    &#039;level&#039;: &#039;info&#039;,<br \/>\n                    &#039;message&#039;: f&#039;Unusual number of {code} responses: {count}&#039;,<br \/>\n                    &#039;metric&#039;: f&#039;status_{code}&#039;,<br \/>\n                    &#039;value&#039;: count<br \/>\n                })<\/p>\n<p>        return alerts<\/p>\n<h4>\u8c03\u8bd5\u5de5\u5177<\/h4>\n<p>javascript<\/p>\n<p>\/\/ 2xx\u72b6\u6001\u7801\u8c03\u8bd5\u5de5\u5177<br \/>\nclass StatusCodeDebugger {<br \/>\n    \/**<br \/>\n     * \u72b6\u6001\u7801\u8c03\u8bd5\u5de5\u5177<br \/>\n     *\/<\/p>\n<p>    constructor() {<br \/>\n        this.interceptRequests();<br \/>\n        this.setupUI();<br \/>\n    }<\/p>\n<p>    interceptRequests() {<br \/>\n        \/\/ \u62e6\u622afetch\u8bf7\u6c42<br \/>\n        const originalFetch &#061; window.fetch;<br \/>\n        window.fetch &#061; async function(&#8230;args) {<br \/>\n            const startTime &#061; Date.now();<br \/>\n            const response &#061; await originalFetch.apply(this, args);<br \/>\n            const duration &#061; Date.now() &#8211; startTime;<\/p>\n<p>            \/\/ \u8bb0\u5f552xx\u54cd\u5e94<br \/>\n            if (response.status &gt;&#061; 200 &amp;&amp; response.status &lt; 300) {<br \/>\n                StatusCodeDebugger.logResponse({<br \/>\n                    url: args[0],<br \/>\n                    method: args[1]?.method || &#039;GET&#039;,<br \/>\n                    status: response.status,<br \/>\n                    statusText: response.statusText,<br \/>\n                    duration: duration,<br \/>\n                    headers: Object.fromEntries(response.headers.entries()),<br \/>\n                    timestamp: new Date().toISOString()<br \/>\n                });<br \/>\n            }<\/p>\n<p>            return response;<br \/>\n        };<\/p>\n<p>        \/\/ \u62e6\u622aXMLHttpRequest<br \/>\n        const originalOpen &#061; XMLHttpRequest.prototype.open;<br \/>\n        XMLHttpRequest.prototype.open &#061; function(&#8230;args) {<br \/>\n            this._url &#061; args[1];<br \/>\n            this._method &#061; args[0];<br \/>\n            return originalOpen.apply(this, args);<br \/>\n        };<\/p>\n<p>        const originalSend &#061; XMLHttpRequest.prototype.send;<br \/>\n        XMLHttpRequest.prototype.send &#061; function(&#8230;args) {<br \/>\n            const startTime &#061; Date.now();<\/p>\n<p>            this.addEventListener(&#039;load&#039;, function() {<br \/>\n                const duration &#061; Date.now() &#8211; startTime;<\/p>\n<p>                if (this.status &gt;&#061; 200 &amp;&amp; this.status &lt; 300) {<br \/>\n                    StatusCodeDebugger.logResponse({<br \/>\n                        url: this._url,<br \/>\n                        method: this._method,<br \/>\n                        status: this.status,<br \/>\n                        statusText: this.statusText,<br \/>\n                        duration: duration,<br \/>\n                        headers: this.getAllResponseHeaders(),<br \/>\n                        timestamp: new Date().toISOString()<br \/>\n                    });<br \/>\n                }<br \/>\n            });<\/p>\n<p>            return originalSend.apply(this, args);<br \/>\n        };<br \/>\n    }<\/p>\n<p>    static logResponse(info) {<br \/>\n        \/\/ \u5b58\u50a8\u5230localStorage<br \/>\n        const key &#061; &#096;debug_${Date.now()}&#096;;<br \/>\n        localStorage.setItem(key, JSON.stringify(info));<\/p>\n<p>        \/\/ \u89e6\u53d1\u4e8b\u4ef6<br \/>\n        const event &#061; new CustomEvent(&#039;statusCodeResponse&#039;, { detail: info });<br \/>\n        document.dispatchEvent(event);<br \/>\n    }<\/p>\n<p>    setupUI() {<br \/>\n        \/\/ \u521b\u5efa\u8c03\u8bd5\u9762\u677f<br \/>\n        const panel &#061; document.createElement(&#039;div&#039;);<br \/>\n        panel.id &#061; &#039;status-code-debug-panel&#039;;<br \/>\n        panel.style.cssText &#061; &#096;<br \/>\n            position: fixed;<br \/>\n            bottom: 10px;<br \/>\n            right: 10px;<br \/>\n            width: 400px;<br \/>\n            max-height: 500px;<br \/>\n            background: white;<br \/>\n            border: 1px solid #ccc;<br \/>\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);<br \/>\n            z-index: 9999;<br \/>\n            font-family: monospace;<br \/>\n            font-size: 12px;<br \/>\n            overflow: hidden;<br \/>\n        &#096;;<\/p>\n<p>        panel.innerHTML &#061; &#096;<br \/>\n            &lt;div style&#061;&#034;background: #f5f5f5; padding: 10px; border-bottom: 1px solid #ccc;&#034;&gt;<br \/>\n                &lt;strong&gt;2xx Status Code Debugger&lt;\/strong&gt;<br \/>\n                &lt;button id&#061;&#034;debug-clear&#034; style&#061;&#034;float: right;&#034;&gt;Clear&lt;\/button&gt;<br \/>\n            &lt;\/div&gt;<br \/>\n            &lt;div id&#061;&#034;debug-content&#034; style&#061;&#034;padding: 10px; overflow-y: auto; max-height: 450px;&#034;&gt;&lt;\/div&gt;<br \/>\n        &#096;;<\/p>\n<p>        document.body.appendChild(panel);<\/p>\n<p>        \/\/ \u76d1\u542c\u54cd\u5e94\u4e8b\u4ef6<br \/>\n        document.addEventListener(&#039;statusCodeResponse&#039;, (e) &#061;&gt; {<br \/>\n            this.updateDebugPanel(e.detail);<br \/>\n        });<\/p>\n<p>        \/\/ \u6e05\u7a7a\u6309\u94ae<br \/>\n        document.getElementById(&#039;debug-clear&#039;).addEventListener(&#039;click&#039;, () &#061;&gt; {<br \/>\n            document.getElementById(&#039;debug-content&#039;).innerHTML &#061; &#039;&#039;;<br \/>\n        });<br \/>\n    }<\/p>\n<p>    updateDebugPanel(info) {<br \/>\n        const content &#061; document.getElementById(&#039;debug-content&#039;);<\/p>\n<p>        const entry &#061; document.createElement(&#039;div&#039;);<br \/>\n        entry.style.cssText &#061; &#096;<br \/>\n            margin-bottom: 10px;<br \/>\n            padding: 8px;<br \/>\n            border-left: 4px solid ${this.getStatusColor(info.status)};<br \/>\n            background: #f9f9f9;<br \/>\n        &#096;;<\/p>\n<p>        entry.innerHTML &#061; &#096;<br \/>\n            &lt;div&gt;&lt;strong&gt;${info.method} ${info.url}&lt;\/strong&gt;&lt;\/div&gt;<br \/>\n            &lt;div&gt;${info.status} ${info.statusText} &#8211; ${info.duration}ms&lt;\/div&gt;<br \/>\n            &lt;div style&#061;&#034;color: #666; font-size: 11px;&#034;&gt;${info.timestamp}&lt;\/div&gt;<br \/>\n            &lt;button onclick&#061;&#034;this.nextElementSibling.style.display&#061;&#039;block&#039;&#034;&gt;Show Headers&lt;\/button&gt;<br \/>\n            &lt;div style&#061;&#034;display: none; margin-top: 5px; font-size: 11px;&#034;&gt;<br \/>\n                ${this.formatHeaders(info.headers)}<br \/>\n            &lt;\/div&gt;<br \/>\n        &#096;;<\/p>\n<p>        content.prepend(entry);<\/p>\n<p>        \/\/ \u9650\u5236\u6761\u76ee\u6570\u91cf<br \/>\n        const entries &#061; content.children;<br \/>\n        if (entries.length &gt; 20) {<br \/>\n            entries[entries.length &#8211; 1].remove();<br \/>\n        }<br \/>\n    }<\/p>\n<p>    getStatusColor(status) {<br \/>\n        const colors &#061; {<br \/>\n            200: &#039;#28a745&#039;, \/\/ \u7eff\u8272<br \/>\n            201: &#039;#20c997&#039;, \/\/ \u9752\u7eff\u8272<br \/>\n            202: &#039;#17a2b8&#039;, \/\/ \u84dd\u8272<br \/>\n            203: &#039;#6c757d&#039;, \/\/ \u7070\u8272<br \/>\n            204: &#039;#ffc107&#039;, \/\/ \u9ec4\u8272<br \/>\n            205: &#039;#fd7e14&#039;, \/\/ \u6a59\u8272<br \/>\n            206: &#039;#007bff&#039;, \/\/ \u6df1\u84dd\u8272<br \/>\n            207: &#039;#6610f2&#039;, \/\/ \u7d2b\u8272<br \/>\n            208: &#039;#e83e8c&#039;, \/\/ \u7c89\u8272<br \/>\n            226: &#039;#20c997&#039;  \/\/ \u9752\u7eff\u8272<br \/>\n        };<\/p>\n<p>        return colors[status] || &#039;#6c757d&#039;;<br \/>\n    }<\/p>\n<p>    formatHeaders(headers) {<br \/>\n        if (typeof headers &#061;&#061;&#061; &#039;string&#039;) {<br \/>\n            return headers.split(&#039;\\\\r\\\\n&#039;).map(line &#061;&gt;<br \/>\n                &#096;&lt;div&gt;${line}&lt;\/div&gt;&#096;<br \/>\n            ).join(&#039;&#039;);<br \/>\n        } else if (typeof headers &#061;&#061;&#061; &#039;object&#039;) {<br \/>\n            return Object.entries(headers).map(([key, value]) &#061;&gt;<br \/>\n                &#096;&lt;div&gt;&lt;strong&gt;${key}:&lt;\/strong&gt; ${value}&lt;\/div&gt;&#096;<br \/>\n            ).join(&#039;&#039;);<br \/>\n        }<br \/>\n        return &#039;&#039;;<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\nif (process.env.NODE_ENV &#061;&#061;&#061; &#039;development&#039;) {<br \/>\n    new StatusCodeDebugger();<br \/>\n}<\/p>\n<h3>9.9 \u517c\u5bb9\u6027\u548c\u540e\u5907\u65b9\u6848<\/h3>\n<h4>\u5411\u540e\u517c\u5bb9\u7b56\u7565<\/h4>\n<p>python<\/p>\n<p># 2xx\u72b6\u6001\u7801\u517c\u5bb9\u6027\u5904\u7406\u5668<br \/>\nclass StatusCodeCompatibility:<br \/>\n    &#034;&#034;&#034;\u5904\u7406\u72b6\u6001\u7801\u517c\u5bb9\u6027\u95ee\u9898&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.problematic_clients &#061; [<br \/>\n            &#039;old-browser-1.0&#039;,<br \/>\n            &#039;legacy-api-client&#039;,<br \/>\n            &#039;custom-http-client&#039;<br \/>\n        ]<\/p>\n<p>    def adapt_response(self, response, user_agent):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u6839\u636e\u5ba2\u6237\u7aef\u9002\u914d\u54cd\u5e94<\/p>\n<p>        Args:<br \/>\n            response: \u539f\u59cb\u54cd\u5e94<br \/>\n            user_agent: \u7528\u6237\u4ee3\u7406\u5b57\u7b26\u4e32<\/p>\n<p>        Returns:<br \/>\n            \u9002\u914d\u540e\u7684\u54cd\u5e94<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u662f\u95ee\u9898\u5ba2\u6237\u7aef<br \/>\n        if self.is_problematic_client(user_agent):<br \/>\n            return self.fallback_response(response)<\/p>\n<p>        return response<\/p>\n<p>    def is_problematic_client(self, user_agent):<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u662f\u5426\u662f\u95ee\u9898\u5ba2\u6237\u7aef&#034;&#034;&#034;<br \/>\n        if not user_agent:<br \/>\n            return False<\/p>\n<p>        ua_lower &#061; user_agent.lower()<br \/>\n        return any(client in ua_lower for client in self.problematic_clients)<\/p>\n<p>    def fallback_response(self, original_response):<br \/>\n        &#034;&#034;&#034;\u63d0\u4f9b\u540e\u5907\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        status &#061; original_response.get(&#039;status&#039;, 200)<\/p>\n<p>        # \u5c06\u975e\u5e38\u89c1\u72b6\u6001\u7801\u6620\u5c04\u5230\u5e38\u89c1\u72b6\u6001\u7801<br \/>\n        mapping &#061; {<br \/>\n            202: 200,  # \u5f02\u6b65\u5904\u7406 -&gt; \u6807\u51c6\u6210\u529f<br \/>\n            203: 200,  # \u975e\u6743\u5a01\u4fe1\u606f -&gt; \u6807\u51c6\u6210\u529f<br \/>\n            205: 200,  # \u91cd\u7f6e\u5185\u5bb9 -&gt; \u6807\u51c6\u6210\u529f<br \/>\n            206: 200,  # \u90e8\u5206\u5185\u5bb9 -&gt; \u6807\u51c6\u6210\u529f&#xff08;\u5b8c\u6574\u5185\u5bb9&#xff09;<br \/>\n            207: 200,  # \u591a\u72b6\u6001 -&gt; \u6807\u51c6\u6210\u529f&#xff08;\u7b2c\u4e00\u4e2a\u7ed3\u679c&#xff09;<br \/>\n            208: 200,  # \u5df2\u62a5\u544a -&gt; \u6807\u51c6\u6210\u529f<br \/>\n            226: 200   # IM\u5df2\u4f7f\u7528 -&gt; \u6807\u51c6\u6210\u529f<br \/>\n        }<\/p>\n<p>        if status in mapping:<br \/>\n            new_status &#061; mapping[status]<\/p>\n<p>            # \u521b\u5efa\u517c\u5bb9\u54cd\u5e94<br \/>\n            compatible_response &#061; {<br \/>\n                &#039;status&#039;: new_status,<br \/>\n                &#039;body&#039;: self.adapt_body(original_response.get(&#039;body&#039;), status),<br \/>\n                &#039;headers&#039;: self.adapt_headers(original_response.get(&#039;headers&#039;, {}), status)<br \/>\n            }<\/p>\n<p>            # \u6dfb\u52a0\u517c\u5bb9\u6027\u5934\u90e8<br \/>\n            compatible_response[&#039;headers&#039;][&#039;X-Original-Status&#039;] &#061; str(status)<br \/>\n            compatible_response[&#039;headers&#039;][&#039;X-Compatible-Response&#039;] &#061; &#039;true&#039;<\/p>\n<p>            return compatible_response<\/p>\n<p>        return original_response<\/p>\n<p>    def adapt_body(self, original_body, original_status):<br \/>\n        &#034;&#034;&#034;\u9002\u914d\u54cd\u5e94\u4f53&#034;&#034;&#034;<br \/>\n        if original_status &#061;&#061; 202:<br \/>\n            # \u5f02\u6b65\u4efb\u52a1\u4fe1\u606f<br \/>\n            return {<br \/>\n                &#039;message&#039;: &#039;Request accepted and will be processed&#039;,<br \/>\n                &#039;original_response&#039;: original_body<br \/>\n            }<\/p>\n<p>        elif original_status &#061;&#061; 206:<br \/>\n            # \u90e8\u5206\u5185\u5bb9 -&gt; \u5b8c\u6574\u5185\u5bb9&#xff08;\u5982\u679c\u53ef\u80fd&#xff09;<br \/>\n            return original_body  # \u6ce8\u610f&#xff1a;\u8fd9\u91cc\u5e94\u8be5\u8fd4\u56de\u5b8c\u6574\u5185\u5bb9<\/p>\n<p>        elif original_status &#061;&#061; 207:<br \/>\n            # \u591a\u72b6\u6001 -&gt; \u7b2c\u4e00\u4e2a\u6210\u529f\u7684\u7ed3\u679c<br \/>\n            if isinstance(original_body, dict) and &#039;responses&#039; in original_body:<br \/>\n                for resp in original_body[&#039;responses&#039;]:<br \/>\n                    if resp.get(&#039;status&#039;, 200) &lt; 300:<br \/>\n                        return resp<br \/>\n            return original_body<\/p>\n<p>        else:<br \/>\n            return original_body<\/p>\n<p>    def adapt_headers(self, original_headers, original_status):<br \/>\n        &#034;&#034;&#034;\u9002\u914d\u54cd\u5e94\u5934\u90e8&#034;&#034;&#034;<br \/>\n        headers &#061; dict(original_headers)<\/p>\n<p>        # \u79fb\u9664\u7279\u5b9a\u72b6\u6001\u7801\u7684\u4e13\u7528\u5934\u90e8<br \/>\n        if original_status &#061;&#061; 206:<br \/>\n            headers.pop(&#039;Content-Range&#039;, None)<br \/>\n            headers.pop(&#039;Accept-Ranges&#039;, None)<\/p>\n<p>        elif original_status &#061;&#061; 226:<br \/>\n            headers.pop(&#039;IM&#039;, None)<br \/>\n            headers.pop(&#039;Delta-Base&#039;, None)<\/p>\n<p>        # \u66f4\u65b0Content-Length&#xff08;\u5982\u679c\u9700\u8981&#xff09;<br \/>\n        return headers<\/p>\n<h4>\u5ba2\u6237\u7aef\u517c\u5bb9\u6027\u68c0\u6d4b<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u5ba2\u6237\u7aef\u517c\u5bb9\u6027\u68c0\u6d4b<br \/>\nclass ClientCompatibilityChecker {<br \/>\n    \/**<br \/>\n     * \u68c0\u6d4b\u5ba2\u6237\u7aef\u5bf92xx\u72b6\u6001\u7801\u7684\u652f\u6301<br \/>\n     *\/<\/p>\n<p>    constructor() {<br \/>\n        this.supportedCodes &#061; new Set([200, 201, 204]); \/\/ \u57fa\u672c\u652f\u6301<br \/>\n        this.testResults &#061; {};<br \/>\n    }<\/p>\n<p>    async testStatusCodeSupport() {<br \/>\n        console.log(&#039;Testing HTTP status code support&#8230;&#039;);<\/p>\n<p>        const codesToTest &#061; [202, 203, 205, 206, 207, 208, 226];<\/p>\n<p>        for (const code of codesToTest) {<br \/>\n            try {<br \/>\n                const supported &#061; await this.testSingleCode(code);<br \/>\n                this.testResults[code] &#061; supported;<\/p>\n<p>                if (supported) {<br \/>\n                    this.supportedCodes.add(code);<br \/>\n                }<\/p>\n<p>                console.log(&#096;Status ${code}: ${supported ? &#039;Supported&#039; : &#039;Not supported&#039;}&#096;);<br \/>\n            } catch (error) {<br \/>\n                console.error(&#096;Test failed for ${code}:&#096;, error);<br \/>\n                this.testResults[code] &#061; false;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        this.saveResults();<br \/>\n        return this.supportedCodes;<br \/>\n    }<\/p>\n<p>    async testSingleCode(statusCode) {<br \/>\n        return new Promise((resolve, reject) &#061;&gt; {<br \/>\n            \/\/ \u521b\u5efa\u4e00\u4e2a\u6d4b\u8bd5\u7aef\u70b9URL<br \/>\n            const testUrl &#061; &#096;\/api\/test\/status\/${statusCode}&#096;;<\/p>\n<p>            \/\/ \u4f7f\u7528fetch\u6d4b\u8bd5<br \/>\n            if (window.fetch) {<br \/>\n                fetch(testUrl)<br \/>\n                    .then(response &#061;&gt; {<br \/>\n                        \/\/ \u68c0\u67e5\u662f\u5426\u6536\u5230\u4e86\u6b63\u786e\u7684\u72b6\u6001\u7801<br \/>\n                        const supported &#061; response.status &#061;&#061;&#061; statusCode;<br \/>\n                        resolve(supported);<br \/>\n                    })<br \/>\n                    .catch(reject);<br \/>\n            }<br \/>\n            \/\/ \u4f7f\u7528XMLHttpRequest\u6d4b\u8bd5<br \/>\n            else if (window.XMLHttpRequest) {<br \/>\n                const xhr &#061; new XMLHttpRequest();<br \/>\n                xhr.open(&#039;GET&#039;, testUrl);<br \/>\n                xhr.onload &#061; () &#061;&gt; {<br \/>\n                    const supported &#061; xhr.status &#061;&#061;&#061; statusCode;<br \/>\n                    resolve(supported);<br \/>\n                };<br \/>\n                xhr.onerror &#061; reject;<br \/>\n                xhr.send();<br \/>\n            }<br \/>\n            \/\/ \u90fd\u4e0d\u652f\u6301<br \/>\n            else {<br \/>\n                resolve(false);<br \/>\n            }<br \/>\n        });<br \/>\n    }<\/p>\n<p>    saveResults() {<br \/>\n        localStorage.setItem(&#039;http_support&#039;, JSON.stringify({<br \/>\n            supportedCodes: Array.from(this.supportedCodes),<br \/>\n            testResults: this.testResults,<br \/>\n            testDate: new Date().toISOString(),<br \/>\n            userAgent: navigator.userAgent<br \/>\n        }));<br \/>\n    }<\/p>\n<p>    loadResults() {<br \/>\n        const saved &#061; localStorage.getItem(&#039;http_support&#039;);<br \/>\n        if (saved) {<br \/>\n            const data &#061; JSON.parse(saved);<br \/>\n            this.supportedCodes &#061; new Set(data.supportedCodes);<br \/>\n            this.testResults &#061; data.testResults;<br \/>\n            return true;<br \/>\n        }<br \/>\n        return false;<br \/>\n    }<\/p>\n<p>    isSupported(statusCode) {<br \/>\n        return this.supportedCodes.has(statusCode);<br \/>\n    }<\/p>\n<p>    getRecommendation(statusCode) {<br \/>\n        if (this.isSupported(statusCode)) {<br \/>\n            return &#096;Status ${statusCode} is supported by this client&#096;;<br \/>\n        } else {<br \/>\n            return &#096;Status ${statusCode} is not supported. Consider using alternative status ${this.getAlternative(statusCode)}&#096;;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    getAlternative(statusCode) {<br \/>\n        const alternatives &#061; {<br \/>\n            202: 200,  \/\/ \u5f02\u6b65 -&gt; \u6807\u51c6\u6210\u529f<br \/>\n            203: 200,  \/\/ \u975e\u6743\u5a01 -&gt; \u6807\u51c6\u6210\u529f<br \/>\n            205: 200,  \/\/ \u91cd\u7f6e -&gt; \u6807\u51c6\u6210\u529f<br \/>\n            206: 200,  \/\/ \u90e8\u5206 -&gt; \u5b8c\u6574<br \/>\n            207: 200,  \/\/ \u591a\u72b6\u6001 -&gt; \u5355\u4e2a\u72b6\u6001<br \/>\n            208: 200,  \/\/ \u5df2\u62a5\u544a -&gt; \u6807\u51c6\u6210\u529f<br \/>\n            226: 200   \/\/ IM\u5df2\u4f7f\u7528 -&gt; \u6807\u51c6\u6210\u529f<br \/>\n        };<\/p>\n<p>        return alternatives[statusCode] || 200;<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\ndocument.addEventListener(&#039;DOMContentLoaded&#039;, async () &#061;&gt; {<br \/>\n    const checker &#061; new ClientCompatibilityChecker();<\/p>\n<p>    \/\/ \u52a0\u8f7d\u4e4b\u524d\u7684\u7ed3\u679c<br \/>\n    if (!checker.loadResults()) {<br \/>\n        \/\/ \u9996\u6b21\u8fd0\u884c&#xff0c;\u8fdb\u884c\u6d4b\u8bd5<br \/>\n        await checker.testStatusCodeSupport();<br \/>\n    }<\/p>\n<p>    \/\/ \u68c0\u67e5\u7279\u5b9a\u72b6\u6001\u7801<br \/>\n    console.log(checker.getRecommendation(206)); \/\/ \u90e8\u5206\u5185\u5bb9<br \/>\n    console.log(checker.getRecommendation(202)); \/\/ \u5f02\u6b65\u63a5\u53d7<\/p>\n<p>    \/\/ \u6839\u636e\u652f\u6301\u60c5\u51b5\u8c03\u6574\u5e94\u7528\u884c\u4e3a<br \/>\n    if (!checker.isSupported(206)) {<br \/>\n        console.warn(&#039;Range requests not supported, using full downloads&#039;);<br \/>\n        \/\/ \u7981\u7528\u8303\u56f4\u8bf7\u6c42\u529f\u80fd<br \/>\n        disableRangeRequests();<br \/>\n    }<br \/>\n});<\/p>\n<p>function disableRangeRequests() {<br \/>\n    \/\/ \u7981\u7528\u8303\u56f4\u8bf7\u6c42\u7684\u5b9e\u73b0<br \/>\n    window.RangeRequestClient &#061; class {<br \/>\n        downloadFile(url) {<br \/>\n            \/\/ \u603b\u662f\u4e0b\u8f7d\u5b8c\u6574\u6587\u4ef6<br \/>\n            return fetch(url).then(response &#061;&gt; response.blob());<br \/>\n        }<br \/>\n    };<br \/>\n}<\/p>\n<h3>9.10 \u6700\u4f73\u5b9e\u8df5\u603b\u7ed3<\/h3>\n<h4>\u670d\u52a1\u5668\u7aef\u6700\u4f73\u5b9e\u8df5<\/h4>\n<li>\n<p>\u6b63\u786e\u9009\u62e9\u72b6\u6001\u7801&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u6839\u636e\u64cd\u4f5c\u8bed\u4e49\u9009\u62e9\u6700\u5408\u9002\u76842xx\u72b6\u6001\u7801<\/p>\n<\/li>\n<li>\n<p>\u4fdd\u6301\u4e00\u81f4\u6027&#xff1a;\u76f8\u540c\u64cd\u4f5c\u4f7f\u7528\u76f8\u540c\u72b6\u6001\u7801<\/p>\n<\/li>\n<li>\n<p>\u8003\u8651\u5ba2\u6237\u7aef\u517c\u5bb9\u6027&#xff0c;\u5fc5\u8981\u65f6\u63d0\u4f9b\u540e\u5907<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u54cd\u5e94\u683c\u5f0f&#xff1a;<\/p>\n<ul>\n<li>\n<p>200\/201&#xff1a;\u5305\u542b\u5b8c\u6574\u7684\u8d44\u6e90\u8868\u793a<\/p>\n<\/li>\n<li>\n<p>202&#xff1a;\u5305\u542b\u4efb\u52a1\u72b6\u6001\u548c\u8ddf\u8e2a\u4fe1\u606f<\/p>\n<\/li>\n<li>\n<p>203&#xff1a;\u6dfb\u52a0Warning\u5934\u90e8\u8bf4\u660e\u8f6c\u6362<\/p>\n<\/li>\n<li>\n<p>204&#xff1a;\u65e0\u54cd\u5e94\u4f53&#xff0c;\u4f46\u53ef\u4ee5\u5305\u542b\u6709\u7528\u7684\u5934\u90e8<\/p>\n<\/li>\n<li>\n<p>205&#xff1a;\u65e0\u54cd\u5e94\u4f53&#xff0c;\u5ba2\u6237\u7aef\u5e94\u91cd\u7f6e<\/p>\n<\/li>\n<li>\n<p>206&#xff1a;\u5fc5\u987b\u5305\u542bContent-Range\u5934\u90e8<\/p>\n<\/li>\n<li>\n<p>207&#xff1a;\u5fc5\u987b\u662fXML\u683c\u5f0f<\/p>\n<\/li>\n<li>\n<p>226&#xff1a;\u5fc5\u987b\u5305\u542b\u589e\u91cf\u6570\u636e<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u7f13\u5b58\u63a7\u5236&#xff1a;<\/p>\n<ul>\n<li>\n<p>200\/206&#xff1a;\u53ef\u7f13\u5b58&#xff0c;\u8bbe\u7f6e\u9002\u5f53Cache-Control<\/p>\n<\/li>\n<li>\n<p>201\/202\/204\/205&#xff1a;\u901a\u5e38\u4e0d\u7f13\u5b58<\/p>\n<\/li>\n<li>\n<p>207\/208\/226&#xff1a;\u6839\u636e\u5185\u5bb9\u51b3\u5b9a\u7f13\u5b58\u7b56\u7565<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u5b89\u5168\u6027&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u9a8c\u8bc1\u6240\u6709\u8f93\u5165<\/p>\n<\/li>\n<li>\n<p>\u68c0\u67e5\u6743\u9650<\/p>\n<\/li>\n<li>\n<p>\u8bb0\u5f55\u654f\u611f\u64cd\u4f5c<\/p>\n<\/li>\n<li>\n<p>\u8bbe\u7f6e\u5b89\u5168\u5934\u90e8<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<h4>\u5ba2\u6237\u7aef\u6700\u4f73\u5b9e\u8df5<\/h4>\n<li>\n<p>\u5904\u7406\u6240\u67092xx\u72b6\u6001\u7801&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u4e0d\u8981\u53ea\u5904\u7406200<\/p>\n<\/li>\n<li>\n<p>\u7406\u89e3\u6bcf\u4e2a\u72b6\u6001\u7801\u7684\u8bed\u4e49<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u9002\u5f53\u7684\u5904\u7406\u903b\u8f91<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u9519\u8bef\u5904\u7406&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u5373\u4f7f2xx\u54cd\u5e94\u4e5f\u53ef\u80fd\u5305\u542b\u90e8\u5206\u9519\u8bef&#xff08;\u5982207&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u68c0\u67e5\u54cd\u5e94\u5934\u90e8\u83b7\u53d6\u989d\u5916\u4fe1\u606f<\/p>\n<\/li>\n<li>\n<p>\u5b9e\u73b0\u91cd\u8bd5\u548c\u56de\u9000\u673a\u5236<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u6027\u80fd\u4f18\u5316&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u5229\u7528206\u8fdb\u884c\u5206\u5757\u4e0b\u8f7d<\/p>\n<\/li>\n<li>\n<p>\u5229\u7528226\u8fdb\u884c\u589e\u91cf\u66f4\u65b0<\/p>\n<\/li>\n<li>\n<p>\u7f13\u5b58\u9002\u5f53\u7684\u54cd\u5e94<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u7528\u6237\u754c\u9762&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u6839\u636e\u72b6\u6001\u7801\u63d0\u4f9b\u9002\u5f53\u7684\u7528\u6237\u53cd\u9988<\/p>\n<\/li>\n<li>\n<p>\u91cd\u7f6e\u8868\u5355&#xff08;205&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u663e\u793a\u4efb\u52a1\u8fdb\u5ea6&#xff08;202&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u5904\u7406\u90e8\u5206\u6210\u529f&#xff08;207&#xff09;<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<h4>\u76d1\u63a7\u548c\u8fd0\u7ef4\u6700\u4f73\u5b9e\u8df5<\/h4>\n<li>\n<p>\u76d1\u63a7&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u8ddf\u8e2a\u5404\u72b6\u6001\u7801\u7684\u6bd4\u4f8b<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7\u54cd\u5e94\u65f6\u95f4<\/p>\n<\/li>\n<li>\n<p>\u8bbe\u7f6e\u544a\u8b66\u9608\u503c<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u65e5\u5fd7\u8bb0\u5f55&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u8bb0\u5f55\u6240\u6709\u72b6\u6001\u7801<\/p>\n<\/li>\n<li>\n<p>\u5305\u542b\u8bf7\u6c42ID\u548c\u4e0a\u4e0b\u6587<\/p>\n<\/li>\n<li>\n<p>\u7ed3\u6784\u5316\u65e5\u5fd7\u4fbf\u4e8e\u5206\u6790<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u6587\u6863&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u6587\u6863\u5316\u6240\u6709\u4f7f\u7528\u7684\u72b6\u6001\u7801<\/p>\n<\/li>\n<li>\n<p>\u63d0\u4f9b\u793a\u4f8b\u8bf7\u6c42\u548c\u54cd\u5e94<\/p>\n<\/li>\n<li>\n<p>\u8bf4\u660e\u9519\u8bef\u5904\u7406\u65b9\u5f0f<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u6d4b\u8bd5&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u6d4b\u8bd5\u6240\u6709\u72b6\u6001\u7801\u8def\u5f84<\/p>\n<\/li>\n<li>\n<p>\u6d4b\u8bd5\u5ba2\u6237\u7aef\u517c\u5bb9\u6027<\/p>\n<\/li>\n<li>\n<p>\u6d4b\u8bd5\u9519\u8bef\u548c\u8fb9\u754c\u60c5\u51b5<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<h3>\u7ed3\u8bed<\/h3>\n<p>2xx\u72b6\u6001\u7801\u7cfb\u5217\u63d0\u4f9b\u4e86\u4e30\u5bcc\u7684\u8bed\u4e49\u6765\u8868\u8fbe\u4e0d\u540c\u7c7b\u578b\u7684\u6210\u529f\u54cd\u5e94\u3002\u6b63\u786e\u4f7f\u7528\u8fd9\u4e9b\u72b6\u6001\u7801\u53ef\u4ee5&#xff1a;<\/p>\n<li>\n<p>\u63d0\u9ad8API\u7684\u6e05\u6670\u5ea6&#xff1a;\u5ba2\u6237\u7aef\u53ef\u4ee5\u66f4\u7cbe\u786e\u5730\u7406\u89e3\u54cd\u5e94\u542b\u4e49<\/p>\n<\/li>\n<li>\n<p>\u4f18\u5316\u6027\u80fd&#xff1a;\u901a\u8fc7\u90e8\u5206\u5185\u5bb9\u3001\u589e\u91cf\u7f16\u7801\u7b49\u51cf\u5c11\u6570\u636e\u4f20\u8f93<\/p>\n<\/li>\n<li>\n<p>\u6539\u5584\u7528\u6237\u4f53\u9a8c&#xff1a;\u901a\u8fc7\u9002\u5f53\u7684\u53cd\u9988\u548c\u72b6\u6001\u7ba1\u7406<\/p>\n<\/li>\n<li>\n<p>\u589e\u5f3a\u53ef\u7ef4\u62a4\u6027&#xff1a;\u6e05\u6670\u7684\u8bed\u4e49\u4f7f\u4ee3\u7801\u66f4\u6613\u4e8e\u7406\u89e3\u548c\u7ef4\u62a4<\/p>\n<\/li>\n<p>\u867d\u7136200 OK\u8db3\u4ee5\u8868\u793a\u4efb\u4f55\u6210\u529f&#xff0c;\u4f46\u4f7f\u7528\u66f4\u5177\u4f53\u7684\u72b6\u6001\u7801\u53ef\u4ee5\u8ba9HTTP API\u66f4\u52a0\u4f18\u96c5\u548c\u5f3a\u5927\u3002\u5728\u9009\u62e9\u72b6\u6001\u7801\u65f6&#xff0c;\u59cb\u7ec8\u8003\u8651&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u64cd\u4f5c\u7684\u8bed\u4e49<\/p>\n<\/li>\n<li>\n<p>\u5ba2\u6237\u7aef\u7684\u671f\u671b<\/p>\n<\/li>\n<li>\n<p>\u6027\u80fd\u5f71\u54cd<\/p>\n<\/li>\n<li>\n<p>\u517c\u5bb9\u6027\u8981\u6c42<\/p>\n<\/li>\n<\/ul>\n<p>\u901a\u8fc7\u672c\u7ae0\u7684\u8be6\u7ec6\u89e3\u6790&#xff0c;\u60a8\u73b0\u5728\u5e94\u8be5\u80fd\u591f&#xff1a;<\/p>\n<li>\n<p>\u7406\u89e3\u6240\u67092xx\u72b6\u6001\u7801\u7684\u7ec6\u5fae\u5dee\u522b<\/p>\n<\/li>\n<li>\n<p>\u5728\u5b9e\u9645\u9879\u76ee\u4e2d\u6b63\u786e\u5b9e\u73b0\u8fd9\u4e9b\u72b6\u6001\u7801<\/p>\n<\/li>\n<li>\n<p>\u5904\u7406\u5ba2\u6237\u7aef\u517c\u5bb9\u6027\u95ee\u9898<\/p>\n<\/li>\n<li>\n<p>\u76d1\u63a7\u548c\u8c03\u8bd5\u72b6\u6001\u7801\u4f7f\u7528\u60c5\u51b5<\/p>\n<\/li>\n","protected":false},"excerpt":{"rendered":"<p>\u7b2c8\u7ae0&#xff1a;206 Partial Content8.1 206 Partial Content\u7684\u8bed\u4e49\u4e0e\u4f7f\u7528\u573a\u666fRFC\u6807\u51c6\u5b9a\u4e49\u6df1\u5ea6\u89e3\u6790\u6839\u636eRFC 7233\u7b2c4.1\u8282&#xff0c;206 Partial Content\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;\\&#8221;206 (Partial Content)\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u6b63\u5728\u6210\u529f\u6267\u884c\u4e00\u4e2a\u6216\u591a\u4e2a\u5b57\u8282\u8303\u56f4\u8bf7\u6c42&#xff0c;\u56e0\u4e3a\u8bf7\u6c42\u5305\u542bRange\u8bf7\u6c42\u5934\u90e8\u5b57\u6bb5&#xff08;\u7b2c3.1\u8282&#xff09;\u3002\u54cd\u5e94\u5fc5\u987b\u5305\u542bContent-Ra<\/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":[230,190,78],"topic":[],"class_list":["post-82295","post","type-post","status-publish","format-standard","hentry","category-server","tag-php","tag-190","tag-78"],"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\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e8c\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\/82295.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\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e8c\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"og:description\" content=\"\u7b2c8\u7ae0&#xff1a;206 Partial Content8.1 206 Partial Content\u7684\u8bed\u4e49\u4e0e\u4f7f\u7528\u573a\u666fRFC\u6807\u51c6\u5b9a\u4e49\u6df1\u5ea6\u89e3\u6790\u6839\u636eRFC 7233\u7b2c4.1\u8282&#xff0c;206 Partial Content\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;&quot;206 (Partial Content)\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u6b63\u5728\u6210\u529f\u6267\u884c\u4e00\u4e2a\u6216\u591a\u4e2a\u5b57\u8282\u8303\u56f4\u8bf7\u6c42&#xff0c;\u56e0\u4e3a\u8bf7\u6c42\u5305\u542bRange\u8bf7\u6c42\u5934\u90e8\u5b57\u6bb5&#xff08;\u7b2c3.1\u8282&#xff09;\u3002\u54cd\u5e94\u5fc5\u987b\u5305\u542bContent-Ra\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.wsisp.com\/helps\/82295.html\" \/>\n<meta property=\"og:site_name\" content=\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"article:published_time\" content=\"2026-07-24T23:43:35+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=\"81 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/82295.html\",\"url\":\"https:\/\/www.wsisp.com\/helps\/82295.html\",\"name\":\"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e8c\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"isPartOf\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\"},\"datePublished\":\"2026-07-24T23:43:35+00:00\",\"dateModified\":\"2026-07-24T23:43:35+00:00\",\"author\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/82295.html#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.wsisp.com\/helps\/82295.html\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/82295.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\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e8c\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\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e8c\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\/82295.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\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e8c\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","og_description":"\u7b2c8\u7ae0&#xff1a;206 Partial Content8.1 206 Partial Content\u7684\u8bed\u4e49\u4e0e\u4f7f\u7528\u573a\u666fRFC\u6807\u51c6\u5b9a\u4e49\u6df1\u5ea6\u89e3\u6790\u6839\u636eRFC 7233\u7b2c4.1\u8282&#xff0c;206 Partial Content\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;\"206 (Partial Content)\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u6b63\u5728\u6210\u529f\u6267\u884c\u4e00\u4e2a\u6216\u591a\u4e2a\u5b57\u8282\u8303\u56f4\u8bf7\u6c42&#xff0c;\u56e0\u4e3a\u8bf7\u6c42\u5305\u542bRange\u8bf7\u6c42\u5934\u90e8\u5b57\u6bb5&#xff08;\u7b2c3.1\u8282&#xff09;\u3002\u54cd\u5e94\u5fc5\u987b\u5305\u542bContent-Ra","og_url":"https:\/\/www.wsisp.com\/helps\/82295.html","og_site_name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","article_published_time":"2026-07-24T23:43:35+00:00","author":"admin","twitter_card":"summary_large_image","twitter_misc":{"\u4f5c\u8005":"admin","\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"81 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.wsisp.com\/helps\/82295.html","url":"https:\/\/www.wsisp.com\/helps\/82295.html","name":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e8c\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","isPartOf":{"@id":"https:\/\/www.wsisp.com\/helps\/#website"},"datePublished":"2026-07-24T23:43:35+00:00","dateModified":"2026-07-24T23:43:35+00:00","author":{"@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41"},"breadcrumb":{"@id":"https:\/\/www.wsisp.com\/helps\/82295.html#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.wsisp.com\/helps\/82295.html"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.wsisp.com\/helps\/82295.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\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e8c\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\/82295","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=82295"}],"version-history":[{"count":0,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/82295\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media?parent=82295"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/categories?post=82295"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/tags?post=82295"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/topic?post=82295"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}