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

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

pagemill/http
=============

A PHP library for helping with HTTP requests and responses

3.0.0(2mo ago)0311BSD-3-ClausePHPPHP ^8.2CI passing

Since Mar 1Pushed 2mo agoCompare

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

READMEChangelog (1)Dependencies (2)Versions (2)Used By (1)

PageMill HTTP
=============

[](#pagemill-http)

A lightweight, focused PHP library for handling HTTP requests and responses. Clean API, no bloat, gets out of your way.

![Tests](https://camo.githubusercontent.com/8918ecef76339ca73359a4d39d7f122631e18679ee3f290cd5988199b6f95850/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d31323525323070617373696e672d627269676874677265656e)![Coverage](https://camo.githubusercontent.com/74e1dbbc33b300666b12b88e7a842206a1dd2ddbe697f506d5dd74bbefe351b7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f7665726167652d38332e34362532352d627269676874677265656e)![PHP Version](https://camo.githubusercontent.com/4f0ff8d47b7c73441eb92a1f49af61c2d6521b14113c8fd85fac4416c863e7cc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e322d626c7565)

Why PageMill HTTP?
------------------

[](#why-pagemill-http)

**Clean API**: Simple, class-based interface for HTTP operations without framework overhead.

**Production Ready**: 125 tests (unit + functional), 83% coverage, battle-tested in production.

**Focused**: Does HTTP headers, status codes, and responses well. Nothing else.

**Modern PHP**: Type hints, PHPDoc everywhere, follows PSR standards (with DealNews conventions).

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

[](#installation)

```
composer require pagemill/http
```

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

[](#quick-start)

```
use PageMill\HTTP\Response;
use PageMill\HTTP\Request;
use PageMill\HTTP\HTTP;

// Parse incoming request
$request = new Request();
$auth_header = $request->header('Authorization');

// Build response
$response = Response::init();
$response->status(HTTP::STATUS_OK);
$response->contentType(HTTP::CONTENT_TYPE_JSON);
$response->cache(3600); // Cache for 1 hour

// Output
echo json_encode(['message' => 'Hello, World!']);
```

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

[](#table-of-contents)

- [Use Cases](#use-cases)
- [Request Handling](#request-handling)
- [Response Building](#response-building)
- [Status Codes](#status-codes)
- [Redirects](#redirects)
- [Cache Control](#cache-control)
- [CORS](#cors-cross-origin-resource-sharing)
- [Error Handling](#error-handling)
- [Custom Headers](#custom-headers)
- [Content-Type](#content-type-negotiation)
- [API Reference](#api-reference)
- [Testing](#testing)

Use Cases
---------

[](#use-cases)

### REST API Endpoint

[](#rest-api-endpoint)

```
use PageMill\HTTP\Response;
use PageMill\HTTP\Request;
use PageMill\HTTP\HTTP;

$request = new Request();
$response = Response::init();

// Check authentication
$api_key = $request->header('X-API-Key');
if (!$api_key || !isValidApiKey($api_key)) {
    $response->error(HTTP::STATUS_UNAUTHORIZED, 'Invalid API key');
    exit;
}

// Set response headers
$response->contentType(HTTP::CONTENT_TYPE_JSON);
$response->cache(300); // 5 minutes
$response->headers->set('X-API-Version', '1.0');

// Return data
echo json_encode([
    'users' => getUserList(),
    'count' => getUserCount(),
]);
```

### CORS-Enabled API

[](#cors-enabled-api)

```
$response = Response::init();

// Configure allowed origins
$allowed_origins = ['app.example.com', 'admin.example.com'];

// Check and set CORS headers
if (!$response->accessAllowed($allowed_origins)) {
    $response->error(HTTP::STATUS_FORBIDDEN);
    exit;
}
$response->accessControl($allowed_origins);

// Handle preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    $response->status(HTTP::STATUS_NO_CONTENT);
    exit;
}
```

Request Handling
----------------

[](#request-handling)

Parse incoming HTTP headers from `$_SERVER` or Apache.

```
use PageMill\HTTP\Request;

$request = new Request();

// Get all headers (normalized)
$all_headers = $request->headers();
// ['Content-Type' => 'application/json', 'Authorization' => 'Bearer ...']

// Get specific header (case-insensitive)
$content_type = $request->header('Content-Type');
$content_type = $request->header('content-type'); // Same result
```

Headers are normalized: `HTTP_CONTENT_TYPE` → `Content-Type`

Response Building
-----------------

[](#response-building)

```
use PageMill\HTTP\Response;
use PageMill\HTTP\HTTP;

// Get singleton instance
$response = Response::init();

// Set status code
$response->status(HTTP::STATUS_OK);                    // 200
$response->status(HTTP::STATUS_NOT_FOUND);             // 404
$response->status(HTTP::STATUS_INTERNAL_SERVER_ERROR); // 500

// Set content-type
$response->contentType(HTTP::CONTENT_TYPE_JSON);
$response->contentType(HTTP::CONTENT_TYPE_HTML, 'utf-8');
```

Status Codes
------------

[](#status-codes)

All standard HTTP status codes available as constants.

```
// Success 2xx
HTTP::STATUS_OK                    // 200
HTTP::STATUS_CREATED               // 201
HTTP::STATUS_NO_CONTENT            // 204

// Redirection 3xx
HTTP::STATUS_MOVED_PERMANENTLY     // 301
HTTP::STATUS_FOUND                 // 302
HTTP::STATUS_TEMPORARY_REDIRECT    // 307

// Client Error 4xx
HTTP::STATUS_BAD_REQUEST           // 400
HTTP::STATUS_UNAUTHORIZED          // 401
HTTP::STATUS_FORBIDDEN             // 403
HTTP::STATUS_NOT_FOUND             // 404
HTTP::STATUS_TOO_MANY_REQUESTS     // 429

// Server Error 5xx
HTTP::STATUS_INTERNAL_SERVER_ERROR // 500
HTTP::STATUS_SERVICE_UNAVAILABLE   // 503
```

Get descriptions:

```
$description = HTTP::$status_codes[404]; // "Not Found"
```

Redirects
---------

[](#redirects)

```
// 302 temporary redirect (default)
$response->redirect('https://example.com');

// 301 permanent redirect
$response->redirect(
    'https://example.com',
    HTTP::STATUS_MOVED_PERMANENTLY
);
```

Note: `redirect()` calls `exit()` after setting headers.

Cache Control
-------------

[](#cache-control)

```
// Cache for 1 hour
$response->cache(3600);

// Sends:
// Cache-Control: max-age=3600
// Expires:
// Last-Modified:

// With custom last-modified
$file_time = filemtime('/path/to/file');
$response->cache(3600, $file_time);

// Disable caching
$response->cache(0);
// or
$response->cache(HTTP::NOCACHE);
```

CORS (Cross-Origin Resource Sharing)
------------------------------------

[](#cors-cross-origin-resource-sharing)

```
$allowed_origins = ['app.example.com', 'admin.example.com'];

// Check if origin is allowed
if ($response->accessAllowed($allowed_origins)) {
    // Set CORS headers
    $response->accessControl($allowed_origins);
}

// Handle preflight OPTIONS
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    $response->status(HTTP::STATUS_NO_CONTENT);
    $response->headers->set('Access-Control-Allow-Methods', 'GET, POST, DELETE');
    $response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    exit;
}
```

Subdomain matching: `'example.com'` matches `www.example.com`, `api.example.com`, etc.

Error Handling
--------------

[](#error-handling)

```
// Basic error
$response->error(HTTP::STATUS_NOT_FOUND);

// With message
$response->error(HTTP::STATUS_FORBIDDEN, 'Access denied');

// With template path
$response->error(
    HTTP::STATUS_INTERNAL_SERVER_ERROR,
    'Database error',
    '/path/to/templates'
);
```

**Templates** (place in template directory):

- `404.html`, `500.html` - HTML error pages
- `error.json` - JSON error format
- `error.xml` - XML error format

**Variables**:

- `{{STATUS}}` - HTTP status code
- `{{MESSAGE}}` - Error message

Example `404.html`:

```
>

{{STATUS}} - {{MESSAGE}}

    {{STATUS}}
    {{MESSAGE}}

```

Custom Headers
--------------

[](#custom-headers)

```
$response = Response::init();
$headers = $response->headers;

// Set header (replaces existing)
$headers->set('X-API-Version', '1.0');

// Add header (allows multiple values)
$headers->add('X-Tag', 'production');
$headers->add('X-Tag', 'api');

// Remove header
$headers->remove('X-Debug');

// Replace part of value
$headers->set('X-Server', 'web-old-001');
$headers->replace('X-Server', 'old', 'new');
// Results in: X-Server: web-new-001

// Get all queued headers
$all = $headers->getHeaders();
```

Content-Type Negotiation
------------------------

[](#content-type-negotiation)

```
// Predefined types
$response->contentType(HTTP::CONTENT_TYPE_JSON);  // application/json
$response->contentType(HTTP::CONTENT_TYPE_HTML);  // text/html
$response->contentType(HTTP::CONTENT_TYPE_XML);   // text/xml
$response->contentType(HTTP::CONTENT_TYPE_CSS);   // text/css
$response->contentType(HTTP::CONTENT_TYPE_CSV);   // text/csv

// With charset
$response->contentType(HTTP::CONTENT_TYPE_HTML, 'utf-8');

// Custom type
$response->contentType('application/pdf');
```

**Available constants**:

- `CONTENT_TYPE_HTML`, `CONTENT_TYPE_JSON`, `CONTENT_TYPE_XML`
- `CONTENT_TYPE_CSS`, `CONTENT_TYPE_CSV`, `CONTENT_TYPE_PLAIN`
- `CONTENT_TYPE_IMAGE_GIF`, `CONTENT_TYPE_IMAGE_PNG`, `CONTENT_TYPE_IMAGE_JPEG`

API Reference
-------------

[](#api-reference)

### Request

[](#request)

```
class Request {
    public function __construct(array $server = null);
    public function headers(): array;
    public function header(string $name): ?string;
}
```

### Response

[](#response)

```
class Response {
    public static function init(): Response;
    public static function resetInstance(): void;

    public function error(int $status, ?string $message = null, ?string $path = null): void;
    public function status(int $status): void;
    public function redirect(string $to, int $status = HTTP::STATUS_FOUND): void;
    public function contentType(string $type, ?string $charset = null): void;
    public function cache(int $ttl, int|string|null $last_modified = null): void;
    public function accessControl(array $allowed_origins): void;
    public function accessAllowed(array $allowed_origins): bool;
}
```

### Headers

[](#headers)

```
class Headers {
    public function set(string $name, string $value): void;
    public function add(string $name, string $value): void;
    public function replace(string $name, string $search, string $value): void;
    public function remove(string $name): void;
    public function removeAll(): void;
    public function getHeaders(): array;
}
```

Testing
-------

[](#testing)

```
# Run all tests
./vendor/bin/phpunit

# With coverage
./vendor/bin/phpunit --coverage-text

# Unit tests only
./vendor/bin/phpunit tests/Unit

# Functional tests only
./vendor/bin/phpunit tests/Functional
```

**Coverage**: 83.46% (227/272 lines)

- Request: 97.37%
- Headers: 95.12%
- Callback: 100%
- Singleton: 100%
- Response: 76.40%

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

[](#requirements)

- PHP 8.2 or higher
- pagemill/accept ^3.0

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

[](#contributing)

1. Fork the repository
2. Create a feature branch
3. Write tests for your changes
4. Ensure all tests pass
5. Submit a pull request

See `AGENTS.md` for coding guidelines.

License
-------

[](#license)

BSD-3-Clause

---

**Questions?** Check the test suite - it demonstrates all features with 125 tests.

###  Health Score

40

—

FairBetter than 88% of packages

Maintenance86

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity46

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

70d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/49531?v=4)[Brian Moon](/maintainers/brianlmoon)[@brianlmoon](https://github.com/brianlmoon)

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

---

Top Contributors

[![brianlmoon](https://avatars.githubusercontent.com/u/49531?v=4)](https://github.com/brianlmoon "brianlmoon (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

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

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

###  Alternatives

[friendsofsymfony/rest-bundle

This Bundle provides various tools to rapidly develop RESTful API's with Symfony

2.8k73.3M319](/packages/friendsofsymfony-rest-bundle)[php-http/discovery

Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations

1.3k309.5M1.2k](/packages/php-http-discovery)[nyholm/psr7

A fast PHP7 implementation of PSR-7

1.3k235.4M2.4k](/packages/nyholm-psr7)[pusher/pusher-php-server

Library for interacting with the Pusher REST API

1.5k94.8M291](/packages/pusher-pusher-php-server)[spatie/crawler

Crawl all internal links found on a website

2.8k16.3M52](/packages/spatie-crawler)[react/http

Event-driven, streaming HTTP client and server implementation for ReactPHP

78126.4M414](/packages/react-http)

PHPackages © 2026

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