Skip to content

Commit 3d97de5

Browse files
committed
Add conditional HTTP requests and cache validators
http_request never sent If-None-Match / If-Modified-Since nor read Cache-Control, so every poll re-downloaded an unchanged resource. Add store_validators, parse_cache_control, conditioned_call, is_fresh (by an explicit age), and is_not_modified so a flow can revalidate and honour 304 Not Modified. Wired through facade, executor (AC_parse_cache_control / AC_store_validators), MCP, and the Script Builder with a headless test batch and EN/Zh docs.
1 parent beca532 commit 3d97de5

15 files changed

Lines changed: 356 additions & 1 deletion

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
## Table of Contents
1515

16+
- [What's new (2026-06-22) — Conditional HTTP Requests & Cache Validators](#whats-new-2026-06-22--conditional-http-requests--cache-validators)
1617
- [What's new (2026-06-22) — Cookie Jar (HTTP Session Carry)](#whats-new-2026-06-22--cookie-jar-http-session-carry)
1718
- [What's new (2026-06-22) — HTTP Content Negotiation & Decompression](#whats-new-2026-06-22--http-content-negotiation--decompression)
1819
- [What's new (2026-06-22) — multipart/form-data Build & Parse](#whats-new-2026-06-22--multipartform-data-build--parse)
@@ -144,6 +145,12 @@
144145

145146
---
146147

148+
## What's new (2026-06-22) — Conditional HTTP Requests & Cache Validators
149+
150+
Skip re-downloading unchanged resources (ETag / 304). Full reference: [`docs/source/Eng/doc/new_features/v92_features_doc.rst`](docs/source/Eng/doc/new_features/v92_features_doc.rst).
151+
152+
- **`store_validators` / `conditioned_call` / `is_fresh` / `parse_cache_control` / `is_not_modified`** (`AC_parse_cache_control`, `AC_store_validators`): `http_request` never sent `If-None-Match`/`If-Modified-Since` nor read `Cache-Control`, so every poll re-downloaded. This extracts validators, parses `Cache-Control` (max-age/no-store/…), decides freshness by an explicit age, conditions the next request, and detects `304 Not Modified`. Pure-stdlib, deterministic.
153+
147154
## What's new (2026-06-22) — Cookie Jar (HTTP Session Carry)
148155

149156
Carry a session across HTTP calls. Full reference: [`docs/source/Eng/doc/new_features/v91_features_doc.rst`](docs/source/Eng/doc/new_features/v91_features_doc.rst).

README/README_zh-CN.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目录
1414

15+
- [本次更新 (2026-06-22) — 条件式 HTTP 请求与缓存验证子](#本次更新-2026-06-22--条件式-http-请求与缓存验证子)
1516
- [本次更新 (2026-06-22) — Cookie Jar(HTTP 会话携带)](#本次更新-2026-06-22--cookie-jarhttp-会话携带)
1617
- [本次更新 (2026-06-22) — HTTP 内容协商与解压](#本次更新-2026-06-22--http-内容协商与解压)
1718
- [本次更新 (2026-06-22) — multipart/form-data 构建与解析](#本次更新-2026-06-22--multipartform-data-构建与解析)
@@ -143,6 +144,12 @@
143144

144145
---
145146

147+
## 本次更新 (2026-06-22) — 条件式 HTTP 请求与缓存验证子
148+
149+
跳过重新下载未变更的资源(ETag / 304)。完整参考:[`docs/source/Zh/doc/new_features/v92_features_doc.rst`](../docs/source/Zh/doc/new_features/v92_features_doc.rst)
150+
151+
- **`store_validators` / `conditioned_call` / `is_fresh` / `parse_cache_control` / `is_not_modified`**(`AC_parse_cache_control``AC_store_validators`):`http_request` 从不发 `If-None-Match`/`If-Modified-Since` 也不读 `Cache-Control`,因此每次轮询都重新下载。本功能提取验证子、解析 `Cache-Control`(max-age/no-store/…)、以明确 age 判定新鲜度、为下一个请求加上条件标头,并检测 `304 Not Modified`。纯标准库、确定。
152+
146153
## 本次更新 (2026-06-22) — Cookie Jar(HTTP 会话携带)
147154

148155
跨 HTTP 调用携带会话。完整参考:[`docs/source/Zh/doc/new_features/v91_features_doc.rst`](../docs/source/Zh/doc/new_features/v91_features_doc.rst)

README/README_zh-TW.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目錄
1414

15+
- [本次更新 (2026-06-22) — 條件式 HTTP 請求與快取驗證子](#本次更新-2026-06-22--條件式-http-請求與快取驗證子)
1516
- [本次更新 (2026-06-22) — Cookie Jar(HTTP 工作階段攜帶)](#本次更新-2026-06-22--cookie-jarhttp-工作階段攜帶)
1617
- [本次更新 (2026-06-22) — HTTP 內容協商與解壓縮](#本次更新-2026-06-22--http-內容協商與解壓縮)
1718
- [本次更新 (2026-06-22) — multipart/form-data 建立與解析](#本次更新-2026-06-22--multipartform-data-建立與解析)
@@ -143,6 +144,12 @@
143144

144145
---
145146

147+
## 本次更新 (2026-06-22) — 條件式 HTTP 請求與快取驗證子
148+
149+
略過重新下載未變更的資源(ETag / 304)。完整參考:[`docs/source/Zh/doc/new_features/v92_features_doc.rst`](../docs/source/Zh/doc/new_features/v92_features_doc.rst)
150+
151+
- **`store_validators` / `conditioned_call` / `is_fresh` / `parse_cache_control` / `is_not_modified`**(`AC_parse_cache_control``AC_store_validators`):`http_request` 從不送 `If-None-Match`/`If-Modified-Since` 也不讀 `Cache-Control`,因此每次輪詢都重新下載。本功能擷取驗證子、解析 `Cache-Control`(max-age/no-store/…)、以明確 age 判定新鮮度、為下一個請求加上條件標頭,並偵測 `304 Not Modified`。純標準函式庫、具決定性。
152+
146153
## 本次更新 (2026-06-22) — Cookie Jar(HTTP 工作階段攜帶)
147154

148155
跨 HTTP 呼叫攜帶工作階段。完整參考:[`docs/source/Zh/doc/new_features/v91_features_doc.rst`](../docs/source/Zh/doc/new_features/v91_features_doc.rst)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
Conditional HTTP Requests & Cache Validators
2+
============================================
3+
4+
``http_request`` never sends ``If-None-Match`` / ``If-Modified-Since`` nor reads
5+
``Cache-Control``, so every poll re-downloads an unchanged resource. This
6+
extracts caching validators from a response, parses ``Cache-Control``, decides
7+
freshness, and conditions the next request so the server can answer ``304 Not
8+
Modified``.
9+
10+
Pure standard library; imports no ``PySide6``. Freshness takes an explicit age
11+
(no wall clock), so the logic is fully deterministic in CI.
12+
13+
Headless API
14+
------------
15+
16+
.. code-block:: python
17+
18+
from je_auto_control import (
19+
store_validators, conditioned_call, is_fresh, is_not_modified,
20+
parse_cache_control, build_call,
21+
)
22+
23+
response = http_request(url)
24+
validators = store_validators(response)
25+
if is_fresh(validators, age_seconds=now - stored_at):
26+
use_cached()
27+
else:
28+
revalidation = conditioned_call(build_call(url), validators)
29+
fresh = perform(revalidation)
30+
if is_not_modified(fresh):
31+
use_cached() # 304 → the stored body is still valid
32+
33+
``store_validators`` pulls ``etag`` / ``last_modified`` / ``date`` and the parsed
34+
``cache_control`` from a response. ``parse_cache_control`` turns the header into
35+
a directive dict (``max-age`` as an int, flags as ``True``). ``conditioned_call``
36+
adds ``If-None-Match`` / ``If-Modified-Since`` to a ``build_call`` dict.
37+
``is_fresh`` reports whether a cached entry is still fresh for a given age
38+
(``no-store`` / ``no-cache`` are never fresh). ``is_not_modified`` detects a
39+
``304`` response.
40+
41+
Executor commands
42+
-----------------
43+
44+
``AC_parse_cache_control`` returns ``{directives}`` for ``headers``;
45+
``AC_store_validators`` returns ``{validators}`` for a ``response``. Both are
46+
exposed as MCP tools (``ac_parse_cache_control`` / ``ac_store_validators``) and
47+
as Script Builder commands under **Data**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Comprehensive guides for all AutoControl features.
114114
doc/new_features/v89_features_doc
115115
doc/new_features/v90_features_doc
116116
doc/new_features/v91_features_doc
117+
doc/new_features/v92_features_doc
117118
doc/ocr_backends/ocr_backends_doc
118119
doc/observability/observability_doc
119120
doc/operations_layer/operations_layer_doc
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
條件式 HTTP 請求與快取驗證子
2+
========================
3+
4+
``http_request`` 從不送出 ``If-None-Match`` / ``If-Modified-Since`` 也不讀取 ``Cache-Control``,因此每次
5+
輪詢都會重新下載未變更的資源。本功能從回應擷取快取驗證子、解析 ``Cache-Control``、判定新鮮度,並為下一個
6+
請求加上條件標頭,讓伺服器可回應 ``304 Not Modified``。
7+
8+
純標準函式庫;不匯入 ``PySide6``。新鮮度判定接受明確的 age(不使用 wall clock),因此邏輯在 CI 中完全
9+
具決定性。
10+
11+
無頭 API
12+
--------
13+
14+
.. code-block:: python
15+
16+
from je_auto_control import (
17+
store_validators, conditioned_call, is_fresh, is_not_modified,
18+
parse_cache_control, build_call,
19+
)
20+
21+
response = http_request(url)
22+
validators = store_validators(response)
23+
if is_fresh(validators, age_seconds=now - stored_at):
24+
use_cached()
25+
else:
26+
revalidation = conditioned_call(build_call(url), validators)
27+
fresh = perform(revalidation)
28+
if is_not_modified(fresh):
29+
use_cached() # 304 → 已儲存的內文仍有效
30+
31+
``store_validators`` 從回應取出 ``etag`` / ``last_modified`` / ``date`` 與解析後的 ``cache_control``。
32+
``parse_cache_control`` 把標頭轉成 directive dict(``max-age`` 為 int,旗標為 ``True``)。``conditioned_call``
33+
把 ``If-None-Match`` / ``If-Modified-Since`` 加到 ``build_call`` dict。``is_fresh`` 在給定 age 下回報快取項
34+
是否仍新鮮(``no-store`` / ``no-cache`` 永不新鮮)。``is_not_modified`` 偵測 ``304`` 回應。
35+
36+
執行器命令
37+
----------
38+
39+
``AC_parse_cache_control`` 對 ``headers`` 回傳 ``{directives}``;``AC_store_validators`` 對 ``response``
40+
回傳 ``{validators}``。兩者皆以 MCP 工具(``ac_parse_cache_control`` / ``ac_store_validators``)以及
41+
Script Builder 中 **Data** 分類下的命令提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ AutoControl 所有功能的完整使用指南。
114114
doc/new_features/v89_features_doc
115115
doc/new_features/v90_features_doc
116116
doc/new_features/v91_features_doc
117+
doc/new_features/v92_features_doc
117118
doc/ocr_backends/ocr_backends_doc
118119
doc/observability/observability_doc
119120
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,11 @@
431431
)
432432
# RFC 6265 cookie jar (carry a session across HTTP calls)
433433
from je_auto_control.utils.cookie_jar import CookieJar, parse_set_cookie
434+
# Conditional HTTP requests + RFC 9111 cache validators (ETag / 304)
435+
from je_auto_control.utils.http_conditional import (
436+
conditioned_call, is_fresh, is_not_modified, parse_cache_control,
437+
store_validators,
438+
)
434439
# W3C Trace Context propagation (traceparent / tracestate)
435440
from je_auto_control.utils.trace_context import (
436441
SpanContext, TraceContextError, child_context, extract_context,
@@ -958,6 +963,8 @@ def start_autocontrol_gui(*args, **kwargs):
958963
"build_accept", "build_accept_encoding", "decode_body", "negotiated_call",
959964
"parse_quality_values",
960965
"CookieJar", "parse_set_cookie",
966+
"conditioned_call", "is_fresh", "is_not_modified", "parse_cache_control",
967+
"store_validators",
961968
"SpanContext", "TraceContextError", "child_context", "extract_context",
962969
"format_traceparent", "format_tracestate", "inject_context",
963970
"new_root_context", "new_span_id", "new_trace_id", "parse_traceparent",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1800,6 +1800,22 @@ def _add_resilience_specs(specs: List[CommandSpec]) -> None:
18001800
),
18011801
description="Parse one Set-Cookie header into name/value/attributes.",
18021802
))
1803+
specs.append(CommandSpec(
1804+
"AC_parse_cache_control", "Data", "HTTP Conditional: Cache-Control",
1805+
fields=(
1806+
FieldSpec("headers", FieldType.STRING,
1807+
placeholder='{"Cache-Control": "max-age=60, public"}'),
1808+
),
1809+
description="Parse a Cache-Control header into directives.",
1810+
))
1811+
specs.append(CommandSpec(
1812+
"AC_store_validators", "Data", "HTTP Conditional: Store Validators",
1813+
fields=(
1814+
FieldSpec("response", FieldType.STRING,
1815+
placeholder='{"headers": {"ETag": "\\"abc\\""}}'),
1816+
),
1817+
description="Extract ETag / Last-Modified / Cache-Control validators.",
1818+
))
18031819
specs.append(CommandSpec(
18041820
"AC_resolve_config", "Data", "Layered Config: Resolve",
18051821
fields=(

je_auto_control/utils/executor/action_executor.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3030,6 +3030,24 @@ def _redact_secret_text(text: str, mask: str = "***") -> Dict[str, Any]:
30303030
return {"text": redact_secret_text(text, mask=mask)}
30313031

30323032

3033+
def _parse_cache_control(headers: Any) -> Dict[str, Any]:
3034+
"""Adapter: parse a Cache-Control header into {directives}."""
3035+
import json
3036+
from je_auto_control.utils.http_conditional import parse_cache_control
3037+
if isinstance(headers, str):
3038+
headers = json.loads(headers)
3039+
return {"directives": parse_cache_control(headers)}
3040+
3041+
3042+
def _store_validators(response: Any) -> Dict[str, Any]:
3043+
"""Adapter: extract cache validators from an HTTP response."""
3044+
import json
3045+
from je_auto_control.utils.http_conditional import store_validators
3046+
if isinstance(response, str):
3047+
response = json.loads(response)
3048+
return {"validators": store_validators(response)}
3049+
3050+
30333051
def _cookie_header(set_cookies: Any) -> Dict[str, Any]:
30343052
"""Adapter: build a Cookie header from one/many Set-Cookie strings."""
30353053
import json
@@ -4372,6 +4390,8 @@ def __init__(self):
43724390
"AC_parse_quality_values": _parse_quality_values,
43734391
"AC_cookie_header": _cookie_header,
43744392
"AC_parse_set_cookie": _parse_set_cookie,
4393+
"AC_parse_cache_control": _parse_cache_control,
4394+
"AC_store_validators": _store_validators,
43754395
"AC_profile_rows": _profile_rows,
43764396
"AC_infer_schema": _infer_schema,
43774397
"AC_parse_problem": _parse_problem,

0 commit comments

Comments
 (0)