PHPackages                             eav93/wreq-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. eav93/wreq-php

ActiveLibrary[HTTP &amp; Networking](/categories/http)

eav93/wreq-php
==============

PHP HTTP client with deterministic connection pooling and browser TLS/HTTP2 fingerprinting, built on the wreq Rust crate

v1.3.0(1w ago)0124↓73.7%LGPL-3.0-or-laterPHPPHP &gt;=8.1CI passing

Since May 22Pushed 1w agoCompare

[ Source](https://github.com/eav93/wreq-php)[ Packagist](https://packagist.org/packages/eav93/wreq-php)[ RSS](/packages/eav93-wreq-php/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)Dependencies (2)Versions (40)Used By (0)

wreq-php
========

[](#wreq-php)

A PHP HTTP client with **deterministic connection reuse** and **browser TLS/HTTP2 fingerprinting**, built on the Rust [`wreq`](https://crates.io/crates/wreq)crate.

Why
---

[](#why)

`curl` (and therefore most PHP HTTP clients) reuses TCP connections through an opaque, process-wide handle cache — you cannot reliably tell whether a keep-alive connection will be reused, or whether it leaks across unrelated handles.

`wreq-php` makes connection reuse **explicit and deterministic**:

- **One `Client` owns one connection pool.** Reuse the same `$client` and its keep-alive TCP/TLS connections are reused. Use a separate `$client` and you get a separate, fully isolated pool.
- **`close()` (or letting the client go out of scope) tears the pool down** — every idle socket is closed immediately.
- **Browser emulation** — TLS/JA3/HTTP2 fingerprints of real browsers, sourced straight from `wreq-util` (100+ profiles, kept current with the crate).

Architecture
------------

[](#architecture)

Two layers in one repository:

LayerWhat it isWhat it doesNative extension (`wreq_php`)Rust + `ext-php-rs`Owns the `wreq::Client` and its pool; executes requests; raw response.Composer package (`eav93/wreq-php`)Pure PHP, `Wreq\*`Laravel-style ergonomics: `Client`, immutable `PendingRequest`, `Response`.The native classes (`Wreq\Ext\*`) are a thin, fast core. The PHP layer wraps them — that is where `json()`, `object()`, `resource()`, the status helpers and the immutable per-request builder live.

Installation
------------

[](#installation)

```
composer require eav93/wreq-php
```

Composer downloads a prebuilt native binary matching your PHP version, OS and architecture. Enable it in `php.ini`:

```
extension=/path/printed/by/the/installer/wreq_php.so
```

No prebuilt binary for your platform? Build it from source:

```
cargo build --release
# then point php.ini at target/release/libwreq_php.so
```

### Docker

[](#docker)

Install the binary with the Composer installer **inside your runtime stage**. It downloads the `.so` from the same GitHub release as the Composer package, so the native binary and the PHP wrapper can never drift apart.

```
FROM php:8.3-cli

WORKDIR /app
COPY . /app
# ... install your Composer dependencies (vendor/) as you normally would ...

# Fetch the matching wreq_php binary and enable it. The installer picks the
# build for this image's PHP version, OS, libc and architecture.
RUN php -r 'require "vendor/autoload.php"; Wreq\Installer::run();' \
    && cp vendor/eav93/wreq-php/runtime/wreq_php-*.so "$(php-config --extension-dir)/wreq_php.so" \
    && docker-php-ext-enable wreq_php
```

Run the installer in the **final image**, not in a separate `composer` build stage — it must see the PHP version, libc and architecture the extension will actually run under (a `composer` image has a different PHP). It reads the installed package version through Composer and downloads `wreq_php-php-nts--.so` from that exact release, verifying the checksum. Bump the version in one place — `composer.lock` — and the next build fetches the matching binary. No `curl` needed: the installer uses PHP's HTTP wrapper (`allow_url_fopen`, on by default in the official `php` images).

As a safety net the library also compares, on first use, the binary version (`Wreq\Client::extensionVersion()`) against the package version and throws `Wreq\Exceptions\VersionMismatchException` on a major.minor mismatch.

ZTS PHP builds are not covered — the prebuilt binary is NTS.

Usage
-----

[](#usage)

```
use Wreq\Client;

// One reusable client === one connection pool.
$client = new Client([
    'emulation'              => 'chrome_131', // browser fingerprint
    'pool_max_idle_per_host' => 8,            // TCP connections kept per host
    'cookies'                => true,         // shared cookie jar
    'timeout'                => 30.0,
]);

$response = $client->get('https://api.example.com/users', ['page' => 1]);

$response->status();          // int
$response->successful();      // 2xx?
$response->body();            // string
$response->json('data.0.name', 'default'); // dot-notation + default
$response->object();          // stdClass graph
$response->header('Content-Type');

// POST JSON (default) — connections reused from the same pool.
$client->post('https://api.example.com/users', ['name' => 'Ada']);

// Per-request tweaks return a new immutable builder; the pool is untouched.
$client->asForm()->post($url, ['field' => 'value']);
$client->withToken('secret')->withHeaders(['X-Trace' => '1'])->get($url);

// multipart/form-data — attach files alongside text fields.
$client->attach('photo', file_get_contents('p.jpg'), 'p.jpg', 'image/jpeg')
       ->post($url, ['caption' => 'Sunset']);

// Stream large downloads straight to disk — the body never enters PHP memory,
// so peak memory stays flat no matter how big the file is.
$response = $client->sink('/tmp/large.zip')->get('https://example.com/large.zip');
$response->savedTo();         // '/tmp/large.zip'
$response->downloadedBytes(); // bytes written (body() is empty for a sink)

// Release the pool and close every idle socket now.
$client->close();
```

### Client options

[](#client-options)

Every `wreq::ClientBuilder` setting expressible as a PHP scalar is supported.

OptionTypeMeaning`emulation`stringBrowser profile (`chrome_131`, `firefox_136`, …).`emulation_os`string`windows`, `macos`, `linux`, `android`, `ios`.`skip_http2` / `skip_headers`boolEmulation fingerprint toggles.`user_agent`stringDefault `User-Agent`.`headers`arrayDefault headers for every request.`base_url`stringPrefix for relative request paths.`pool_max_idle_per_host`intIdle keep-alive sockets kept per host.`pool_max_size`intMax total connections in the pool.`pool_idle_timeout`floatIdle socket lifetime, seconds.`timeout` / `read_timeout` / `connect_timeout`floatRequest / body-read / connect timeouts, seconds.`cookies`boolEnable a per-client cookie jar.`gzip` / `brotli` / `zstd` / `deflate`boolToggle response decompression.`max_redirects`intRedirect limit; `0` disables following.`referer`boolAuto-set the `Referer` header.`http1_only` / `http2_only` / `https_only`boolRestrict protocol / scheme.`connection_verbose`boolVerbose connection tracing.`tcp_nodelay` / `tcp_reuse_address`boolTCP socket options.`tcp_keepalive` / `tcp_keepalive_interval` / `tcp_user_timeout` / `tcp_happy_eyeballs_timeout`floatTCP timers, seconds.`tcp_keepalive_retries`intTCP keep-alive probe count.`tcp_send_buffer_size` / `tcp_recv_buffer_size`intSocket buffer sizes, bytes.`local_address`stringBind to a local IP.`interface`stringBind to a network interface (Unix only).`proxy`stringProxy URL for all requests (`http://`, `https://`, `socks4://`, `socks4a://`, `socks5://`, `socks5h://`).`no_proxy`boolIgnore proxies, including system ones.`no_hickory_dns`boolUse the system DNS resolver.`resolve`arrayDNS overrides, `host => "ip:port"`.`verify`boolVerify TLS certificate and hostname (default `true`).`tls_sni` / `tls_info`boolTLS SNI / expose TLS info.`min_tls_version` / `max_tls_version`string`"1.0"`–`"1.3"`.Settings that need Rust objects (client certificates, custom cert/DNS stores, HTTP/2 frame tuning, tower layers) are not exposed — use the `wreq` crate directly for those.

### Emulation profiles

[](#emulation-profiles)

```
use Wreq\Emulation;

Emulation::all();              // every supported profile name
Emulation::random();           // a random profile
Emulation::random('chrome');   // a random Chrome version
Emulation::like('firefox');    // all Firefox profile names
Emulation::exists('chrome_131');
```

A random browser version with a fixed OS:

```
new Wreq\Client(['emulation' => [
    'profile' => Emulation::random('chrome'),
    'os'      => 'windows',
]]);
```

Development
-----------

[](#development)

Building the extension needs a PHP install with development files (`php-config` and headers). On macOS, `brew install php` provides them; Laravel Herd's PHP does not. Point the build at it if it is not on `PATH`:

```
export PHP_CONFIG=$(command -v php-config)
cargo build --release                        # build the extension
composer install                             # PHP dev dependencies
php -d extension=./target/release/libwreq_php.so vendor/bin/phpunit
```

The pure-PHP test suite runs without the extension; integration tests are skipped automatically when it is not loaded.

License
-------

[](#license)

**LGPL-3.0-or-later.** The browser-emulation crate `wreq-util` is LGPL-3.0, and the prebuilt binaries statically link it, so the project as a whole is LGPL-3.0. See [`LICENSE`](LICENSE) and [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md) for details and the relinking provisions.

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance98

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity54

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

39

Last Release

12d ago

Major Versions

v0.3.9 → v1.0.02026-05-27

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4afa3b245e6037c7af2a43c7172e43316fb86c83aac6a6da7e05a6989df3521?d=identicon)[eav93](/maintainers/eav93)

---

Top Contributors

[![eav93](https://avatars.githubusercontent.com/u/11286496?v=4)](https://github.com/eav93 "eav93 (62 commits)")

---

Tags

httpclienttlsFingerprintemulationkeep-alivewreq

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/eav93-wreq-php/health.svg)

```
[![Health](https://phpackages.com/badges/eav93-wreq-php/health.svg)](https://phpackages.com/packages/eav93-wreq-php)
```

###  Alternatives

[m6web/guzzle-http-bundle

Symfony bundle on top of Guzzle

17514.2k2](/packages/m6web-guzzle-http-bundle)[swoole/etcd-client

Grpc PHP Client base on Swoole Http2 Coroutine

1973.3k1](/packages/swoole-etcd-client)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28146.3k](/packages/phpro-http-tools)[chillerlan/php-httpinterface

A PSR-7/17/18 http message/client implementation

1417.8k6](/packages/chillerlan-php-httpinterface)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
