PHPackages                             pac/lean-http - 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. pac/lean-http

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

pac/lean-http
=============

A lean, modern, and standards-compliant PSR-7 HTTP message library for PHP. Includes robust URI normalization, validation, and building — without routers, middleware, or extra dependencies.

1.0.0(3mo ago)01MITPHPPHP ^8.3CI passing

Since Feb 1Pushed 3mo agoCompare

[ Source](https://github.com/pedroac/lean-http)[ Packagist](https://packagist.org/packages/pac/lean-http)[ RSS](/packages/pac-lean-http/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (7)Versions (2)Used By (0)

pac/lean-http
=============

[](#paclean-http)

[![PHP Version](https://camo.githubusercontent.com/91e2ff786d2fba1edf015025006e0156a071320b3662eaf2c50f39d4bb4b2369/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e332d626c7565)](https://camo.githubusercontent.com/91e2ff786d2fba1edf015025006e0156a071320b3662eaf2c50f39d4bb4b2369/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e332d626c7565)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)[![Stable](https://camo.githubusercontent.com/f3adeea933a64c2014c89092040b8c02f4931f3f5a5d46a189133d4ac21d0ebf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7374617475732d737461626c652d627269676874677265656e)](https://camo.githubusercontent.com/f3adeea933a64c2014c89092040b8c02f4931f3f5a5d46a189133d4ac21d0ebf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7374617475732d737461626c652d627269676874677265656e)[![codecov](https://camo.githubusercontent.com/b0efec8cff9e9b6acc9b06cc84ca0e899ceb622bfd5d916dff5ec5c954e4f354/68747470733a2f2f636f6465636f762e696f2f67682f706564726f61632f6c65616e2d687474702f67726170682f62616467652e7376673f746f6b656e3d4f494238535646314e4f)](https://codecov.io/gh/pedroac/lean-http)[![Buy Me a Coffee](https://camo.githubusercontent.com/779326b871dea012d511a6ff2a95a99d03cfb628e66fdad1aadb27f78d7a68f2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275792532306d6525323061253230636f666665652d2545322539382539352d79656c6c6f77)](https://www.buymeacoffee.com/pedroac)

A fast, developer-friendly [PSR-7](https://www.php-fig.org/psr/psr-7/) HTTP message library for PHP, focused on robust URI handling, message validation, and convenient request/response operations. Designed for modern PHP applications that need a lightweight, standards-compliant foundation for HTTP messaging.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Why lean-http?](#why-lean-http)
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Features](#core-features)
    - [HTTP Messages](#http-messages)
    - [Body Parsing](#body-parsing)
    - [URI Handling](#uri-handling)
    - [File Uploads](#file-uploads)
- [Advanced Usage](#advanced-usage)
- [Contributing](#contributing)
- [License](#license)

---

Why lean-http?
--------------

[](#why-lean-http)

**lean-http** provides comprehensive HTTP message features without the bloat. Unlike full-stack frameworks, this library focuses solely on HTTP messaging:

- ✅ **PSR-7 Compliant** - Full implementation of the PSR-7 standard
- ✅ **Zero Dependencies** - Only requires `psr/http-message` interface
- ✅ **Lightweight** - No routers, middleware, or application-layer code
- ✅ **Production Ready** - Optimized for performance and reliability
- ✅ **Well Tested** - High test coverage with comprehensive test suite
- ✅ **Type Safe** - Built for PHP 8.3+ with strict types and static analysis

Perfect for building custom HTTP applications, API clients, or when you need a solid foundation without framework overhead.

---

Features
--------

[](#features)

### 🎯 PSR-7 Implementation

[](#-psr-7-implementation)

Complete implementation of the [PSR-7 HTTP message interfaces](https://www.php-fig.org/psr/psr-7/), including:

- `MessageInterface` - Headers, protocol version, and body management
- `RequestInterface` - HTTP request methods, URI, and request target
- `ServerRequestInterface` - Server-side request with cookies, query params, and uploaded files
- `ResponseInterface` - HTTP response with status codes and reason phrases
- `UriInterface` - Full URI manipulation and normalization
- `StreamInterface` - Stream-based body handling
- `UploadedFileInterface` - Secure file upload handling

### 🔍 Smart Body Parsing

[](#-smart-body-parsing)

Automatically parses request/response bodies based on `Content-Type`:

- **JSON** (`application/json`) - Automatic JSON decoding
- **Form Data** (`application/x-www-form-urlencoded`) - Query string parsing
- **Multipart** (`multipart/form-data`) - Form data with file uploads
- **CSV** (`text/csv`) - CSV row parsing
- **XML** (`text/xml`, `application/xml`) - DOMDocument parsing with XXE protection
- **HTML** (`text/html`) - HTML parsing with XXE protection

### 🔗 Advanced URI Handling

[](#-advanced-uri-handling)

Comprehensive URI tools for validation, normalization, and building:

- **URI Normalization** - Removes dot segments, normalizes encoding, handles default ports
- **URI Validation** - Validates schemes, hosts, ports, paths, queries, and fragments
- **URI Builder** - Construct URIs from components with type safety
- **Query Parameter Helpers** - Easy conversion between query strings and arrays
- **IDN Support** - Internationalized domain name handling (requires `ext-intl`)

### 📤 File Upload Management

[](#-file-upload-management)

Secure and convenient file upload handling:

- Automatic parsing from `$_FILES` superglobal
- Support for single and multiple file uploads
- Nested file array structures
- Secure file movement with validation
- Upload error handling

### 🛡️ Security Features

[](#️-security-features)

Built-in security best practices:

- XXE (XML External Entity) attack prevention
- Header injection protection
- Path traversal protection for file uploads
- Input validation and sanitization
- Specialized exception types for better error handling

### ⚡ Developer Experience

[](#-developer-experience)

- **From Globals** - Easy instantiation from `$_SERVER`, `$_GET`, `$_POST`, `$_FILES`
- **Immutable Objects** - All message objects are immutable (PSR-7 compliant)
- **Type Safety** - Full PHP 8.3+ type hints and return types
- **Clear Exceptions** - Specialized exception classes for different error types
- **Comprehensive Documentation** - Well-documented code with examples

---

Requirements
------------

[](#requirements)

- **PHP 8.3+** - Modern PHP features and performance
- **Composer** - For dependency management

### Optional Extensions

[](#optional-extensions)

- `ext-intl` - Recommended for IDN (Internationalized Domain Names) support
- `ext-xml` - Recommended for XML/HTML body parsing

---

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

[](#installation)

Install via [Composer](https://getcomposer.org/):

```
composer require pac/lean-http
```

---

Quick Start
-----------

[](#quick-start)

### Handle an HTTP Request

[](#handle-an-http-request)

```
use Pac\LeanHttp\ServerRequest;
use Pac\LeanHttp\Response;

// Create request from PHP globals
$request = ServerRequest::fromGlobals();

// Parse request body automatically based on Content-Type
$data = $request->parseBody(); // Returns array, object, or null

// Access request data
$method = $request->getMethod(); // 'GET', 'POST', etc.
$uri = $request->getUri();
$headers = $request->getHeaders();
$queryParams = $request->getQueryParams();
$cookies = $request->getCookieParams();
```

### Create an HTTP Response

[](#create-an-http-response)

```
use Pac\LeanHttp\Response;
use Pac\LeanHttp\Stream;

// Simple response
$response = new Response(200);
$response->getBody()->write('Hello, World!');

// Response with automatic content type handling
$response = Response::byContentType(
    200,
    ['message' => 'Success', 'data' => [1, 2, 3]],
    ['Content-Type' => 'application/json']
);
// Body is automatically JSON-encoded
```

### Handle File Uploads

[](#handle-file-uploads)

```
$request = ServerRequest::fromGlobals();
$uploadedFiles = $request->getUploadedFiles();

// Single file
$file = $uploadedFiles['avatar'] ?? null;
if ($file) {
    $file->moveTo('/path/to/uploads/' . $file->getClientFilename());
}

// Multiple files
foreach ($uploadedFiles['images'] ?? [] as $file) {
    $file->moveTo('/path/to/uploads/' . $file->getClientFilename());
}
```

---

Core Features
-------------

[](#core-features)

### HTTP Messages

[](#http-messages)

#### Creating Requests

[](#creating-requests)

```
use Pac\LeanHttp\ServerRequest;
use Pac\LeanHttp\Uri;
use Pac\LeanHttp\Stream;

// From PHP globals (most common)
$request = ServerRequest::fromGlobals();

// Manually
$request = new ServerRequest(
    'POST',
    new Uri('https://api.example.com/users'),
    Stream::fromMemory('{"name": "John"}'),
    headers: ['Content-Type' => 'application/json'],
    cookieParams: ['session' => 'abc123'],
    queryParams: ['page' => '1']
);
```

#### Creating Responses

[](#creating-responses)

```
use Pac\LeanHttp\Response;
use Pac\LeanHttp\Stream;

// Basic response
$response = new Response(200, 'OK');

// With headers
$response = new Response(
    201,
    'Created',
    ['Location' => '/users/123', 'X-Custom-Header' => 'value']
);

// With body
$body = Stream::fromMemory('{"status": "ok"}');
$response = new Response(200, 'OK', ['Content-Type' => 'application/json'], $body);

// Automatic content type handling
$response = Response::byContentType(
    200,
    ['status' => 'ok'],
    ['Content-Type' => 'application/json']
);
```

#### Working with Headers

[](#working-with-headers)

```
// Get headers
$contentType = $request->getHeaderLine('Content-Type');
$allHeaders = $request->getHeaders();

// Modify headers (returns new instance - immutable)
$newRequest = $request
    ->withHeader('X-API-Key', 'secret123')
    ->withAddedHeader('X-Custom', 'value1')
    ->withAddedHeader('X-Custom', 'value2') // Adds second value
    ->withoutHeader('X-Old-Header');
```

### Body Parsing

[](#body-parsing)

The library automatically parses request/response bodies based on the `Content-Type` header:

```
$request = ServerRequest::fromGlobals();
$parsed = $request->parseBody();

// Content-Type: application/json
// Returns: array or object (decoded JSON)

// Content-Type: application/x-www-form-urlencoded
// Returns: array (parsed form data)

// Content-Type: multipart/form-data
// Returns: array (from $_POST)

// Content-Type: text/csv
// Returns: array of arrays (CSV rows)

// Content-Type: text/xml or application/xml
// Returns: DOMDocument instance

// Content-Type: text/html
// Returns: DOMDocument instance

// Unknown or empty Content-Type
// Returns: array with body as string
```

**Example:**

```
// JSON request body
$request = new ServerRequest(
    'POST',
    new Uri('https://api.example.com/users'),
    Stream::fromMemory('{"name": "John", "age": 30}'),
    headers: ['Content-Type' => 'application/json']
);
$data = $request->parseBody();
// $data = ['name' => 'John', 'age' => 30]

// Form data
$request = new ServerRequest(
    'POST',
    new Uri('https://api.example.com/users'),
    Stream::fromMemory('name=John&age=30'),
    headers: ['Content-Type' => 'application/x-www-form-urlencoded']
);
$data = $request->parseBody();
// $data = ['name' => 'John', 'age' => 30]
```

### URI Handling

[](#uri-handling)

#### URI Normalization

[](#uri-normalization)

URIs are automatically normalized when created:

```
use Pac\LeanHttp\Uri;

$uri = new Uri('http://example.com:80/%7Efoo/./bar/baz/../qux/index.html#fragment');
echo (string) $uri;
// Output: http://example.com/~foo/bar/qux/index.html#fragment

// Normalization includes:
// - Removing default ports (80 for http, 443 for https)
// - Decoding percent-encoded characters where appropriate
// - Removing dot segments (./ and ../)
// - Lowercasing scheme and host
```

#### Query Parameters

[](#query-parameters)

```
$uri = new Uri('https://example.com/search?q=hello&page=2&sort=name');

// Get query string
$query = $uri->getQuery(); // 'q=hello&page=2&sort=name'

// Get as array
$params = $uri->getQueryParams();
// ['q' => 'hello', 'page' => '2', 'sort' => 'name']

// Modify query parameters
$newUri = $uri->withQueryParams([
    'q' => 'world',
    'page' => '1',
    'filter' => 'active'
]);
// Query string is automatically built and URL-encoded
```

#### URI Builder

[](#uri-builder)

Build URIs from components:

```
use Pac\LeanHttp\Uri\UriBuilder;

// Using positional arguments
$uri = (new UriBuilder(
    'https',
    'api.example.com',
    '/users',
    ['page' => 1, 'limit' => 10],
    'section1',
    443,
    'user',
    'pass'
))->build();

// Using named arguments (PHP 8+)
$uri = (new UriBuilder(
    scheme: 'https',
    host: 'api.example.com',
    path: '/users',
    query: ['page' => 1],
    fragment: 'section1',
    user: 'admin',
    password: 'secret'
))->build();
```

#### URI Validation and Normalization Tools

[](#uri-validation-and-normalization-tools)

```
use Pac\LeanHttp\Uri\UriValidator;
use Pac\LeanHttp\Uri\UriNormalizer;

$validator = UriValidator::getDefault();
$normalizer = UriNormalizer::getDefault();

// Validate components
$isValid = $validator->validateQuery('name=John&age=30'); // true
$isValid = $validator->validatePort(8080); // true
$isValid = $validator->validatePort(99999); // false

// Normalize components
$normalized = $normalizer->normalizePath('/foo/../bar/./baz');
// Result: '/bar/baz'

$normalized = $normalizer->normalizeHost('EXAMPLE.COM');
// Result: 'example.com'

$normalized = $normalizer->normalizeQuery('b=2&a=1', sortQuery: true);
// Result: 'a=1&b=2' (sorted)
```

### File Uploads

[](#file-uploads)

#### Handling Uploaded Files

[](#handling-uploaded-files)

```
$request = ServerRequest::fromGlobals();
$files = $request->getUploadedFiles();

// Single file upload
$avatar = $files['avatar'] ?? null;
if ($avatar && $avatar->getError() === UPLOAD_ERR_OK) {
    $filename = $avatar->getClientFilename();
    $size = $avatar->getSize();
    $type = $avatar->getClientMediaType();

    // Move to permanent location
    $avatar->moveTo('/uploads/' . $filename);
}

// Multiple file uploads
$images = $files['images'] ?? [];
foreach ($images as $image) {
    if ($image->getError() === UPLOAD_ERR_OK) {
        $image->moveTo('/uploads/' . $image->getClientFilename());
    }
}

// Nested file structures
$documents = $files['documents']['user']['profile'] ?? null;
if ($documents) {
    $documents->moveTo('/uploads/' . $documents->getClientFilename());
}
```

#### Creating UploadedFile Instances

[](#creating-uploadedfile-instances)

```
use Pac\LeanHttp\UploadedFile;

// From $_FILES array
$file = UploadedFile::fromArray($_FILES['avatar']);

// Manually
$file = new UploadedFile(
    filePath: '/tmp/phpXXXXXX',
    clientFilename: 'photo.jpg',
    clientMediaType: 'image/jpeg',
    size: 102400,
    error: UPLOAD_ERR_OK
);

// Get file information
$filename = $file->getClientFilename();
$size = $file->getSize();
$type = $file->getClientMediaType();
$error = $file->getError();

// Read file content
$stream = $file->getStream();
$content = $stream->getContents();
```

---

Advanced Usage
--------------

[](#advanced-usage)

### Streams

[](#streams)

```
use Pac\LeanHttp\Stream;

// Create from file
$stream = new Stream('/path/to/file.txt', 'r');

// Create from memory
$stream = Stream::fromMemory('Initial content');
$stream->write('More content');

// Create temporary stream
$stream = Stream::fromTemporary();

// Read from PHP input
$input = Stream::fromInput();

// Write to PHP output
$output = Stream::fromOutput();
$output->write('Hello, World!');

// Stream operations
$content = $stream->getContents();
$size = $stream->getSize();
$position = $stream->tell();
$stream->seek(0); // Rewind
$data = $stream->read(1024); // Read 1024 bytes
```

### Custom Status Codes

[](#custom-status-codes)

```
use Pac\LeanHttp\Response;
use Pac\LeanHttp\Status;

// Using Status enum
$response = new Response(Status::OK->value);
$reasonPhrase = Status::OK->getReasonPhrase(); // 'OK'

// Custom status code
$response = new Response(418, "I'm a teapot");

// Modify status
$newResponse = $response->withStatus(404, 'Not Found');
```

### Error Handling

[](#error-handling)

The library uses specialized exception types for better error handling:

```
use Pac\LeanHttp\Exception\ParseException;
use Pac\LeanHttp\Exception\StreamException;
use Pac\LeanHttp\Exception\UploadedFileException;
use Pac\LeanHttp\Exception\HeaderException;

try {
    $data = $request->parseBody();
} catch (ParseException $e) {
    // Handle parsing errors (invalid JSON, XML, etc.)
    error_log("Parse error: " . $e->getMessage());
} catch (StreamException $e) {
    // Handle stream errors (file not found, read errors, etc.)
    error_log("Stream error: " . $e->getMessage());
} catch (UploadedFileException $e) {
    // Handle file upload errors
    error_log("Upload error: " . $e->getMessage());
} catch (HeaderException $e) {
    // Handle header validation errors
    error_log("Header error: " . $e->getMessage());
}
```

---

Contributing
------------

[](#contributing)

Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines on how to contribute to this project.

---

License
-------

[](#license)

This project is open-source under the **MIT License**.

###  Health Score

36

—

LowBetter than 82% of packages

Maintenance80

Actively maintained with recent releases

Popularity1

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity49

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

Unknown

Total

1

Last Release

107d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/88bfa55bf24657c50ea82659e180b32fdf2af812dcf5719484ea4349922ad74f?d=identicon)[pedroac](/maintainers/pedroac)

---

Top Contributors

[![pedroac](https://avatars.githubusercontent.com/u/84463?v=4)](https://github.com/pedroac "pedroac (17 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/pac-lean-http/health.svg)

```
[![Health](https://phpackages.com/badges/pac-lean-http/health.svg)](https://phpackages.com/packages/pac-lean-http)
```

###  Alternatives

[league/uri-interfaces

Common tools for parsing and resolving RFC3987/RFC3986 URI

536204.9M23](/packages/league-uri-interfaces)[shopify/shopify-api

Shopify API Library for PHP

4634.8M16](/packages/shopify-shopify-api)[laudis/neo4j-php-client

Neo4j-PHP-Client is the most advanced PHP Client for Neo4j

184616.9k31](/packages/laudis-neo4j-php-client)[http-interop/response-sender

A function to convert PSR-7 Response to HTTP output

46711.5k40](/packages/http-interop-response-sender)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28137.8k](/packages/phpro-http-tools)[mezzio/mezzio-authentication-oauth2

OAuth2 (server) authentication middleware for Mezzio and PSR-7 applications.

28483.0k2](/packages/mezzio-mezzio-authentication-oauth2)

PHPackages © 2026

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