mirror of
https://github.com/littledivy/mimic.git
synced 2026-10-26 19:15:10 +00:00
fix: harden HTTP error handling and auth retries (#1)
This commit is contained in:
1 parent
64f1f94af9
commit
67441bfaba
2 files changed
+45
-9
No files matched your search
@@ -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
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user