fasthttpc 0.6.0__py3-none-any.whl
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.
- fasthttpc/__init__.py +102 -0
- fasthttpc/_api.py +272 -0
- fasthttpc/_backend.py +589 -0
- fasthttpc/_log.py +40 -0
- fasthttpc/async_session.py +1724 -0
- fasthttpc/auth.py +328 -0
- fasthttpc/buffer.py +137 -0
- fasthttpc/cache.py +151 -0
- fasthttpc/config.py +207 -0
- fasthttpc/cookies.py +186 -0
- fasthttpc/crypto_math.py +202 -0
- fasthttpc/fingerprint.py +768 -0
- fasthttpc/http3/__init__.py +18 -0
- fasthttpc/http3/altsvc.py +115 -0
- fasthttpc/http3/async_client.py +234 -0
- fasthttpc/http3/core.py +276 -0
- fasthttpc/http3/fingerprint.py +95 -0
- fasthttpc/http3/manager.py +199 -0
- fasthttpc/http3/options.py +37 -0
- fasthttpc/http3/sync_client.py +174 -0
- fasthttpc/http3/verify.py +21 -0
- fasthttpc/metrics.py +108 -0
- fasthttpc/models/__init__.py +0 -0
- fasthttpc/models/headers.py +109 -0
- fasthttpc/models/request.py +100 -0
- fasthttpc/models/response.py +558 -0
- fasthttpc/models/urls.py +104 -0
- fasthttpc/multipart.py +119 -0
- fasthttpc/plugins.py +146 -0
- fasthttpc/protocol/__init__.py +0 -0
- fasthttpc/protocol/async_h2.py +774 -0
- fasthttpc/protocol/dns_over_https.py +202 -0
- fasthttpc/protocol/http1_parser.py +190 -0
- fasthttpc/protocol/http2_parser.py +1753 -0
- fasthttpc/py.typed +0 -0
- fasthttpc/retry.py +156 -0
- fasthttpc/security/__init__.py +0 -0
- fasthttpc/security/alerts.py +39 -0
- fasthttpc/security/cert_validator.py +441 -0
- fasthttpc/security/client_cert.py +344 -0
- fasthttpc/security/keyshare.py +265 -0
- fasthttpc/security/mtls.py +282 -0
- fasthttpc/security/session_tickets.py +466 -0
- fasthttpc/security/tls12.py +1435 -0
- fasthttpc/security/tls12_legacy.py +299 -0
- fasthttpc/security/tls_adapter.py +1111 -0
- fasthttpc/session.py +1372 -0
- fasthttpc/streaming.py +218 -0
- fasthttpc/timeout.py +9 -0
- fasthttpc/tls13/__init__.py +26 -0
- fasthttpc/tls13/_compat.py +216 -0
- fasthttpc/tls13/_records.py +35 -0
- fasthttpc/tls13/async_stream.py +173 -0
- fasthttpc/tls13/connection.py +833 -0
- fasthttpc/tls13/errors.py +22 -0
- fasthttpc/tls13/events.py +57 -0
- fasthttpc/tls13/sync_socket.py +102 -0
- fasthttpc/tls_crafter.py +523 -0
- fasthttpc/tls_tools.py +623 -0
- fasthttpc/transport/__init__.py +0 -0
- fasthttpc/transport/connection_pool.py +366 -0
- fasthttpc/transport/happy_eyeballs.py +144 -0
- fasthttpc/transport/lingering.py +50 -0
- fasthttpc/transport/mock.py +83 -0
- fasthttpc/transport/proxy.py +410 -0
- fasthttpc-0.6.0.dist-info/METADATA +232 -0
- fasthttpc-0.6.0.dist-info/RECORD +70 -0
- fasthttpc-0.6.0.dist-info/WHEEL +5 -0
- fasthttpc-0.6.0.dist-info/licenses/LICENSE +21 -0
- fasthttpc-0.6.0.dist-info/top_level.txt +1 -0
fasthttpc/__init__.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fasthttpc — Pure-Python HTTP istemcisi
|
|
3
|
+
|
|
4
|
+
TLS 1.3 fingerprinting, Chrome/Firefox/Safari impersonation,
|
|
5
|
+
async destek, akıllı connection pool ve plugin sistemi içerir.
|
|
6
|
+
|
|
7
|
+
Hızlı başlangıç::
|
|
8
|
+
|
|
9
|
+
from fasthttpc import Session, Timeout
|
|
10
|
+
|
|
11
|
+
with Session(impersonate="chrome_131") as s:
|
|
12
|
+
r = s.get("https://httpbin.org/get")
|
|
13
|
+
print(r.status_code, r.elapsed)
|
|
14
|
+
|
|
15
|
+
Async::
|
|
16
|
+
|
|
17
|
+
from fasthttpc import AsyncSession
|
|
18
|
+
|
|
19
|
+
async with AsyncSession() as s:
|
|
20
|
+
r = await s.get("https://httpbin.org/get")
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# `cryptography` alt modülleri import edilmeden ÖNCE ayarlanmalı. Bazı
|
|
24
|
+
# ortamlarda (özellikle Pydroid3/Android'deki OpenSSL 3.0) legacy provider
|
|
25
|
+
# yüklenemez ve import ölümcül bir RuntimeError ile patlar. fasthttpc yalnızca
|
|
26
|
+
# modern algoritmalar kullanır (AES-GCM, ChaCha20-Poly1305, x25519, ECDSA,
|
|
27
|
+
# RSA-PSS, Ed25519) — hiçbiri legacy provider'a ihtiyaç duymaz, dolayısıyla
|
|
28
|
+
# onu devre dışı bırakmak güvenli ve gereklidir.
|
|
29
|
+
import os as _os
|
|
30
|
+
_os.environ.setdefault("CRYPTOGRAPHY_OPENSSL_NO_LEGACY", "1")
|
|
31
|
+
|
|
32
|
+
from .session import Session
|
|
33
|
+
from .async_session import AsyncSession
|
|
34
|
+
from .config import Timeout, Limits
|
|
35
|
+
from .retry import Retry
|
|
36
|
+
from .transport.mock import MockTransport, BaseTransport
|
|
37
|
+
from .multipart import encode_multipart
|
|
38
|
+
from .models.response import Response, HTTPStatusError
|
|
39
|
+
from .models.request import Request
|
|
40
|
+
from .models.headers import Headers
|
|
41
|
+
from .models.urls import URL
|
|
42
|
+
from .cache import ResponseCache
|
|
43
|
+
from .cookies import CookieJar, Cookie
|
|
44
|
+
from ._log import enable_debug_logging
|
|
45
|
+
from .protocol.dns_over_https import DoHResolver
|
|
46
|
+
from .fingerprint import (
|
|
47
|
+
get_profile,
|
|
48
|
+
available_profiles,
|
|
49
|
+
ja3_from_client_hello,
|
|
50
|
+
ja3_for_profile,
|
|
51
|
+
)
|
|
52
|
+
from .metrics import RequestMetrics
|
|
53
|
+
from .tls_tools import (
|
|
54
|
+
TLSFragmenter,
|
|
55
|
+
TLSDowngradeDetector,
|
|
56
|
+
ClientHelloEntropyScorer,
|
|
57
|
+
)
|
|
58
|
+
from .async_session import ProxyRotator
|
|
59
|
+
from .plugins import Plugin, RetryPlugin, LoggingPlugin, RateLimitPlugin
|
|
60
|
+
from .auth import Auth, BasicAuth, BearerAuth, DigestAuth
|
|
61
|
+
from ._api import USE_CLIENT_DEFAULT
|
|
62
|
+
|
|
63
|
+
__version__ = "0.6.0"
|
|
64
|
+
__author__ = "Buğra"
|
|
65
|
+
__all__ = [
|
|
66
|
+
"Session",
|
|
67
|
+
"AsyncSession",
|
|
68
|
+
"Timeout",
|
|
69
|
+
"Limits",
|
|
70
|
+
"Retry",
|
|
71
|
+
"MockTransport",
|
|
72
|
+
"BaseTransport",
|
|
73
|
+
"encode_multipart",
|
|
74
|
+
"Response",
|
|
75
|
+
"HTTPStatusError",
|
|
76
|
+
"Request",
|
|
77
|
+
"Headers",
|
|
78
|
+
"URL",
|
|
79
|
+
"ResponseCache",
|
|
80
|
+
"CookieJar",
|
|
81
|
+
"Cookie",
|
|
82
|
+
"DoHResolver",
|
|
83
|
+
"enable_debug_logging",
|
|
84
|
+
"get_profile",
|
|
85
|
+
"available_profiles",
|
|
86
|
+
"ja3_from_client_hello",
|
|
87
|
+
"ja3_for_profile",
|
|
88
|
+
"RequestMetrics",
|
|
89
|
+
"TLSFragmenter",
|
|
90
|
+
"TLSDowngradeDetector",
|
|
91
|
+
"ClientHelloEntropyScorer",
|
|
92
|
+
"Plugin",
|
|
93
|
+
"RetryPlugin",
|
|
94
|
+
"LoggingPlugin",
|
|
95
|
+
"RateLimitPlugin",
|
|
96
|
+
"ProxyRotator",
|
|
97
|
+
"Auth",
|
|
98
|
+
"BasicAuth",
|
|
99
|
+
"BearerAuth",
|
|
100
|
+
"DigestAuth",
|
|
101
|
+
"USE_CLIENT_DEFAULT",
|
|
102
|
+
]
|
fasthttpc/_api.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Session / AsyncSession'ın ortak, G/Ç'siz yardımcıları (requests/httpx uyumu).
|
|
2
|
+
|
|
3
|
+
params birleştirme, istek düzeyi çerez/zaman aşımı, gövde kodlama, URL'deki
|
|
4
|
+
``user:pass@`` ayrıştırma ve hook sözlüklerinin normalleştirilmesi burada
|
|
5
|
+
durur; senkron ve asenkron oturum aynı kodu kullanır (davranış farkı olmaz).
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import contextvars
|
|
10
|
+
from http.cookies import SimpleCookie
|
|
11
|
+
from typing import Any, Callable, Optional
|
|
12
|
+
from urllib.parse import unquote, urlencode, urlsplit
|
|
13
|
+
|
|
14
|
+
from .config import Timeout
|
|
15
|
+
from .multipart import encode_multipart
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _UseClientDefault:
|
|
19
|
+
"""``auth=`` / ``timeout=`` için "oturum varsayılanını kullan" işareti (httpx ile aynı ad)."""
|
|
20
|
+
|
|
21
|
+
def __repr__(self) -> str:
|
|
22
|
+
return "USE_CLIENT_DEFAULT"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
USE_CLIENT_DEFAULT: Any = _UseClientDefault()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# --- params -----------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
def _encode_params(params) -> str:
|
|
31
|
+
if params is None:
|
|
32
|
+
return ""
|
|
33
|
+
if isinstance(params, bytes):
|
|
34
|
+
return params.decode("latin-1")
|
|
35
|
+
if isinstance(params, str):
|
|
36
|
+
return params
|
|
37
|
+
items = params.items() if hasattr(params, "items") else params
|
|
38
|
+
flat = []
|
|
39
|
+
for k, v in items:
|
|
40
|
+
if v is None:
|
|
41
|
+
continue # requests gibi: None → parametre yok
|
|
42
|
+
if isinstance(v, (list, tuple)):
|
|
43
|
+
flat.extend((k, x) for x in v if x is not None)
|
|
44
|
+
else:
|
|
45
|
+
flat.append((k, v))
|
|
46
|
+
return urlencode([(k, _scalar(v)) for k, v in flat])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _scalar(v):
|
|
50
|
+
if isinstance(v, bool):
|
|
51
|
+
return "true" if v else "false" # httpx ile aynı
|
|
52
|
+
return v
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def merge_params(session_params, request_params) -> str:
|
|
56
|
+
"""Oturum ve istek parametrelerini tek query string'e çevirir.
|
|
57
|
+
|
|
58
|
+
İkisi de sözlükse istek aynı anahtarı ezer (``None`` anahtarı kaldırır);
|
|
59
|
+
aksi hâlde sıralı birleştirilir.
|
|
60
|
+
"""
|
|
61
|
+
if session_params is None:
|
|
62
|
+
return _encode_params(request_params)
|
|
63
|
+
if request_params is None:
|
|
64
|
+
return _encode_params(session_params)
|
|
65
|
+
if isinstance(session_params, dict) and isinstance(request_params, dict):
|
|
66
|
+
merged = {k: v for k, v in session_params.items()}
|
|
67
|
+
merged.update(request_params)
|
|
68
|
+
return _encode_params(merged)
|
|
69
|
+
return "&".join(p for p in (_encode_params(session_params),
|
|
70
|
+
_encode_params(request_params)) if p)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def apply_params(url: str, session_params, request_params) -> str:
|
|
74
|
+
"""``params`` sorgusunu URL'in mevcut sorgusuna EKLER (requests gibi)."""
|
|
75
|
+
extra = merge_params(session_params, request_params)
|
|
76
|
+
if not extra:
|
|
77
|
+
return url
|
|
78
|
+
frag = ""
|
|
79
|
+
if "#" in url:
|
|
80
|
+
url, _, frag = url.partition("#")
|
|
81
|
+
frag = "#" + frag
|
|
82
|
+
sep = "&" if "?" in url else "?"
|
|
83
|
+
if url.endswith(("?", "&")):
|
|
84
|
+
sep = ""
|
|
85
|
+
return f"{url}{sep}{extra}{frag}"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# --- URL'deki kimlik ----------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def split_userinfo(url) -> "tuple[str, Optional[tuple[str, str]]]":
|
|
91
|
+
"""``https://u:p@host/x`` -> (``https://host/x``, ``("u", "p")``)."""
|
|
92
|
+
username = getattr(url, "username", None)
|
|
93
|
+
if username is not None and not isinstance(url, str): # URL nesnesi
|
|
94
|
+
creds = (username, getattr(url, "password", "") or "")
|
|
95
|
+
return str(url), (creds if any(creds) else None)
|
|
96
|
+
url = str(url)
|
|
97
|
+
netloc = urlsplit(url).netloc
|
|
98
|
+
if "@" not in netloc:
|
|
99
|
+
return url, None
|
|
100
|
+
userinfo, _, hostport = netloc.rpartition("@")
|
|
101
|
+
user, _, pw = userinfo.partition(":")
|
|
102
|
+
clean = url.replace(netloc, hostport, 1)
|
|
103
|
+
creds = (unquote(user), unquote(pw))
|
|
104
|
+
return clean, (creds if any(creds) else None)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# --- gövde ----------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def _has(headers: dict, name: str) -> bool:
|
|
110
|
+
return any(k.lower() == name for k in headers)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def encode_body(data, json, files, headers: dict) -> bytes:
|
|
114
|
+
"""``data/json/files`` -> bayt gövde; gerekirse Content-Type ``headers``'a eklenir."""
|
|
115
|
+
import json as jsonlib
|
|
116
|
+
if files is not None:
|
|
117
|
+
body, ct = encode_multipart(fields=data if isinstance(data, dict) else None,
|
|
118
|
+
files=files)
|
|
119
|
+
if not _has(headers, "content-type"):
|
|
120
|
+
headers["Content-Type"] = ct
|
|
121
|
+
return body
|
|
122
|
+
if json is not None:
|
|
123
|
+
if not _has(headers, "content-type"):
|
|
124
|
+
headers["Content-Type"] = "application/json"
|
|
125
|
+
return jsonlib.dumps(json).encode("utf-8")
|
|
126
|
+
if isinstance(data, (dict, list, tuple)):
|
|
127
|
+
if not _has(headers, "content-type"):
|
|
128
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
129
|
+
return urlencode(data, doseq=True).encode("ascii")
|
|
130
|
+
if isinstance(data, str):
|
|
131
|
+
return data.encode("utf-8")
|
|
132
|
+
if isinstance(data, (bytes, bytearray)):
|
|
133
|
+
return bytes(data)
|
|
134
|
+
return b""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# --- çerezler -------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def merge_cookie_header(jar_header: str, extra) -> str:
|
|
140
|
+
"""Çerez kavanozundan gelen ``Cookie`` değerine istek çerezlerini ekler (istek kazanır)."""
|
|
141
|
+
if not extra:
|
|
142
|
+
return jar_header
|
|
143
|
+
pairs: dict = {}
|
|
144
|
+
for part in (jar_header or "").split(";"):
|
|
145
|
+
k, sep, v = part.strip().partition("=")
|
|
146
|
+
if sep:
|
|
147
|
+
pairs[k] = v
|
|
148
|
+
items = extra.items() if hasattr(extra, "items") else extra
|
|
149
|
+
for k, v in items:
|
|
150
|
+
pairs[str(k)] = str(v)
|
|
151
|
+
return "; ".join(f"{k}={v}" for k, v in pairs.items())
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def parse_cookie_arg(cookies) -> "dict[str, str]":
|
|
155
|
+
"""``cookies=`` (dict / (k, v) listesi / ``Cookie:`` metni) -> dict."""
|
|
156
|
+
if not cookies:
|
|
157
|
+
return {}
|
|
158
|
+
if isinstance(cookies, str):
|
|
159
|
+
sc = SimpleCookie()
|
|
160
|
+
sc.load(cookies)
|
|
161
|
+
return {k: m.value for k, m in sc.items()}
|
|
162
|
+
if hasattr(cookies, "as_dict"):
|
|
163
|
+
return dict(cookies.as_dict())
|
|
164
|
+
return {str(k): str(v) for k, v in (cookies.items() if hasattr(cookies, "items") else cookies)}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# --- zaman aşımı -------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
def resolve_timeout(value, session_timeout: Timeout) -> Optional[Timeout]:
|
|
170
|
+
"""İstek düzeyi ``timeout=`` -> Timeout; varsayılansa ``None`` (oturumunki geçerli).
|
|
171
|
+
|
|
172
|
+
float/int -> hepsi; ``(connect, read)`` ikilisi (requests); ``Timeout``;
|
|
173
|
+
``None`` -> sınırsız (``Timeout(None)``).
|
|
174
|
+
"""
|
|
175
|
+
if value is USE_CLIENT_DEFAULT:
|
|
176
|
+
return None
|
|
177
|
+
if isinstance(value, bool):
|
|
178
|
+
raise TypeError("timeout bool olamaz; saniye, (connect, read) ikilisi veya Timeout verin.")
|
|
179
|
+
if isinstance(value, Timeout):
|
|
180
|
+
return value
|
|
181
|
+
if value is None:
|
|
182
|
+
return Timeout(None)
|
|
183
|
+
if isinstance(value, (int, float)):
|
|
184
|
+
return Timeout(float(value))
|
|
185
|
+
if isinstance(value, (tuple, list)) and len(value) == 2:
|
|
186
|
+
c, r = value
|
|
187
|
+
return Timeout(session_timeout, connect=c, read=r)
|
|
188
|
+
raise TypeError(f"Geçersiz timeout değeri: {value!r}")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
_TIMEOUT_OVERRIDE: "contextvars.ContextVar[Optional[tuple]]" = contextvars.ContextVar(
|
|
192
|
+
"fasthttpc_timeout_override", default=None)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def set_timeout_override(owner, timeout: Optional[Timeout]):
|
|
196
|
+
"""İstek boyunca (iş parçacığı/görev başına) geçerli zaman aşımını ayarlar."""
|
|
197
|
+
return _TIMEOUT_OVERRIDE.set(None if timeout is None else (owner, timeout))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def reset_timeout_override(token) -> None:
|
|
201
|
+
_TIMEOUT_OVERRIDE.reset(token)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def effective_timeout(owner, default: Timeout) -> Timeout:
|
|
205
|
+
cur = _TIMEOUT_OVERRIDE.get()
|
|
206
|
+
return cur[1] if cur is not None and cur[0] is owner else default
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# --- hook'lar -------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
_RESPONSE_ONLY = ("response",)
|
|
212
|
+
_EVENT_KEYS = ("request", "response")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _as_list(v) -> list:
|
|
216
|
+
if v is None:
|
|
217
|
+
return []
|
|
218
|
+
if callable(v):
|
|
219
|
+
return [v]
|
|
220
|
+
return list(v)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def normalize_hooks(hooks, allowed=_RESPONSE_ONLY, label="hooks") -> "dict[str, list]":
|
|
224
|
+
"""``{"response": fn | [fn, ...]}`` -> ``{"response": [fn, ...]}`` (anahtarlar doğrulanır)."""
|
|
225
|
+
out = {k: [] for k in allowed}
|
|
226
|
+
for k, v in (hooks or {}).items():
|
|
227
|
+
if k not in allowed:
|
|
228
|
+
raise ValueError(f"{label}: bilinmeyen olay {k!r}; geçerli: {', '.join(allowed)}")
|
|
229
|
+
out[k] = _as_list(v)
|
|
230
|
+
return out
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def normalize_event_hooks(hooks) -> "dict[str, list]":
|
|
234
|
+
return normalize_hooks(hooks, _EVENT_KEYS, "event_hooks")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def hook_list(mapping, key: str) -> list:
|
|
238
|
+
"""Canlı (mutable) hook sözlüğünden ``key`` listesini okur (callable / liste kabul)."""
|
|
239
|
+
return _as_list((mapping or {}).get(key))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def hook_kwargs(session, timeout) -> dict:
|
|
243
|
+
"""requests hook'larına geçirilen ``**kwargs`` (``timeout``, ``verify`` ...)."""
|
|
244
|
+
return {"timeout": timeout, "verify": getattr(session, "verify", True),
|
|
245
|
+
"proxies": None, "stream": False, "cert": None}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
REDIRECT_STATUSES = (301, 302, 303, 307, 308)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def next_request(resp, method, current_url, headers, body):
|
|
252
|
+
"""``allow_redirects=False`` iken ``r.next``: izlenecek yönlendirme isteği (ya da None)."""
|
|
253
|
+
from .models.request import Request
|
|
254
|
+
from .models.urls import URL
|
|
255
|
+
from .session import _resolve_location
|
|
256
|
+
if resp.status_code not in REDIRECT_STATUSES:
|
|
257
|
+
return None
|
|
258
|
+
location = resp.headers.get("location", "")
|
|
259
|
+
if not location:
|
|
260
|
+
return None
|
|
261
|
+
target = _resolve_location(URL(str(current_url)), location)
|
|
262
|
+
hdrs = {k: v for k, v in dict(headers or {}).items() if v is not None}
|
|
263
|
+
if URL(target).origin != URL(str(current_url)).origin:
|
|
264
|
+
hdrs = {k: v for k, v in hdrs.items()
|
|
265
|
+
if k.lower() not in ("authorization", "cookie", "proxy-authorization")}
|
|
266
|
+
if resp.status_code in (301, 302, 303):
|
|
267
|
+
method, body = "GET", b""
|
|
268
|
+
hdrs = {k: v for k, v in hdrs.items() if k.lower() not in ("content-type", "content-length")}
|
|
269
|
+
return Request(method, target, headers=hdrs, data=body or None)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
Hook = Callable[..., Any]
|