fix: harden HTTP error handling and auth retries (#1)

This commit is contained in:
Sma Das authored and GitHub committed 2026-07-14 09:18:23 +05:30
1 parent 64f1f94af9
commit 67441bfaba
2 files changed
+45 -9

No files matched your search

+5 -2
View File
@@ -81,8 +81,11 @@ Session.from_curl(open("copied.txt").read()) # paste "Copy as cURL" from de
Session(base_url="https://x.com", headers={...}) # explicit
```
`.get(path)` and `.post(path, json=...)` return parsed JSON. If your token
rotates, a `401`/`403` triggers one re-pull from mitmweb and a retry.
`.get(path)`, `.post(path, json=...)`, and the other common HTTP verb helpers
return parsed JSON and raise `requests.HTTPError` for failed responses. If your
token rotates, a `401` on an idempotent request triggers one re-pull from
mitmweb and a retry. Non-idempotent requests are not retried unless you explicitly
pass `refresh=True`.
## Capture backends
+40 -7
View File
@@ -13,6 +13,9 @@ from . import extract
from .sources.mitm import Mitm
_IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "PUT", "DELETE"})
class Session:
"""Base URL + reusable headers, with helpers that return parsed JSON.
@@ -48,17 +51,32 @@ class Session:
return cls(base_url, headers)
# ---- calls --------------------------------------------------------------
def request(self, method, path, json=None, params=None, refresh=True, **kw):
def request(self, method, path, json=None, params=None, refresh=None, **kw):
"""Make a request and return its parsed body.
Failed responses raise ``requests.HTTPError``. A 401 refreshes captured
credentials and retries idempotent methods once. Pass ``refresh=True``
to explicitly allow one retry for a non-idempotent method such as POST.
"""
method = method.upper()
url = path if path.startswith("http") else f"{self.base_url}{path}"
r = self._http.request(
method, url, headers=self.headers, json=json, params=params, **kw
)
if r.status_code in (401, 403) and refresh and self.host and self._mitm:
# token likely rotated — re-pull from mitm once and retry
self.headers = extract.session_headers(self._mitm.flows(), self.host)
return self.request(
method, path, json=json, params=params, refresh=False, **kw
)
should_refresh = refresh is True or (
refresh is None and method in _IDEMPOTENT_METHODS
)
if r.status_code == 401 and should_refresh and self.host and self._mitm:
# Retry only when mitmweb has a non-empty, changed credential set.
# Keeping the old headers avoids turning a refresh miss into an
# unauthenticated retry.
new_headers = extract.session_headers(self._mitm.flows(), self.host)
if new_headers and new_headers != self.headers:
self.headers = new_headers
return self.request(
method, path, json=json, params=params, refresh=False, **kw
)
r.raise_for_status()
return _parse(r)
def get(self, path, **kw):
@@ -67,6 +85,21 @@ class Session:
def post(self, path, json=None, **kw):
return self.request("POST", path, json=json, **kw)
def put(self, path, json=None, **kw):
return self.request("PUT", path, json=json, **kw)
def patch(self, path, json=None, **kw):
return self.request("PATCH", path, json=json, **kw)
def delete(self, path, json=None, **kw):
return self.request("DELETE", path, json=json, **kw)
def head(self, path, **kw):
return self.request("HEAD", path, **kw)
def options(self, path, **kw):
return self.request("OPTIONS", path, **kw)
class App(Session):
"""Subclass this in a generated client and add named methods.