syshttp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- syshttp-0.1.0/.gitignore +38 -0
- syshttp-0.1.0/CHANGELOG.md +343 -0
- syshttp-0.1.0/LICENSE +21 -0
- syshttp-0.1.0/PKG-INFO +219 -0
- syshttp-0.1.0/README.md +177 -0
- syshttp-0.1.0/docs/api/autopatch.md +3 -0
- syshttp-0.1.0/docs/api/client.md +15 -0
- syshttp-0.1.0/docs/api/cookies.md +3 -0
- syshttp-0.1.0/docs/api/env.md +3 -0
- syshttp-0.1.0/docs/api/exceptions.md +3 -0
- syshttp-0.1.0/docs/api/hooks.md +3 -0
- syshttp-0.1.0/docs/api/jar.md +3 -0
- syshttp-0.1.0/docs/api/proxy.md +3 -0
- syshttp-0.1.0/docs/api/tls_retry.md +5 -0
- syshttp-0.1.0/docs/changelog.md +1 -0
- syshttp-0.1.0/docs/index.md +22 -0
- syshttp-0.1.0/docs/settings.md +119 -0
- syshttp-0.1.0/mkdocs.yml +59 -0
- syshttp-0.1.0/pyproject.toml +85 -0
- syshttp-0.1.0/src/syshttp/AGENTS.md +817 -0
- syshttp-0.1.0/src/syshttp/__init__.py +71 -0
- syshttp-0.1.0/src/syshttp/_fileio.py +78 -0
- syshttp-0.1.0/src/syshttp/_parse.py +30 -0
- syshttp-0.1.0/src/syshttp/auth.py +17 -0
- syshttp-0.1.0/src/syshttp/autopatch.py +382 -0
- syshttp-0.1.0/src/syshttp/client/__init__.py +70 -0
- syshttp-0.1.0/src/syshttp/client/_body.py +156 -0
- syshttp-0.1.0/src/syshttp/client/_client.py +413 -0
- syshttp-0.1.0/src/syshttp/client/_context.py +107 -0
- syshttp-0.1.0/src/syshttp/client/_deadline.py +94 -0
- syshttp-0.1.0/src/syshttp/client/_default.py +72 -0
- syshttp-0.1.0/src/syshttp/client/_fetch.py +98 -0
- syshttp-0.1.0/src/syshttp/client/_follow.py +243 -0
- syshttp-0.1.0/src/syshttp/client/_headers.py +238 -0
- syshttp-0.1.0/src/syshttp/client/_pool.py +170 -0
- syshttp-0.1.0/src/syshttp/client/_prepare.py +77 -0
- syshttp-0.1.0/src/syshttp/client/_response.py +301 -0
- syshttp-0.1.0/src/syshttp/client/_route.py +75 -0
- syshttp-0.1.0/src/syshttp/client/_settings.py +12 -0
- syshttp-0.1.0/src/syshttp/client/_state.py +322 -0
- syshttp-0.1.0/src/syshttp/client/_status.py +59 -0
- syshttp-0.1.0/src/syshttp/client/_tlstunnel.py +114 -0
- syshttp-0.1.0/src/syshttp/client/_transport.py +550 -0
- syshttp-0.1.0/src/syshttp/cookies.py +391 -0
- syshttp-0.1.0/src/syshttp/env.py +144 -0
- syshttp-0.1.0/src/syshttp/exceptions.py +29 -0
- syshttp-0.1.0/src/syshttp/hooks.py +314 -0
- syshttp-0.1.0/src/syshttp/jar.py +133 -0
- syshttp-0.1.0/src/syshttp/proxy.py +341 -0
- syshttp-0.1.0/src/syshttp/py.typed +0 -0
- syshttp-0.1.0/src/syshttp/retry.py +34 -0
- syshttp-0.1.0/src/syshttp/tls.py +60 -0
- syshttp-0.1.0/tests/client_fakes.py +129 -0
- syshttp-0.1.0/tests/client_wire.py +411 -0
- syshttp-0.1.0/tests/conftest.py +43 -0
- syshttp-0.1.0/tests/data/ca.pem +11 -0
- syshttp-0.1.0/tests/data/client-rsa.key +28 -0
- syshttp-0.1.0/tests/data/client-rsa.pem +16 -0
- syshttp-0.1.0/tests/data/server.key +28 -0
- syshttp-0.1.0/tests/data/server.pem +16 -0
- syshttp-0.1.0/tests/test_autopatch.py +389 -0
- syshttp-0.1.0/tests/test_client_credential_redirects.py +74 -0
- syshttp-0.1.0/tests/test_client_deadline.py +182 -0
- syshttp-0.1.0/tests/test_client_default.py +244 -0
- syshttp-0.1.0/tests/test_client_default_transport.py +165 -0
- syshttp-0.1.0/tests/test_client_errors.py +64 -0
- syshttp-0.1.0/tests/test_client_guards.py +106 -0
- syshttp-0.1.0/tests/test_client_paths.py +151 -0
- syshttp-0.1.0/tests/test_client_peer_text.py +61 -0
- syshttp-0.1.0/tests/test_client_redirect_origin.py +122 -0
- syshttp-0.1.0/tests/test_client_redirects.py +217 -0
- syshttp-0.1.0/tests/test_client_reply_bounds.py +73 -0
- syshttp-0.1.0/tests/test_client_reply_limits.py +189 -0
- syshttp-0.1.0/tests/test_client_response.py +68 -0
- syshttp-0.1.0/tests/test_client_routing.py +421 -0
- syshttp-0.1.0/tests/test_client_secure_cookies.py +116 -0
- syshttp-0.1.0/tests/test_client_state.py +372 -0
- syshttp-0.1.0/tests/test_client_tls.py +264 -0
- syshttp-0.1.0/tests/test_client_urls.py +64 -0
- syshttp-0.1.0/tests/test_client_write_redirects.py +229 -0
- syshttp-0.1.0/tests/test_cookie_hosts.py +114 -0
- syshttp-0.1.0/tests/test_cookie_injection.py +162 -0
- syshttp-0.1.0/tests/test_cookies.py +363 -0
- syshttp-0.1.0/tests/test_edges.py +69 -0
- syshttp-0.1.0/tests/test_env.py +188 -0
- syshttp-0.1.0/tests/test_exceptions.py +113 -0
- syshttp-0.1.0/tests/test_fetch.py +274 -0
- syshttp-0.1.0/tests/test_jar_files.py +137 -0
- syshttp-0.1.0/tests/test_jars_proxy.py +247 -0
- syshttp-0.1.0/tests/test_os_context.py +93 -0
- syshttp-0.1.0/tests/test_proxy.py +106 -0
- syshttp-0.1.0/tests/test_proxy_auth.py +409 -0
- syshttp-0.1.0/tests/test_retry.py +50 -0
- syshttp-0.1.0/tests/test_switches.py +153 -0
- syshttp-0.1.0/tests/test_system_proxy.py +234 -0
- syshttp-0.1.0/tests/test_transport_body.py +294 -0
- syshttp-0.1.0/tests/test_transport_pool.py +411 -0
- syshttp-0.1.0/tests/test_transport_prepare.py +217 -0
- syshttp-0.1.0/tests/test_transport_send.py +630 -0
- syshttp-0.1.0/tests/test_transport_tls.py +127 -0
- syshttp-0.1.0/tests/test_url_hooks.py +258 -0
syshttp-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Category rule: nothing dot-prefixed at the repo root gets tracked except the
|
|
2
|
+
# git config files and CI re-included below (a category beats a list).
|
|
3
|
+
/.*
|
|
4
|
+
!/.gitignore
|
|
5
|
+
!/.gitattributes
|
|
6
|
+
!/.github/
|
|
7
|
+
|
|
8
|
+
# Private agent config. No trailing slash on `.agents`: it is usually a
|
|
9
|
+
# symlink, which a directory-only pattern would not match. `*.local.*` is the
|
|
10
|
+
# unshared override and is never committed.
|
|
11
|
+
.agents
|
|
12
|
+
*.local.*
|
|
13
|
+
CLAUDE*
|
|
14
|
+
.claude
|
|
15
|
+
|
|
16
|
+
# Build/dist output and generated docs site
|
|
17
|
+
build/
|
|
18
|
+
dist/
|
|
19
|
+
site/
|
|
20
|
+
|
|
21
|
+
# Python
|
|
22
|
+
__pycache__/
|
|
23
|
+
*.py[cod]
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.hypothesis/
|
|
26
|
+
.mypy_cache/
|
|
27
|
+
.ruff_cache/
|
|
28
|
+
.coverage*
|
|
29
|
+
htmlcov/
|
|
30
|
+
*.egg-info/
|
|
31
|
+
.pyvenv/
|
|
32
|
+
.venv/
|
|
33
|
+
|
|
34
|
+
# OS/editor
|
|
35
|
+
.DS_Store
|
|
36
|
+
Thumbs.db
|
|
37
|
+
.idea/
|
|
38
|
+
.vscode/
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-10-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `syshttp.client`: an HTTP client on the standard library, so that no feature
|
|
15
|
+
of syshttp needs `requests` (which only `syshttp.autopatch` patches, in a
|
|
16
|
+
program that imports it). `HTTPClient(transport=None, *, headers, timeout,
|
|
17
|
+
deadline, user_agent, env, proxymap, ssl_context, proxy_ssl_context, verify,
|
|
18
|
+
cookies, tokens, auth_provider, retries, backoff, status_forcelist,
|
|
19
|
+
cookie_hosts)` sends through a transport and follows redirects with one rule
|
|
20
|
+
for credentials (`Authorization`, `Cookie` and `Proxy-Authorization` go to the
|
|
21
|
+
origin they were given for and to no other scheme, host or port). The default `HTTPTransport` (on `http.client`,
|
|
22
|
+
with a per-thread connection pool, framed bodies, `gzip` / `deflate`
|
|
23
|
+
decoding and a bounded reply size) routes every request and redirect hop by
|
|
24
|
+
`proxy.proxy_map(env=env)` (or `proxymap=`): the first entry that accepts a
|
|
25
|
+
connection, an `http` or `https` proxy (`CONNECT`; an `https` proxy over TLS
|
|
26
|
+
with the origin's TLS inside it), a URL-prefix gateway, a proxy's
|
|
27
|
+
credential to the proxy alone; and verifies with the caller's `ssl_context`,
|
|
28
|
+
else the `CA_BUNDLE` bundle or `verify=<path>`, else the system's context
|
|
29
|
+
(`proxy_ssl_context` for an `https` proxy or gateway). Cookies (RFC 6265,
|
|
30
|
+
per origin, on every hop, in a jar by host with `cookies=`), tokens, `_PY`
|
|
31
|
+
hooks, `auth_provider` and retries (`Retry-After` honoured, a status that
|
|
32
|
+
outlasts them returned) work as they did on the session. `HTTPResponse`
|
|
33
|
+
reads its body whole as `content`, `text()` and `json()`.
|
|
34
|
+
- `syshttp.fetch()` (defined in `syshttp.client`, re-exported from the root
|
|
35
|
+
without importing it): `fetch(url, method="GET", *, headers, data, json,
|
|
36
|
+
timeout, client, transport, reauth, **client_options)` returns the
|
|
37
|
+
`HTTPResponse` with its body read, whatever the status; a given `client` is
|
|
38
|
+
left open and a new one is closed; `reauth` runs once after a 401 or 403.
|
|
39
|
+
- The transport protocol is public: `Transport` (`send()` and `close()`),
|
|
40
|
+
`PreparedRequest` and `prepare_request()` give another transport (on
|
|
41
|
+
`requests`, `httpx`, or a fake) the checks and body framing of syshttp's own.
|
|
42
|
+
`SYSHTTP_DEFAULT_TRANSPORT` (`module:attr` or `<file>.py:attr`, read under the
|
|
43
|
+
prefixes) names the transport of a client given none; a value that cannot be
|
|
44
|
+
used is `ConfigurationError`. `hooks.load_object()` is the loader it shares
|
|
45
|
+
with the `_PY` hooks.
|
|
46
|
+
- `syshttp.exceptions`: `SysHTTPError` (the base of what goes wrong at run
|
|
47
|
+
time), `ConfigurationError` (a `ValueError`), `HookError` (a `RuntimeError`)
|
|
48
|
+
and `ResponseError` (a `ValueError`), exported from the root. A malformed
|
|
49
|
+
setting, a hook that cannot be loaded, a failing hook and a body that is not
|
|
50
|
+
JSON raise them; every one is still the `ValueError` or `RuntimeError` it
|
|
51
|
+
was, so existing handlers keep working. A caller's own mistake stays a plain
|
|
52
|
+
`ValueError` / `TypeError`.
|
|
53
|
+
- Every public module declares `__all__` (`proxy` also lists its setting-name
|
|
54
|
+
constants, `PROXY` and `DEFAULT_AUTH_HEADER` among them).
|
|
55
|
+
- `syshttp.env.HTTPEnv(prefixes=None, environ=None)`: every setting is a
|
|
56
|
+
variable with a full name -- syshttp's own carry `SYSHTTP_`
|
|
57
|
+
(`SYSHTTP_PROXY`, `SYSHTTP_HOME`, `SYSHTTP_HOOKS_<KEY>`,
|
|
58
|
+
`SYSHTTP_IMPORTPATCH`), conventional ones keep theirs (`HTTPS_PROXY`,
|
|
59
|
+
`NO_PROXY`, `HTTP_PROXY_AUTH`, `CA_BUNDLE`) -- read as `<PREFIX><NAME>` for
|
|
60
|
+
each prefix of `SYSHTTP_PREFIX` in turn, a `:`-separated list used
|
|
61
|
+
literally (default: one empty prefix, the names as written). The first
|
|
62
|
+
prefix that sets a variable wins, each variable on its own:
|
|
63
|
+
`SYSHTTP_PREFIX=MYTOOL_` reads only `MYTOOL_<NAME>`, `MYTOOL_:` falls back
|
|
64
|
+
to the unprefixed names. `find()` returns the variable found with its
|
|
65
|
+
value, for messages. Every function that reads a setting takes `env=`,
|
|
66
|
+
`new_session()` and the `fetch` `*_auto` helpers included.
|
|
67
|
+
- `CA_BUNDLE` (under the prefixes) names the CA bundle a session's
|
|
68
|
+
`verify=True` uses; sessions no longer read requests' own
|
|
69
|
+
`REQUESTS_CA_BUNDLE` / `CURL_CA_BUNDLE`.
|
|
70
|
+
- The system's proxy configuration is the last proxy source, decided for the
|
|
71
|
+
URL being requested: WinHTTP on Windows, CFNetwork on macOS, libproxy or the
|
|
72
|
+
desktop's settings elsewhere, PAC scripts and WPAD included (through
|
|
73
|
+
proxylib, a new dependency). It comes after `SYSHTTP_PROXY` and the
|
|
74
|
+
`*_PROXY` variables, after the `NO_PROXY` check, and is always spoken to as
|
|
75
|
+
`connect`. `SYSHTTP_SYSTEM_PROXY=0` turns it off. `proxy.plan()`,
|
|
76
|
+
`proxy_url()`, `resolve()` and `proxy_authorization()` take the request URL
|
|
77
|
+
(`url=` / `target=`). `proxy.configured_proxy_url()` gives the
|
|
78
|
+
environment's answer alone. The `pac` extra adds a JavaScript engine for PAC
|
|
79
|
+
scripts proxylib evaluates itself (Linux without libproxy).
|
|
80
|
+
- `syshttp.autopatch`: `SYSHTTP_IMPORTPATCH=1` (or `:`-separated names:
|
|
81
|
+
`requests`, `urllib`, `httpx`, `aiohttp`; read under the prefixes like
|
|
82
|
+
every setting) patches those libraries as they are imported,
|
|
83
|
+
in any program of the environment, so requests without a proxy or TLS
|
|
84
|
+
setting of the caller's route by syshttp and verify with the system's
|
|
85
|
+
context. Through importpatch, a new dependency, and its entry points;
|
|
86
|
+
`patch()` / `unpatch()` do it from code.
|
|
87
|
+
- The system's answer is a list (`PROXY a; PROXY b; DIRECT`): the first proxy
|
|
88
|
+
that accepts a connection is used (proxylib's `first_working_proxy`), and a
|
|
89
|
+
DIRECT reached first is direct. No reachable proxy and no DIRECT raises
|
|
90
|
+
`proxy.NoReachableProxyError` (a `ConnectionError`, exported from the
|
|
91
|
+
root); a session raises `requests.exceptions.ProxyError` for it.
|
|
92
|
+
- `new_session(ssl_context=..., proxy_ssl_context=...)` (and the same on the
|
|
93
|
+
`fetch` `*_auto` helpers): a caller's `ssl.SSLContext` is used as given for
|
|
94
|
+
every https origin, or for an `https://` proxy. That covers mutual TLS,
|
|
95
|
+
ciphers and protocol versions. It cannot be combined with a `verify` other
|
|
96
|
+
than `True`.
|
|
97
|
+
- `hooks.wrapper_environment()`: the `SYSHTTP_HOOK_*` variables a `_CURL`
|
|
98
|
+
wrapper run receives.
|
|
99
|
+
- `jar.default_root()`: the per-user state directory used when
|
|
100
|
+
`SYSHTTP_HOME` is unset.
|
|
101
|
+
- `syshttp.__version__`.
|
|
102
|
+
- Hosts that share cookies: `SYSHTTP_COOKIE_HOSTS` (hosts split by `,`,
|
|
103
|
+
groups by `;`) or `new_session(cookie_hosts=...)` declares names that reach
|
|
104
|
+
one service. In a group, a cookie a member sets for itself or another
|
|
105
|
+
member belongs to the first host, every member's requests carry the
|
|
106
|
+
group's cookies, and any member can delete them; a `Domain` outside the
|
|
107
|
+
group is still judged on the host that sent it. `cookies.host_aliases()`
|
|
108
|
+
builds the mapping, which `set_cookie_specs`, `merge_set_cookie_headers`
|
|
109
|
+
and `host_only_policy` take as `aliases=`.
|
|
110
|
+
- The shipped header and the settings page list every variable syshttp
|
|
111
|
+
reads under its own name (the proxy family, `REQUEST_METHOD`, the CA bundle
|
|
112
|
+
variables, the state-directory variables, importpatch's and proxylib's).
|
|
113
|
+
|
|
114
|
+
### Changed
|
|
115
|
+
|
|
116
|
+
- `syshttp.fetch` is a function, not a module: `fetch()` replaces the helpers
|
|
117
|
+
below. `_PY` hooks are called as `hook(client, method, url, kwargs)` with the
|
|
118
|
+
keyword arguments of `HTTPClient.request()`, and may return an
|
|
119
|
+
`HTTPResponse`; `auth.AuthProvider.ensure(client, url)` takes the client.
|
|
120
|
+
`CA_BUNDLE` is the bundle a client's `verify=True` uses. `cookies.py` keeps
|
|
121
|
+
the Netscape rows, `set_cookie_specs`, `cookie_applies` and `host_aliases`
|
|
122
|
+
only; the client keeps its cookies as those rows.
|
|
123
|
+
- Routing is proxylib 2's (`proxylib>=2.0.2,<3`; 2.0.1 breaks pip's cached
|
|
124
|
+
requests). `proxy.proxy_map(proxy,
|
|
125
|
+
noproxy, proxy_user, *, env)` is the settings as one proxylib proxy map,
|
|
126
|
+
read at each lookup; `proxy.configured_proxy()` is `SYSHTTP_PROXY` as a
|
|
127
|
+
proxylib `Proxy`; `proxy.system_map()` is the system's configuration
|
|
128
|
+
(`proxylib.auto_proxy`). The `*_PROXY` variables are read per scheme
|
|
129
|
+
(lowercase first, httpoxy-safe), `NO_PROXY` takes CIDR entries, and curl's
|
|
130
|
+
`-x ""` is direct. `SYSHTTP_PROXY_AUTH` goes to `SYSHTTP_PROXY` only and
|
|
131
|
+
`HTTP_PROXY_AUTH` to the machine's proxies only.
|
|
132
|
+
- A list from the system (`PROXY a; PROXY b; DIRECT`) is tried in order by the
|
|
133
|
+
session, httpx and aiohttp (proxylib's failover), replacing the probe of
|
|
134
|
+
the entry above; urllib takes the first reachable entry. No proxy
|
|
135
|
+
connecting and no DIRECT is the client's own connection error;
|
|
136
|
+
`NoReachableProxyError` is proxylib's.
|
|
137
|
+
- The session's `ProxyAdapter` is proxylib's `ProxyMapAdapter` with the
|
|
138
|
+
system's TLS; `adapter.route(url)` returns the entries it will try.
|
|
139
|
+
`proxies=` given to `new_session` are requests' own, with no syshttp
|
|
140
|
+
credential added.
|
|
141
|
+
- `syshttp.autopatch` routes through `proxylib.patch()` and patches TLS only;
|
|
142
|
+
requests' TLS is a pool hook on every `HTTPAdapter`. A caller's own
|
|
143
|
+
proxies and `trust_env = False` still win over syshttp (proxylib 2.0.2's
|
|
144
|
+
rule). aiohttp is not offered
|
|
145
|
+
URL-prefix gateways. New target `pip` (`pip._vendor.requests`, proxylib
|
|
146
|
+
2.0.1's `pip` target): pip's adapters route through the map unless pip is
|
|
147
|
+
given `--proxy` or `--no-proxy-env`; pip keeps its own TLS context.
|
|
148
|
+
|
|
149
|
+
- The jar constructors (`FileCookieJar`, `FileTokenJar`) no longer create
|
|
150
|
+
their directory; it is created at the first write.
|
|
151
|
+
|
|
152
|
+
- A session returns the last response when a status in `status_forcelist`
|
|
153
|
+
outlasts its retries, as plain requests does; it used to raise
|
|
154
|
+
`requests.exceptions.RetryError`, which the fetch helpers (`-> (status,
|
|
155
|
+
text)`) passed on. The `*_auto` helpers take `new_session`'s options as
|
|
156
|
+
`**session_options` (every keyword they had still works) and their
|
|
157
|
+
`timeout` may be a `(connect, read)` pair.
|
|
158
|
+
- Every dependency's floor is its newest release that supports Python 3.9:
|
|
159
|
+
the `session` extra requires `requests>=2.32.5` and `urllib3>=2.6.3` (was
|
|
160
|
+
`requests>=2.25`), and `sslcontext>=2.1.0`. Below requests 2.32.3 a caller's
|
|
161
|
+
`ssl_context` was silently ignored, urllib3 1.26 raises a bare `ValueError`
|
|
162
|
+
for an https IP-literal host, and urllib3 up to 2.2 leaks the socket of a
|
|
163
|
+
failed TLS handshake through an https proxy. On Python 3.9, botocore pins `urllib3<1.27`, so `syshttp[session]`
|
|
164
|
+
cannot share an environment with it there.
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
- The header says that jars, tokens, `_PY` hooks and `auth_provider` apply to
|
|
168
|
+
`session.request` and not to a bare `session.send()`, which gets the proxy
|
|
169
|
+
decision and TLS only.
|
|
170
|
+
|
|
171
|
+
- `new_session()` no longer calls `urllib3.disable_warnings`, which silenced
|
|
172
|
+
`InsecureRequestWarning` for the whole process; with `verify=False` urllib3
|
|
173
|
+
warns as it does for any requests session. The undocumented
|
|
174
|
+
`syshttp.warnings` module is gone.
|
|
175
|
+
|
|
176
|
+
### Removed
|
|
177
|
+
|
|
178
|
+
- `syshttp.session` (`new_session()` and its `requests` adapter), the
|
|
179
|
+
`session` extra (and its `requests` and `urllib3` floors; `requests` stays a
|
|
180
|
+
development dependency for the tests of `syshttp.autopatch`), and the helpers
|
|
181
|
+
of the old `syshttp.fetch` module: `request_text`, `request_json`,
|
|
182
|
+
`request_with_reauth`, `request_text_auto` and `request_json_auto`. Their
|
|
183
|
+
replacements are `HTTPClient` and `fetch()`. A per-request `verify=` and
|
|
184
|
+
`cert=` are gone with them: build a client with `verify=` or an
|
|
185
|
+
`ssl_context` that holds the identity. `cookies.apply_to_session`,
|
|
186
|
+
`merge_set_cookie_headers` and `host_only_policy`, which only a `requests`
|
|
187
|
+
session used, are gone too.
|
|
188
|
+
- `proxy.proxy_url`, `configured_proxy_url`, `system_proxy`,
|
|
189
|
+
`system_proxy_map`, `first_reachable`, `PROBE_MAX_AGE`, `PROBE_TIMEOUT`,
|
|
190
|
+
`resolve`, `proxy_authorization`, `configured_authorization`, `userinfo`,
|
|
191
|
+
`basic_authorization`, `strip_auth`, `redact`, `prefix_url`,
|
|
192
|
+
`should_bypass`, `bypassed_by`, `no_proxy_from_env`, `proxies_from_env`,
|
|
193
|
+
`plan` and `ProxyPlan`: `proxy_map()` and proxylib's `Proxy` replace them.
|
|
194
|
+
|
|
195
|
+
### Changed (earlier)
|
|
196
|
+
|
|
197
|
+
- The package stands alone as `syshttp`. It was previously the `httplib`
|
|
198
|
+
library of the dotagents `net` overlay, which shipped with a curl-compatible
|
|
199
|
+
command (`httplib.cli`, `python -m httplib`) that is now the `curlike`
|
|
200
|
+
package. Imports move from `httplib` to `syshttp`, and settings from the
|
|
201
|
+
dotagents names to the prefixed ones: `AGENTS_PROXY` → `SYSHTTP_PROXY`,
|
|
202
|
+
`AGENTS_PROXY_AUTH` → `SYSHTTP_PROXY_AUTH`, `AGENTS_PROXY_AUTH_HEADER` →
|
|
203
|
+
`SYSHTTP_PROXY_AUTH_HEADER`, `AGENTS_PROXY_TYPE` → `SYSHTTP_PROXY_TYPE`,
|
|
204
|
+
`AGENTS_HOME` → `SYSHTTP_HOME`, `NET_HOOKS_<KEY>` → `SYSHTTP_HOOKS_<KEY>`,
|
|
205
|
+
`NET_HOOK_*` → `SYSHTTP_HOOK_*`. A tool's own namespace is a prefix:
|
|
206
|
+
`SYSHTTP_PREFIX=AGENTS_` reads `AGENTS_SYSHTTP_PROXY`,
|
|
207
|
+
`AGENTS_SYSHTTP_HOOKS_<KEY>` and so on.
|
|
208
|
+
- The system's TLS client by default: `tls.new_os_context()` (and so
|
|
209
|
+
`os_ssl_context()` and every session with `verify=True`) returns
|
|
210
|
+
`sslcontext.system_context()` (sslcontext 2 is a new dependency).
|
|
211
|
+
Certificates are verified by the operating system on Python 3.10+
|
|
212
|
+
(CryptoAPI on Windows, the Security framework on macOS, OpenSSL over the
|
|
213
|
+
system store elsewhere), by OpenSSL over the default store on 3.9, and a
|
|
214
|
+
server that asks for a client certificate gets one from the OS store,
|
|
215
|
+
chosen as a browser chooses. `new_os_context(client_certificates=False)`
|
|
216
|
+
offers none, for a caller loading its own. A `cafile` / `capath` still
|
|
217
|
+
replaces the store with a plain `ssl` context.
|
|
218
|
+
- A session without caller `proxies` decides the proxy for every request and
|
|
219
|
+
every redirect hop, from the environment as it is then and the system's
|
|
220
|
+
answer for that URL. A direct decision is final: requests' `trust_env` cannot
|
|
221
|
+
re-add a proxy. `adapter.route(url)` shows the decision.
|
|
222
|
+
- Without `SYSHTTP_HOME`, the jars live in the per-user state directory
|
|
223
|
+
instead of the dotagents store directory.
|
|
224
|
+
- `proxy.resolve()` and `proxy.proxy_authorization()`: the keyword `env: bool`
|
|
225
|
+
is now `configured: bool`.
|
|
226
|
+
- `hooks.hooks_from_env()`, `matching()`, `skipped()` and `call_py_hooks()`
|
|
227
|
+
take `env: HTTPEnv` (keyword-only) instead of a mapping `environ`.
|
|
228
|
+
`hooks.PREFIX`, `SKIP_ENV`, `URL_ENV`, `KEY_ENV` and `CURL_ENV` are
|
|
229
|
+
replaced by the setting names `HOOKS`, `HOOK_SKIP`, `HOOK_URL`, `HOOK_KEY`
|
|
230
|
+
and `HOOK_CURL`.
|
|
231
|
+
- `proxy.AUTH_VARS`, `AUTH_HEADER_VAR` and `TYPE_VAR` are replaced by the
|
|
232
|
+
setting names `PROXY_AUTH`, `PROXY_AUTH_HEADER` and `PROXY_TYPE`.
|
|
233
|
+
|
|
234
|
+
### Fixed
|
|
235
|
+
|
|
236
|
+
- A by-host cookie file keeps a cookie its host set for a parent domain
|
|
237
|
+
(`Domain=example.com` from `www.example.com`); it was never saved.
|
|
238
|
+
- The proxy credential reaches a proxy whose host is configured in upper
|
|
239
|
+
case. requests lower-cases the proxy URL it hands to `proxy_headers`, so the
|
|
240
|
+
credential used to be dropped and the proxy answered 407.
|
|
241
|
+
- `Set-Cookie` is read as RFC 6265 section 5.2 reads it, not through
|
|
242
|
+
`http.cookies.SimpleCookie`: a value with a space or JSON is kept, an
|
|
243
|
+
unknown attribute (`Priority`, `Partitioned`) is no longer a cookie of its
|
|
244
|
+
own, `Max-Age` beats `Expires`, and a header with no `name=value` is
|
|
245
|
+
skipped. A `Domain` without a leading dot covers subdomains. A cookie with
|
|
246
|
+
no `Path` gets the directory of the request path
|
|
247
|
+
(`set_cookie_specs(..., request_path=)`).
|
|
248
|
+
- A session holds one copy of each cookie. `merge_set_cookie_headers` no
|
|
249
|
+
longer re-sets what requests already stored (it used to add a second copy
|
|
250
|
+
with path `/` and a dotless domain); it applies only the deletions requests
|
|
251
|
+
misses, for redirect hops too.
|
|
252
|
+
- A cookie set without `Domain` stays on its host: rows loaded from a jar and
|
|
253
|
+
cookies a server sets are no longer sent to subdomains
|
|
254
|
+
(`new_session` uses `cookies.host_only_policy()`).
|
|
255
|
+
- A cookie for a single-label host (`localhost`, `wiki`) is loaded from its
|
|
256
|
+
jar file, and is saved under its own name instead of `localhost.local`.
|
|
257
|
+
- `FileCookieJar` keeps `HttpOnly` and the include-subdomains flag.
|
|
258
|
+
- `load_netscape` keeps a row whose value is empty, and an expiry written
|
|
259
|
+
with a non-ASCII digit is a session cookie instead of a `ValueError`.
|
|
260
|
+
`retry_after` returns `None` for such a value, and reads an HTTP date
|
|
261
|
+
written without a zone (`asctime`) as UTC.
|
|
262
|
+
- A cookie expiry beyond 400 days is cut to 400 days.
|
|
263
|
+
- A hook whose `_PY` / `_CURL` target is blank is ignored instead of failing
|
|
264
|
+
every request, and `HTTPEnv.family` strips values as `get` does.
|
|
265
|
+
- Hook KEYs are compared without regard to case: `HOOK_SKIP=mykey` did not
|
|
266
|
+
suppress `MYKEY`, the form Windows holds. `Hook.key` and `skipped()` are
|
|
267
|
+
upper-case.
|
|
268
|
+
- A `<file>.py:callable` hook's module is loaded once per process, not on
|
|
269
|
+
every request (its module-level state was reset each time), and two files
|
|
270
|
+
with one name no longer share a module name.
|
|
271
|
+
- `request_text_auto` and `request_json_auto` retry a 408 by default, as
|
|
272
|
+
`new_session` does (their own list had dropped it), and close the session
|
|
273
|
+
they built instead of leaking its connection pool. The five fetch helpers
|
|
274
|
+
have docstrings.
|
|
275
|
+
- `new_session(user_agent=...)` sets the User-Agent; the default
|
|
276
|
+
`python-requests/x` was sent whatever was given.
|
|
277
|
+
- The guards that keep a hook or `auth_provider.ensure` from running again for
|
|
278
|
+
its own requests are per thread: while one thread was inside a hook, the
|
|
279
|
+
other threads' requests skipped hooks and `ensure`.
|
|
280
|
+
- A request-level `verify=` (`False`, or a bundle) beside `ssl_context`, which
|
|
281
|
+
was silently ignored, raises `ValueError` as the same value does at session
|
|
282
|
+
level. `CA_BUNDLE` is not a request-level value, and a session with an
|
|
283
|
+
`ssl_context` does not consult it.
|
|
284
|
+
- The import patch replaces requests 2.32.3's own default context (a certifi
|
|
285
|
+
bundle) in a pool, as it does for the later versions that leave it out.
|
|
286
|
+
- Token and cookie files are replaced whole by a rename, so a reader never sees
|
|
287
|
+
an empty or half-written file and concurrent savers no longer truncate each
|
|
288
|
+
other's file.
|
|
289
|
+
- `SYSHTTP_HOME=~/jars` is expanded instead of creating a directory named `~`.
|
|
290
|
+
- Jar files are written with LF on Windows too, as curl writes them.
|
|
291
|
+
- A session whose cookie jar cannot be read (an error while iterating
|
|
292
|
+
`session.cookies`) no longer saves an empty list over the host's cookies, and
|
|
293
|
+
writes a host's file only when its cookies changed instead of after every
|
|
294
|
+
response.
|
|
295
|
+
|
|
296
|
+
### Security
|
|
297
|
+
|
|
298
|
+
- The client closes six gaps found in review. A `Set-Cookie` with a control
|
|
299
|
+
character in it is ignored whole (RFC 6265bis 5.6), `load_netscape` ends a
|
|
300
|
+
line at a newline only (it ended one at `\x0b`, `\x0c`, `\x1c`-`\x1e` and
|
|
301
|
+
`\x85` too, so a value could become a whole row for another domain),
|
|
302
|
+
`save_netscape` writes no row with a control character or a tab in a field,
|
|
303
|
+
and a by-host jar file gives the client only the rows that cover its host or
|
|
304
|
+
group. A plain-http response can no longer set a Secure cookie, or set,
|
|
305
|
+
replace or delete one that is stored (RFC 6265bis 5.7; the loopback hosts
|
|
306
|
+
count as secure, as in curl). A redirect to another scheme, host or port
|
|
307
|
+
drops a caller's `Cookie` and `Proxy-Authorization` header with
|
|
308
|
+
`Authorization`. `HTTPTransport.send(stream=False)` and
|
|
309
|
+
`HTTPResponse.buffered()` hold a decoded reply to `MAX_REPLY_BYTES` (a
|
|
310
|
+
kilobyte of gzip decoded to tens of megabytes), and the too-large error names
|
|
311
|
+
the URL as scheme, host, port and path, without its query. An `https:` origin
|
|
312
|
+
is no longer sent through a plain `prefix+http` gateway, which would carry its
|
|
313
|
+
tokens and Secure cookies in clear text: the entry is skipped, and with
|
|
314
|
+
nothing else left the request raises `proxylib.UnsupportedProxyError`.
|
|
315
|
+
- A `Set-Cookie` can no longer name a domain its host does not own. Any
|
|
316
|
+
host could set or delete another host's cookie (`Domain=bank.example` from
|
|
317
|
+
`evil.example`) or one for a top level domain; such a header is now skipped
|
|
318
|
+
on a set and on a deletion, in the session, in `FileCookieJar` and in
|
|
319
|
+
`set_cookie_specs` (which curlike's `-c` uses). A public suffix is
|
|
320
|
+
recognised without the Public Suffix List (a name with no dot, or a
|
|
321
|
+
`co.uk`-style registry).
|
|
322
|
+
- Token and cookie files are created `0600`, and the directories this library
|
|
323
|
+
creates `0700`, instead of world-readable under a usual umask.
|
|
324
|
+
- A client certificate passed as `cert=` is no longer loaded into the shared OS
|
|
325
|
+
context (or into the caller's `ssl_context`), where every later session, and
|
|
326
|
+
every other user of that context, presented it too. It gets a context of the
|
|
327
|
+
session's own; `cert=` beside `ssl_context` raises `ValueError`.
|
|
328
|
+
- A URL-prefix gateway is spoken to with `proxy_ssl_context` (or the system's
|
|
329
|
+
verifier), no longer with the `ssl_context` and client certificate meant for
|
|
330
|
+
the origin.
|
|
331
|
+
- A token, or a proxy credential, containing CR, LF or NUL raises a
|
|
332
|
+
`ValueError` that names the jar key or variable instead of requests'
|
|
333
|
+
`InvalidHeader` carrying the whole secret.
|
|
334
|
+
- No setting can be chosen by a CGI client unless the program reads the
|
|
335
|
+
unprefixed names: syshttp's own all start `SYSHTTP_`, which a request
|
|
336
|
+
header never becomes, and a CGI program sets a prefix list without the
|
|
337
|
+
empty entry so that `HTTP_PROXY` and `HTTP_PROXY_AUTH` are not read.
|
|
338
|
+
- A failing hook's message names its URL as scheme, host, port and path, no
|
|
339
|
+
longer with the userinfo, query and fragment (`user:pass@`,
|
|
340
|
+
`?access_token=`).
|
|
341
|
+
|
|
342
|
+
[Unreleased]: https://github.com/jose-pr/syshttp/compare/v0.1.0...HEAD
|
|
343
|
+
[0.1.0]: https://github.com/jose-pr/syshttp/releases/tag/v0.1.0
|
syshttp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jose A.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
syshttp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: syshttp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: HTTP plumbing that follows the system: the system proxy (PAC, WPAD) and the OS certificate verifier by default, proxy credentials and prefix gateways, Netscape cookie jars, OS trust-store TLS, URL hooks, and an HTTP client on the standard library
|
|
5
|
+
Project-URL: Homepage, https://github.com/jose-pr/syshttp
|
|
6
|
+
Project-URL: Documentation, https://jose-pr.github.io/syshttp/
|
|
7
|
+
Project-URL: Issues, https://github.com/jose-pr/syshttp/issues
|
|
8
|
+
Author-email: jose-pr <jose-pr@coqui.dev>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Requires-Dist: importpatch<0.3,>=0.2.1
|
|
24
|
+
Requires-Dist: proxylib<3,>=2.0.2
|
|
25
|
+
Requires-Dist: sslcontext<3,>=2.1.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: aiohttp; extra == 'dev'
|
|
28
|
+
Requires-Dist: black; extra == 'dev'
|
|
29
|
+
Requires-Dist: build; extra == 'dev'
|
|
30
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
31
|
+
Requires-Dist: mypy<2; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
33
|
+
Requires-Dist: requests; extra == 'dev'
|
|
34
|
+
Requires-Dist: types-requests; extra == 'dev'
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: mkdocs; extra == 'docs'
|
|
37
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
38
|
+
Requires-Dist: mkdocstrings[python]; extra == 'docs'
|
|
39
|
+
Provides-Extra: pac
|
|
40
|
+
Requires-Dist: proxylib[pac]<3,>=2.0.2; extra == 'pac'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# syshttp
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/syshttp/)
|
|
46
|
+
[](https://pypi.org/project/syshttp/)
|
|
47
|
+
[](https://github.com/jose-pr/syshttp/blob/main/LICENSE)
|
|
48
|
+
[](https://jose-pr.github.io/syshttp/)
|
|
49
|
+
[](https://github.com/jose-pr/syshttp/actions/workflows/test.yml)
|
|
50
|
+
|
|
51
|
+
**HTTP plumbing that behaves the same in every client you build on it**:
|
|
52
|
+
which proxy to use, with which credential, for which hosts; how a prefix
|
|
53
|
+
gateway is spoken to; which cookies and tokens go with which origin; and TLS
|
|
54
|
+
through the operating system's own certificate verifier. By default it
|
|
55
|
+
routes the way the system does: the system's proxy configuration, PAC scripts
|
|
56
|
+
and WPAD included, unless a proxy variable says otherwise. An HTTP client on the
|
|
57
|
+
standard library puts all of them together; `requests` is needed by none of
|
|
58
|
+
it. Documentation: <https://jose-pr.github.io/syshttp/>.
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
- **Routing is [proxylib](https://pypi.org/project/proxylib/)'s** —
|
|
63
|
+
`proxy.proxy_map()` turns the settings below into one proxylib proxy map,
|
|
64
|
+
and proxylib's client integrations send through it.
|
|
65
|
+
- **The system's proxy, by default** — with no proxy variable set, the
|
|
66
|
+
system's configuration decides per URL: WinHTTP on Windows, CFNetwork on
|
|
67
|
+
macOS, libproxy or the desktop's settings elsewhere, PAC and WPAD included.
|
|
68
|
+
A list (`PROXY a; PROXY b; DIRECT`) is tried in order, the next entry when
|
|
69
|
+
one does not connect; with no proxy connecting and no DIRECT the request
|
|
70
|
+
fails rather than going direct.
|
|
71
|
+
- **Proxies with credentials** — a proxy URL from `SYSHTTP_PROXY`, else the
|
|
72
|
+
usual `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` for the request's scheme.
|
|
73
|
+
The credential is a header value sent verbatim to that proxy only, so any
|
|
74
|
+
scheme works (`Basic`, `Bearer`, `Negotiate`). `NO_PROXY` is honoured
|
|
75
|
+
(CIDR included), and every redirect hop is decided again.
|
|
76
|
+
- **Prefix gateways** — `SYSHTTP_PROXY_TYPE=prefix:/fetch/` sends
|
|
77
|
+
`GET https://api.example.com/x` to `<proxy>/fetch/https://api.example.com/x`
|
|
78
|
+
(the client, requests, httpx and urllib; the client sends an `https:` origin
|
|
79
|
+
through an `https` gateway only). Responses, redirects and cookies still use
|
|
80
|
+
the caller's URL.
|
|
81
|
+
- **Prefixes of a tool's own** — `SYSHTTP_PREFIX=MYTOOL_` makes the package
|
|
82
|
+
read `MYTOOL_HTTPS_PROXY`, `MYTOOL_SYSHTTP_HOME` and so on instead of the
|
|
83
|
+
defaults; `MYTOOL_:` falls back to them, variable by variable. An
|
|
84
|
+
`HTTPEnv("MYTOOL_")` object keeps one configuration apart from another in
|
|
85
|
+
the same process.
|
|
86
|
+
- **Cookie and token jars** — Netscape cookie files with curl's matching
|
|
87
|
+
rules, plus per-host token files. Both are chosen by the origin's host,
|
|
88
|
+
never by the proxy's or the gateway's.
|
|
89
|
+
- **URL hooks** — `SYSHTTP_HOOKS_<KEY>=<regex>` plus `_PY=module:callable`
|
|
90
|
+
runs your code before matching requests: a login, a signature, a canned
|
|
91
|
+
response.
|
|
92
|
+
- **The system's TLS client, or your own TLS context** — by default
|
|
93
|
+
[sslcontext](https://pypi.org/project/sslcontext/)'s system context: the
|
|
94
|
+
operating system verifies certificates (CryptoAPI, the Security framework),
|
|
95
|
+
with no `certifi` bundle involved, and a server that asks for a client
|
|
96
|
+
certificate gets one from the OS store (smart cards included), chosen as a
|
|
97
|
+
browser chooses. Or `HTTPClient(ssl_context=...)` for a given identity, a
|
|
98
|
+
cipher list or a pinned protocol version.
|
|
99
|
+
- **An HTTP client on the standard library** — `HTTPClient` and `fetch()`:
|
|
100
|
+
retry with backoff, redirects that keep a credential with its origin, and
|
|
101
|
+
all of the above, on `http.client` and `ssl` alone. Its transport is
|
|
102
|
+
replaceable: any object with `send()` (a `requests` or `httpx` one, a fake
|
|
103
|
+
in a test), chosen per client or by `SYSHTTP_DEFAULT_TRANSPORT`.
|
|
104
|
+
- **Other programs' clients, patched on import** — `SYSHTTP_IMPORTPATCH=1`
|
|
105
|
+
(or `requests:urllib:httpx:aiohttp:pip`) makes those libraries route and
|
|
106
|
+
verify the same way in any program of the environment, through
|
|
107
|
+
[importpatch](https://pypi.org/project/importpatch/); what a caller sets
|
|
108
|
+
itself still wins.
|
|
109
|
+
|
|
110
|
+
## Installation
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pip install syshttp
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| Extra | Adds | Needed for |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `pac` | a JavaScript engine for proxylib | PAC scripts proxylib evaluates itself (Linux without libproxy) |
|
|
119
|
+
|
|
120
|
+
syshttp depends on proxylib (the system proxy), sslcontext (the system's
|
|
121
|
+
TLS client: the OS verifier through truststore on Python 3.10+, OpenSSL over
|
|
122
|
+
the default store on 3.9) and importpatch (its `.pth` hooks imports at
|
|
123
|
+
interpreter start; nothing is patched unless `SYSHTTP_IMPORTPATCH` says so).
|
|
124
|
+
|
|
125
|
+
## Quick start
|
|
126
|
+
|
|
127
|
+
Ask where a request would go, the way every client here does:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
import proxylib
|
|
131
|
+
from syshttp import proxy
|
|
132
|
+
|
|
133
|
+
for entry in proxylib.lookup(proxy.proxy_map(), "https://api.example.com/"):
|
|
134
|
+
print("DIRECT" if entry is None else entry.url) # tried in this order
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
One request, routed and verified the way the system does (no extra to install):
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from syshttp import fetch
|
|
141
|
+
|
|
142
|
+
response = fetch("https://api.example.com/thing")
|
|
143
|
+
print(response.status, response.json())
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
A client with file-backed cookies and tokens, kept across requests:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from syshttp.client import HTTPClient
|
|
150
|
+
|
|
151
|
+
with HTTPClient(cookies=True, tokens=True) as client:
|
|
152
|
+
response = client.request("GET", "https://api.example.com/thing")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A separate configuration, isolated from the process environment:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from syshttp import HTTPEnv, fetch
|
|
159
|
+
|
|
160
|
+
env = HTTPEnv("MYTOOL_", environ={"MYTOOL_SYSHTTP_PROXY": "http://proxy.internal:3128"})
|
|
161
|
+
response = fetch("https://api.example.com/thing", env=env)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## API overview
|
|
165
|
+
|
|
166
|
+
| Module | Purpose |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| `syshttp.env` | `HTTPEnv`: the prefixes and mapping every setting is read from |
|
|
169
|
+
| `syshttp.proxy` | `proxy_map()`: the settings as one proxylib proxy map |
|
|
170
|
+
| `syshttp.client` | `HTTPClient` and `fetch()`: the HTTP client, its transport protocol and response |
|
|
171
|
+
| `syshttp.jar` | file and memory cookie/token jars |
|
|
172
|
+
| `syshttp.cookies` | Netscape cookie files and curl's cookie rules |
|
|
173
|
+
| `syshttp.hooks` | URL hooks declared in the environment |
|
|
174
|
+
| `syshttp.tls` | the OS verifier as an `SSLContext` |
|
|
175
|
+
| `syshttp.autopatch` | requests, urllib, httpx, aiohttp and pip patched on import |
|
|
176
|
+
| `syshttp.retry` | the transient statuses, `Retry-After` |
|
|
177
|
+
|
|
178
|
+
## Settings
|
|
179
|
+
|
|
180
|
+
Every setting is read as `<PREFIX><NAME>` for each prefix of
|
|
181
|
+
`SYSHTTP_PREFIX` (`:`-separated; default none, the names as written). The
|
|
182
|
+
full list is on the docs site:
|
|
183
|
+
<https://jose-pr.github.io/syshttp/settings/>.
|
|
184
|
+
|
|
185
|
+
| Variable | Sets |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| `SYSHTTP_PREFIX` | the prefixes every other setting is read under |
|
|
188
|
+
| `SYSHTTP_PROXY` | the proxy URL; else `HTTPS_PROXY`, `HTTP_PROXY`, `ALL_PROXY`, else the system's |
|
|
189
|
+
| `SYSHTTP_SYSTEM_PROXY` | `0` to stop consulting the system's proxy configuration |
|
|
190
|
+
| `SYSHTTP_PROXY_AUTH` | the proxy credential, sent verbatim; else `HTTP_PROXY_AUTH` |
|
|
191
|
+
| `SYSHTTP_PROXY_AUTH_HEADER` | the header it is sent in (default `Proxy-Authorization`) |
|
|
192
|
+
| `SYSHTTP_PROXY_TYPE` | `connect` (default) or `prefix[:/endpoint]` |
|
|
193
|
+
| `SYSHTTP_HOME` | the jar directory (default: the per-user state directory) |
|
|
194
|
+
| `SYSHTTP_IMPORTPATCH` | `1`, or `:`-separated names: the client libraries patched on import |
|
|
195
|
+
| `SYSHTTP_HOOKS_<KEY>` | a URL hook's pattern, with `_PY` / `_CURL` targets |
|
|
196
|
+
| `SYSHTTP_DEFAULT_TRANSPORT` | `module:attr` or `<file>.py:attr`: the transport of a client given none (it names code to import) |
|
|
197
|
+
| `CA_BUNDLE` | the CA bundle a client verifies with (default: the OS verifier) |
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
dotagents pyvenv # or any Python 3.9+ virtual environment
|
|
203
|
+
.pyvenv/<tag>/Scripts/python -m pip install -e ".[dev]"
|
|
204
|
+
.pyvenv/<tag>/Scripts/python -m pytest -q
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Releasing
|
|
208
|
+
|
|
209
|
+
This project follows [Semantic Versioning](https://semver.org/) and keeps a
|
|
210
|
+
[`CHANGELOG.md`](https://github.com/jose-pr/syshttp/blob/main/CHANGELOG.md).
|
|
211
|
+
Pushing a tag matching `v*` triggers the release workflow: test gate → build
|
|
212
|
+
(checking the tag names the version built) → a strict docs build as a gate →
|
|
213
|
+
GitHub release → publish. The release workflow never deploys the docs site
|
|
214
|
+
itself: for a final release its last job dispatches the docs workflow at the
|
|
215
|
+
tag, which owns every Pages deploy.
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT — see [LICENSE](https://github.com/jose-pr/syshttp/blob/main/LICENSE).
|