Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Http Laravel Package

react/http

Event-driven, streaming HTTP client and server for ReactPHP. Send concurrent async HTTP/HTTPS requests and build plaintext or secure servers that handle many requests without blocking.

View on GitHub
Deep Wiki
Context7

Getting Started

react/http is distributed as react/http and requires an event loop from react/event-loop (plus react/socket and react/promise, pulled in transitively). The install is trivial:

composer require react/http

Two entry points account for almost everything you'll do:

  • React\Http\Browser — the async HTTP client. Each verb (get(), post(), put(), patch(), delete(), head()) returns a React\Promise\PromiseInterface that fulfils with a PSR-7 ResponseInterface or rejects with an Exception.
  • React\Http\HttpServer — a request handler wrapped around a React\Socket\SocketServer, producing PSR-7 ServerRequestInterface and expecting a ResponseInterface (or a promise for one).

The first five minutes should be: instantiate a Browser, fire two GETs, and confirm they interleave rather than run sequentially. That is the whole payoff of the library — non-blocking I/O with a familiar PSR-7 surface.

$browser = new React\Http\Browser();
$browser->get('https://example.com/a')->then(fn ($r) => print $r->getBody());
$browser->get('https://example.com/b')->then(fn ($r) => print $r->getBody());

For the server side, mirror the README quickstart: an HttpServer closure returning Response::plaintext() or Response::json(), handed to SocketServer::listen(). Read the examples/ directory next — it is the real documentation for streaming, proxies, and middleware. Then read the API sections for Browser::with*() and React\Http\Middleware\*, because those two groups define the library's actual workflow model.

A realistic first production use case is not replacing your web framework. It is a fan-out worker: an Artisan command or long-running daemon that fires concurrent calls at an internal API, a webhook dispatcher, or a health-check probe. That is where the async model pays for itself immediately and the blast radius is small.

Implementation Patterns

Treat Browser as an immutable, pre-configured service. Every withTimeout(), withBase(), withFollowRedirects(), withRejectErrorResponse(), withHeader(), and withResponseBuffer() returns a clone. The idiomatic pattern is to build one configured instance at container-registration time — base URI, auth header, timeout, redirect policy — and inject it read-only wherever needed:

$browser = (new Browser(new Connector(['timeout' => 5])))
    ->withBase('https://api.internal/v1/')
    ->withTimeout(10.0)
    ->withHeader('Authorization', 'Bearer ' . $token)
    ->withFollowRedirects(false);

Bind this as a singleton in your service container; do not mutate it per request.

Concurrency is handled by promises, not by you. Use React\Promise\all() when every call must succeed and React\Promise\allSettled() (or per-promise then(null, $onError)) when partial failure is acceptable. For hundreds of simultaneous requests, cap in-flight work yourself — the client will happily open them all and exhaust file descriptors.

Streaming is the escape hatch for anything large or unbounded. The default get()/post() keeps the entire response body in memory. Switch to requestStreaming() when any of these hold: >100 concurrent requests, expect >1 MiB bodies, or you want to process chunks as they arrive. Attach data, end, and error handlers or pipe() into a filesystem stream:

$browser->requestStreaming('GET', $url)->then(function (ResponseInterface $response) use ($sink) {
    $response->getBody()->pipe($sink);
});

The same holds symmetrically for uploads: pass a ReadableStreamInterface as the body to post()/put() and it will use Transfer-Encoding: chunked unless you supply an explicit Content-Length.

Server handlers should return promises for anything deferred. A handler may return a ResponseInterface synchronously or a promise that resolves to one. This is how you await a database or downstream API without blocking the loop. Wrap the whole handler chain in a try/catch + rejection handler so no exception escapes as an unhandled rejection (which silently kills the connection).

Middleware is a plain callable chain. Signature: function (ServerRequestInterface $request, callable $next): ResponseInterface|PromiseInterface. Use it for routing, CORS, request IDs, auth, and structured access logging — the same mental model as Laravel middleware, minus container magic. Ship-order matters: StreamingRequestMiddleware must come first if you want streaming bodies; RequestBodyBufferMiddleware + RequestBodyParserMiddleware must run before any handler that reads parsed JSON or form data, because buffering consumes the stream.

Reach for the bundled middleware rather than hand-rolling: LimitConcurrentRequestsMiddleware (backpressure for expensive handlers), RequestBodyBufferMiddleware (with a size limit — set one, the default is unlimited), and RequestBodyParserMiddleware (JSON/form decoding into getParsedBody()).

Use the transport options for infrastructure concerns. Browser accepts a custom Connector, which is the extension point for SOCKS/SSH proxies, Unix domain sockets, DNS behaviour, and connection timeouts. For servers, SocketServer handles TLS via stream context — but do that behind nginx in practice.

Where it fits in a Laravel codebase: put it in queued jobs, Artisan daemons (via supervisor), or a standalone service. Do not try to run the event loop inside an FPM request — Laravel's kernel is synchronous and you would be building a second runtime inside the first.

Gotchas and Tips

  • with*() returns a new object. $browser->withTimeout(5); without reassignment silently does nothing. This is the single most common first-day bug.
  • default_socket_timeout is the default timeout (60s) and covers connect + send + headers + full buffered body + redirects. Streaming request/response bodies are excluded from it — so a stalled stream can hang indefinitely. Always set an explicit withTimeout() and add your own timer or cancellation for streaming paths.
  • Cancel promises to free sockets. $promise->cancel() rejects with an exception and tears down the connection. Use it on request-abort or when a fan-out has already lost its race.
  • Blocking code in a handler blocks everything. sleep(), synchronous PDO/Redis, file_get_contents(), CPU-heavy transforms — all of them stall the entire loop for every concurrent connection. Use async drivers (clue/reactphp-redis, reactphp/mysql) or push work out to a queue.
  • The default client buffers the whole body. Set withResponseBuffer() to a sane cap so a hostile or misconfigured endpoint can't OOM your worker; combined with requestStreaming(), you get both safety and throughput.
  • Rejections must be handled. An unhandled rejected promise in a server handler produces a closed connection rather than a 500. Always terminate the chain with an error handler returning Response::plaintext(...)->withStatus(500).
  • HTTP/1.1 only. No HTTP/2, no server push. Terminate TLS and HTTP/2 at nginx/Caddy and proxy to the ReactPHP server over plaintext loopback.
  • Version pinning matters. The repository's master tracks an unreleased v3 API while 1.x is the stable release. Pin ^1.0 unless you are deliberately tracking development, and re-read the changelog before bumping — the message/Browser surface has shifted between lines.
  • Request body streams are single-read. Once buffering middleware has drained the body, later middleware sees an empty stream. Order the chain accordingly, and never read the body twice.
  • Response framing is your responsibility. Set Content-Length for known-size bodies; omit it and the server falls back to chunked/connection-close semantics. A missing length on a keep-alive connection is a classic source of hung clients.
  • Authorization is stripped on cross-host redirects by default — good for security, surprising if you rely on redirects to an authenticated CDN. Set withFollowRedirects(false) and follow manually when you need control.
  • Debugging: there is no built-in logging. Instrument middleware for request/access logs, wrap promise rejections with context, and remember that Loop::run() must be called exactly once at the top level — nested Loop::run() calls throw. In tests, drive the loop explicitly or use React\Async\await() to make assertions on resolved values.
  • Extension points worth knowing: custom Connector (proxies, sockets, DNS), custom loop instance (for deterministic tests with a fake clock), middleware (all cross-cutting behaviour), and ResponseException + withRejectErrorResponse() for turning non-2xx into rejections instead of fulfilled responses you must inspect.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager