Short answers
Should I use HTTPX or Requests?
Keep Requests when its synchronous interface fits a maintained application. Consider HTTPX when you need its native async client or optional HTTP/2, then test the workflow before migrating. Neither choice guarantees faster requests or better proxy results.
Can I replace requests.get with httpx.get?
Do not assume identical behaviour. Requests GET follows redirects by default; HTTPX does not. Timeout defaults, response-error checks, proxy arguments and exception classes also differ. Make the intended policy explicit.
Does HTTPX use the same proxies argument as Requests?
No. Current HTTPX Client uses proxy= for one gateway; Requests uses a proxies dictionary. Complex HTTPX routing uses its supported transport configuration. Configure the client version actually installed.
Choose for the application you need to maintain
Requests fits a synchronous job that already works with its API and dependencies. HTTPX is useful when the application needs a native async client, an explicit transport configuration or optional HTTP/2. Migrating just to obtain a different library name adds work without establishing a better result. Decide which interface the application needs, then test the behaviour that matters to that job.
This comparison concerns the documented default clients, rather than every third-party adapter. If the question is simply how to connect through a gateway, use the complete Requests proxy setup or HTTPX proxy setup. Their credentials, certificate trust and proxy arguments are deliberately different.
| Decision | Requests | HTTPX |
|---|---|---|
| Synchronous work | Session provides a synchronous interface. | Client provides a synchronous interface; changing libraries does not require async. |
| Async application | The default adapter performs blocking I/O. Third-party integrations are outside this comparison. | AsyncClient supports async requests; the calling application still needs bounded concurrency. |
| Connection lifetime | Reuse a scoped Session for related requests. | Reuse a scoped Client or AsyncClient for related requests. |
| GET redirects by default | Follows redirects. HEAD has a different default. | Does not follow redirects unless enabled. |
| Timeout defaults | No timeout unless supplied. | Default network-inactivity protection; configure the phases deliberately. |
| One proxy gateway | A proxies dictionary maps destination schemes to gateway URLs. | Current Client accepts proxy=; use supported transports/mounts for more complex routing. |
| HTTP/2 | Not provided by the default Requests adapter. | Optional dependencies plus http2=True; actual protocol depends on negotiation. |
The HTTPX compatibility guide documents the API differences. Its native async interface is a reason to consider a migration, not evidence that a particular workload will run faster. HTTP/2 support is opt-in and does not guarantee that every response uses HTTP/2. No throughput benchmark is claimed here.
Make redirects and accepted responses explicit
A Requests GET can receive a redirect and return the response from a later request. An HTTPX GET normally returns the redirect itself. A simple rename can therefore change both the visible status and the number of requests. Use allow_redirects=False with Requests or follow_redirects=False with HTTPX when the job must inspect the first response. If redirects are part of the intended workflow, enable them deliberately and review allowed destinations and request limits.
There is another migration trap: Requests raise_for_status() raises for 4xx and 5xx responses; HTTPX raises for responses outside 2xx. In our local 302 check, Requests returned without an exception and HTTPX raised HTTPStatusError. Neither behaviour defines your application’s success. A job expecting one JSON document can require status 200 and specific content before accepting the result. See the Requests response checks and HTTPX status checks.
Keep response errors separate from transport errors. An HTTP 429 is a response requiring the service’s rate-limit policy; a failed connection or tunnel may have no destination response at all. An exception handler copied from Requests will not automatically catch HTTPX’s exception classes. Record the client’s error category without printing a credential-bearing URL or the raw exception. Use 429 troubleshooting for retry decisions.
Transfer the timeout policy rather than a number
Requests accepts a connection/read tuple, such as (2, 3). HTTPX separates connect, read, write and connection-pool waits, so httpx.Timeout(3, connect=2, pool=1) gives those phases different example limits. These numbers demonstrate configuration; they are not recommended limits for every network. Select budgets from your authorised job’s requirements.
A read limit is a wait for incoming data, not a deadline for the entire download. A response that keeps supplying chunks can last longer. Give the worker a separate overall deadline and cap its queue, attempts and response size. HTTPX’s timeout reference explains the phases; Requests documents its timeout boundaries. Changing clients does not create an overall job deadline.
Preserve the intended network and trust policy
Both clients can read environment proxy configuration. A worker shell, notebook and deployed service may therefore use different routes. Make the policy explicit before comparing results. trust_env=False disables environment-derived behaviour; it also affects certificate or authentication configuration, depending on the client. Do not use it to bypass an organisation’s required proxy or approved CA policy.
The HTTPX environment reference covers its proxy and certificate variables. Requests documents environment proxies in advanced usage and its Session trust setting. The existing Portproof setup guides supply complete explicit gateway and trust settings. Keep TLS verification enabled, and keep gateway credentials separate from destination login.
A Python Session or Client owns connections and cookies; it is not the gateway’s sticky-session setting. Reusing a connection can preserve a tunnel, so a new function call does not guarantee a new exit address. Preserve the generated gateway username for the intended workflow and consult rotation and sticky sessions. Dedicated-device rental and shared traffic billed by GB also remain separate products.
Use the same small response contract in both clients
The following helpers expect a synthetic JSON document with ready equal to true. Both reject a redirect or any status other than 200 before reading the body. They cap collected decoded content at 64 KiB, disable redirects, add no retry, and close the response and client on success or failure. The output reports status and a category; it never prints the URL or a response body.
python -m pip install "requests==2.34.2" "httpx==0.28.1"import json
import httpx
import requests
def read_result(response, chunks):
status = response.status_code
if status != 200:
return {"status": status, "accepted": False, "reason": "status"}
body = bytearray()
for chunk in chunks:
if len(body) + len(chunk) > 65536:
raise ValueError("Response exceeds 64 KiB")
body.extend(chunk)
payload = json.loads(body)
accepted = isinstance(payload, dict) and payload.get("ready") is True
return {"status": status, "accepted": accepted,
"reason": "accepted" if accepted else "content"}
def requests_check(url):
status = None
try:
with requests.Session() as client:
client.trust_env = False
with client.get(url, timeout=(2, 3), allow_redirects=False,
stream=True, verify=True) as response:
status = response.status_code
return read_result(response, response.iter_content(8192))
except requests.RequestException as error:
return {"status": status, "accepted": False,
"reason": type(error).__name__}
except ValueError:
return {"status": status, "accepted": False, "reason": "ValueError"}
def httpx_check(url):
status = None
try:
with httpx.Client(trust_env=False, follow_redirects=False,
verify=True,
timeout=httpx.Timeout(3, connect=2, pool=1)) as client:
with client.stream("GET", url) as response:
status = response.status_code
return read_result(response, response.iter_bytes(8192))
except httpx.HTTPError as error:
return {"status": status, "accepted": False,
"reason": type(error).__name__}
except ValueError:
return {"status": status, "accepted": False, "reason": "ValueError"}For a local fixture URL, call either requests_check(url) or httpx_check(url). These are isolated direct-connection examples with environment settings disabled. They do not configure a Portproof gateway or replace the complete setup guides. The Requests tuple limits connection/read waits; HTTPX also limits write/pool waits. The decoded-content cap does not measure billable proxy traffic or impose a whole-job deadline.
Each helper opens a client for one diagnostic read. In an application processing related requests, move that client’s lifetime outside the loop and preserve response cleanup. Do not create an AsyncClient inside every iteration of a hot loop. Start with one authorised task before adding concurrency or implementing a retry policy.
What we checked locally
On 23 October 2026, we ran the exact displayed Python helpers with Python 3.12.14, Requests 2.34.2 and HTTPX 0.28.1 against a synthetic loopback HTTP origin. No live proxy, real credential or external destination was used. Separate checks observed the clients’ default GET redirect behaviour and their handling of an unfollowed 302.
| Case | Observed result |
|---|---|
| Default Requests GET to one local redirect | Followed the 302 to the local 200 endpoint; two origin requests. |
| Default HTTPX GET to that redirect | Returned 302; one origin request. |
| Calling raise_for_status on an unfollowed 302 | Requests did not raise; HTTPX raised HTTPStatusError. |
| Displayed helpers: valid JSON, redirect and 429 | Accepted the required 200 JSON; rejected 302 and 429 with one origin request each and no retry. |
| Displayed helpers: invalid JSON, wrong content and oversized body | Rejected all three; HTTP 200 alone was insufficient. |
| Displayed helpers: headers or body stalled | Before headers, both reported ReadTimeout. With headers received and the body stalled, Requests reported ConnectionError and HTTPX ReadTimeout in this streamed-response fixture. |
| Conflicting synthetic proxy, CA and netrc environment | The explicitly isolated helpers still reached the local origin; no environment-selected gateway was used. |
Migrate one observable workflow before scaling
- Define the accepted response: status, content type or body shape, and any required application field. Keep rejected tasks in the record.
- Copy the intended redirect, proxy, trust, timeout and cookie policies into the new client’s supported interface. Check the installed version and remove obsolete arguments.
- Review existing exception handlers and retries. Do not multiply an adapter retry policy by an outer loop or automatically replay an uncertain purchase or form submission.
- Run a small controlled read and compare request count, response acceptance and cleanup. A successful echo or parsed JSON alone does not establish every later application request.
- Measure one representative permitted workflow, then use the traffic planning guide and current pricing for shared traffic sold by GB. A library migration alone provides no bandwidth saving or performance guarantee.
What is not allowed
Use these clients for authorised QA, monitoring and research that respects the destination’s terms and access rules. A working connection does not grant permission to collect information or send requests. See the acceptable-use policy.
Read next
HTTPX proxy setup for sync and async
Tested Python Client and AsyncClient examples with proxy authentication, explicit settings and response cleanup.
Read the guide
Python Requests proxy setup
A complete Python check with HTTP and SOCKS5, predictable settings and a practical failure checklist.
Read the guide