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

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

callismart/http
===============

A lightweight, multi-adapter HTTP client for PHP 8.1+ with AWS SigV4 support and fallbacks for restricted environments.

v1.0.0(4w ago)02MITPHPPHP &gt;=8.1

Since May 12Pushed 4w agoCompare

[ Source](https://github.com/CallismartLtd/Http)[ Packagist](https://packagist.org/packages/callismart/http)[ RSS](/packages/callismart-http/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependenciesVersions (2)Used By (0)

Callismart HTTP Client
======================

[](#callismart-http-client)

A lightweight, multi-adapter HTTP client for PHP 8.1+ designed for maximum reliability across diverse hosting environments. It provides a clean, fluent API with native support for AWS Signature V4 and automatic adapter fallbacks.

Why Callismart HTTP?
--------------------

[](#why-callismart-http)

Most modern PHP HTTP clients rely heavily on the cURL extension. However, in many shared hosting environments or restricted server setups, cURL may be disabled or outdated.

Callismart HTTP solves this by providing:

- **Adapter Fallbacks**: If `ext-curl` isn't available, the client automatically fails over to `fopen` or `socket` communication.
- **Zero Hard Dependencies**: Keep your project footprint small—ideal for WordPress plugins and CLI tools.
- **AWS SigV4**: Native support for signing requests to AWS services (SES, S3, Lambda, etc.) without the weight of the full AWS SDK.
- **Immutable Value Objects**: Predictable request and response objects that are thread-safe and cacheable.
- **Performance Tracking**: Built-in latency measuring for every request.
- **Smart Streaming**: Download large files directly to disk without buffering in memory.
- **Cookie Management**: Automatic parsing and replay of session cookies across requests.

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

[](#installation)

Install the package via Composer:

```
composer require callismart/http
```

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

[](#quick-start)

### Basic GET Request

[](#basic-get-request)

```
use Callismart\Http\HttpClient;

$client   = new HttpClient();
$response = $client->get( 'https://api.example.com/v1/resource' );

if ( $response->ok() ) {
    $data = $response->json();
    print_r( $data );
}
```

### POST Request with JSON

[](#post-request-with-json)

```
$response = $client->post_json( 'https://api.example.com/v1/users', [
    'name'  => 'Callistus',
    'role'  => 'Developer',
    'email' => 'callistus@example.com',
] );

if ( $response->is_success() ) {
    $user = $response->json();
    echo "Created user ID: {$user['id']}";
}
```

### Download a File

[](#download-a-file)

Stream a large file directly to disk without holding it in memory:

```
$response = $client->download(
    'https://example.com/releases/plugin-2.1.0.zip',
    '/var/www/downloads/plugin-2.1.0.zip'
);

if ( $response->is_success() ) {
    echo "Saved {$response->file_size} bytes to {$response->sink_path}";
}
```

### AWS Service Request (SES)

[](#aws-service-request-ses)

Sign and send a request to AWS:

```
use Callismart\Http\HttpRequest;
use Callismart\Http\AwsSignatureV4;

$request = HttpRequest::post( 'https://email.us-east-1.amazonaws.com/', $body )
    ->with_header( 'Content-Type', 'application/x-www-form-urlencoded' );

$signer   = new AwsSignatureV4( $access_key, $secret_key, 'us-east-1', 'ses' );
$signed   = $signer->sign( $request );
$response = $client->send( $signed );
```

Client Initialization
---------------------

[](#client-initialization)

### Auto-Detection (Recommended)

[](#auto-detection-recommended)

The client automatically detects the best available adapter in this priority order:

1. **cURL** (`ext-curl`) — Highest performance, full feature support
2. **fopen** (`allow_url_fopen = On`) — Fallback for shared hosting
3. **Socket** (`fsockopen`) — Last resort, always available

```
$client = new HttpClient();
// Automatically uses the first available adapter
```

### Explicit Adapter Selection

[](#explicit-adapter-selection)

If you want to force a specific adapter:

```
use Callismart\Http\Adapters\CurlAdapter;
use Callismart\Http\Adapters\FopenAdapter;
use Callismart\Http\Adapters\SocketAdapter;

// Force cURL
$client = new HttpClient( new CurlAdapter() );

// Force fopen
$client = new HttpClient( new FopenAdapter() );

// Force socket (always available)
$client = new HttpClient( new SocketAdapter() );
```

### Check Current Adapter

[](#check-current-adapter)

```
$adapter = $client->get_adapter();
echo "Using adapter: " . $adapter->get_id(); // "curl", "fopen", or "socket"
```

### Set Default Headers

[](#set-default-headers)

Persistent headers applied to every request (useful for authentication):

```
$client->with_default_header( 'Authorization', "Bearer {$token}" )
       ->with_default_header( 'User-Agent', 'MyApp/1.0' );

// These headers are merged into every request sent via this client
$response = $client->get( 'https://api.example.com/user' );
// Automatically includes the Authorization and User-Agent headers
```

Per-request headers always override client defaults.

Building Requests
-----------------

[](#building-requests)

Requests are immutable value objects built with a fluent API.

### Named Constructors

[](#named-constructors)

```
use Callismart\Http\HttpRequest;

// GET
$request = HttpRequest::get( 'https://api.example.com/posts' );

// POST
$request = HttpRequest::post( 'https://api.example.com/posts', $body );

// PUT
$request = HttpRequest::put( 'https://api.example.com/posts/123', $body );

// PATCH
$request = HttpRequest::patch( 'https://api.example.com/posts/123', $body );

// DELETE
$request = HttpRequest::delete( 'https://api.example.com/posts/123' );
```

### Fluent Withers

[](#fluent-withers)

All withers return a new immutable request copy:

#### Add a Header

[](#add-a-header)

```
$request = HttpRequest::get( $url )
    ->with_header( 'Authorization', "Bearer {$token}" )
    ->with_header( 'X-Custom-Header', 'value' );
```

#### JSON Body &amp; Headers

[](#json-body--headers)

Automatically sets `Content-Type` and `Accept` to `application/json`:

```
$request = HttpRequest::post( $url )
    ->with_json( [
        'name'  => 'Alice',
        'email' => 'alice@example.com',
    ] );

// Equivalent to:
// $request = HttpRequest::post( $url, json_encode( [...] ) )
//     ->with_header( 'Content-Type', 'application/json' )
//     ->with_header( 'Accept', 'application/json' );
```

#### Add a Cookie

[](#add-a-cookie)

```
$request = HttpRequest::get( $url )
    ->with_cookie( 'session_id', 'abc123' )
    ->with_cookie( 'user_pref', 'dark_mode' );

// Cookies are automatically formatted into the Cookie header
```

#### Stream Response to File

[](#stream-response-to-file)

Downloads the response body directly to disk without buffering:

```
$request = HttpRequest::get( 'https://example.com/large-file.zip' )
    ->with_sink( '/tmp/large-file.zip' );

$response = $client->send( $request );

if ( $response->is_success() ) {
    echo "File saved to: {$response->sink_path}";
    echo "File size: {$response->file_size} bytes";
}
```

The destination directory must exist and be writable. Throws `InvalidArgumentException` otherwise.

#### Disable SSL Verification

[](#disable-ssl-verification)

Use only in development or testing:

```
$request = HttpRequest::get( 'https://self-signed.example.com' )
    ->without_ssl_verification();
```

#### Apply Multiple Options

[](#apply-multiple-options)

```
$request = HttpRequest::get( $url )->with_options( [
    'timeout'       => 60,           // seconds
    'verify_ssl'    => false,        // boolean
    'max_redirects' => 10,           // integer
    'cookies'       => [             // array of name => value
        'session_id' => 'xyz789',
    ],
] );
```

Sending Requests
----------------

[](#sending-requests)

### Via HttpClient

[](#via-httpclient)

```
$response = $client->send( $request );
```

### Convenience Methods

[](#convenience-methods)

HttpClient provides shorthand methods for common patterns:

```
// GET
$response = $client->get( 'https://api.example.com/users' );

// POST with body
$response = $client->post( 'https://api.example.com/users', $json_body );

// POST with JSON (automatic encoding + headers)
$response = $client->post_json( 'https://api.example.com/users', [
    'name' => 'Bob',
] );

// PUT
$response = $client->put( 'https://api.example.com/users/123', $body );

// PATCH
$response = $client->patch( 'https://api.example.com/users/123', $body );

// DELETE
$response = $client->delete( 'https://api.example.com/users/123' );

// Download (with automatic streaming)
$response = $client->download( $url, '/path/to/file' );
```

Handling Responses
------------------

[](#handling-responses)

Responses are immutable value objects with helper methods for common patterns.

### Status Checks

[](#status-checks)

```
$response = $client->get( $url );

// Shorthand for is_success()
if ( $response->ok() ) {
    // 2xx status
}

// Detailed checks
if ( $response->is_success() ) {
    // 200-299
}

if ( $response->is_client_error() ) {
    // 400-499
}

if ( $response->is_server_error() ) {
    // 500-599
}

if ( $response->is_error() ) {
    // 400-599
}

if ( $response->is_redirect() ) {
    // 300-399
}
```

### Body Parsing

[](#body-parsing)

```
// JSON decoding
$data = $response->json(); // returns decoded array (or null if invalid)

// Check if body is valid JSON
if ( $response->is_json() ) {
    $data = $response->json();
}

// Raw body as string
$raw = $response->body;
```

### Headers

[](#headers)

```
// Get a single header (case-insensitive)
$content_type = $response->get_header( 'content-type' );

// Check if header exists
if ( $response->has_header( 'content-length' ) ) {
    // ...
}

// Shorthand for Content-Type
$type = $response->content_type(); // e.g. "application/json; charset=utf-8"

// All headers as associative array
$headers = $response->headers; // keys are lowercase
```

### Cookies

[](#cookies)

```
// Get a single cookie
$session_id = $response->get_cookie( 'session_id' );

// Check if cookie exists
if ( $response->has_cookie( 'session_id' ) ) {
    // ...
}

// All cookies as associative array
$cookies = $response->cookies;
```

### Redirect Tracking

[](#redirect-tracking)

```
// Check if any redirects were followed
if ( $response->was_redirected() ) {
    echo "Original URL: {$response->original_url()}";
    echo "Final URL: {$response->final_url()}";
}

// Complete redirect history (ordered list of URLs)
foreach ( $response->redirect_history as $url ) {
    echo "Followed: {$url}\n";
}
```

### Download Responses

[](#download-responses)

```
$response = $client->download( $url, '/tmp/file.zip' );

// Check if response was streamed to file
if ( $response->is_download() ) {
    echo "File: {$response->sink_path}";
    echo "Size: {$response->file_size} bytes";
    echo "Body is empty: " . ( $response->body === '' ? 'yes' : 'no' );
}
```

For buffered responses, save to file later:

```
$response = $client->get( $url ); // buffered in memory

if ( $response->is_success() ) {
    $response->save_to( '/tmp/file.zip' ); // write to disk
}
```

### Performance Metrics

[](#performance-metrics)

```
$response = $client->get( $url );

echo "Request took {$response->latency} seconds";
```

HTTP Methods with Examples
--------------------------

[](#http-methods-with-examples)

### GET

[](#get)

Retrieve data from a server:

```
$response = $client->get( 'https://api.example.com/posts/42' );

if ( $response->is_success() ) {
    $post = $response->json();
    echo "Title: {$post['title']}";
}
```

### POST

[](#post)

Submit data and create resources:

```
$response = $client->post_json( 'https://api.example.com/posts', [
    'title'   => 'Hello World',
    'content' => 'This is my first post.',
    'author'  => 'Alice',
] );

if ( $response->is_success() ) {
    $created = $response->json();
    echo "Post created with ID: {$created['id']}";
}
```

Custom headers:

```
$request = HttpRequest::post( $url, $body )
    ->with_header( 'Content-Type', 'application/x-www-form-urlencoded' )
    ->with_header( 'X-API-Key', 'secret' );

$response = $client->send( $request );
```

### PUT

[](#put)

Replace an entire resource:

```
$response = $client->put_json( 'https://api.example.com/posts/42', [
    'title'   => 'Updated Title',
    'content' => 'Updated content.',
    'author'  => 'Alice',
] );
```

### PATCH

[](#patch)

Partially update a resource:

```
$response = $client->patch_json( 'https://api.example.com/posts/42', [
    'title' => 'New Title Only',
] );
```

### DELETE

[](#delete)

Remove a resource:

```
$response = $client->delete( 'https://api.example.com/posts/42' );

if ( $response->is_success() ) {
    echo "Post deleted";
}
```

JSON Support
------------

[](#json-support)

### Automatic Encoding

[](#automatic-encoding)

The `with_json()` method automatically encodes your data and sets the correct headers:

```
$request = HttpRequest::post( $url )
    ->with_json( [ 'key' => 'value' ] );

// Headers set automatically:
// Content-Type: application/json
// Accept: application/json
```

### Automatic Decoding

[](#automatic-decoding)

The `json()` method safely decodes JSON responses:

```
$response = $client->post_json( $url, $data );

// Returns decoded array (or null if invalid/empty)
$result = $response->json();

// Check validity first
if ( $response->is_json() ) {
    $result = $response->json();
} else {
    echo "Response is not valid JSON";
}
```

File Downloads
--------------

[](#file-downloads)

### Stream to Disk (Recommended for Large Files)

[](#stream-to-disk-recommended-for-large-files)

Stream the response body directly to a file without buffering in memory:

```
$response = $client->download(
    'https://example.com/releases/large-file-100mb.zip',
    '/var/downloads/large-file.zip'
);

if ( $response->is_success() ) {
    echo "Downloaded {$response->file_size} bytes";
} else {
    echo "Download failed with status {$response->status_code}";
}
```

The file is created at download time. On error, partially downloaded files are automatically cleaned up.

### Buffer in Memory (Small Files)

[](#buffer-in-memory-small-files)

For small responses, buffer in memory and save later:

```
$response = $client->get( 'https://example.com/image.png' );

if ( $response->is_success() ) {
    $response->save_to( '/var/downloads/image.png' );
}
```

### Request-Level Sink

[](#request-level-sink)

Manual sink configuration via request:

```
$request = HttpRequest::get( $url )->with_sink( '/tmp/download.zip' );
$response = $client->send( $request );

if ( $response->is_download() ) {
    echo "Saved to {$response->sink_path}";
}
```

AWS Signature Version 4
-----------------------

[](#aws-signature-version-4)

Sign requests to AWS services without the full AWS SDK:

### Setup

[](#setup)

```
use Callismart\Http\AwsSignatureV4;

$signer = new AwsSignatureV4(
    access_key: 'AKIAIOSFODNN7EXAMPLE',
    secret_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
    region:     'us-east-1',
    service:    'ses', // or 's3', 'lambda', etc.
);
```

### Sign a Request

[](#sign-a-request)

The `sign()` method returns a new request with AWS signing headers applied:

```
$request = HttpRequest::post( 'https://email.us-east-1.amazonaws.com/', $body )
    ->with_header( 'Content-Type', 'application/x-www-form-urlencoded' );

$signed = $signer->sign( $request );

// Headers automatically added:
// - X-Amz-Date: 20250512T123456Z
// - X-Amz-Content-Sha256: (SHA256 hash of body)
// - Authorization: AWS4-HMAC-SHA256 Credential=..., SignedHeaders=..., Signature=...

$response = $client->send( $signed );
```

### Headers Added by the Signer

[](#headers-added-by-the-signer)

The signer adds or overwrites three headers:

- **X-Amz-Date**: Request timestamp in `YYYYMMDDTHHmmssZ` format (UTC)
- **X-Amz-Content-Sha256**: SHA256 hash of the request body (hex-encoded)
- **Authorization**: AWS4-HMAC-SHA256 signature string containing credentials, signed headers, and signature

All other headers (including custom ones) are preserved and included in the signature.

### Example: AWS SES

[](#example-aws-ses)

Send an email via AWS SES:

```
$body = http_build_query( [
    'Action'                   => 'SendEmail',
    'Source'                   => 'noreply@example.com',
    'Destination.ToAddresses.1' => 'user@example.com',
    'Message.Subject.Data'      => 'Hello!',
    'Message.Body.Text.Data'    => 'This is a test email.',
] );

$request = HttpRequest::post( 'https://email.us-east-1.amazonaws.com/', $body )
    ->with_header( 'Content-Type', 'application/x-www-form-urlencoded' );

$signer   = new AwsSignatureV4( $key, $secret, 'us-east-1', 'ses' );
$signed   = $signer->sign( $request );
$response = $client->send( $signed );

if ( $response->is_success() ) {
    $xml = $response->body;
    // Parse XML response...
}
```

### Example: AWS S3

[](#example-aws-s3)

Sign a request to S3 (Get Object):

```
$request = HttpRequest::get( 'https://my-bucket.s3.amazonaws.com/config.json' );

$signer   = new AwsSignatureV4( $key, $secret, 'us-east-1', 's3' );
$signed   = $signer->sign( $request );
$response = $client->send( $signed );

if ( $response->is_success() ) {
    $config = $response->json();
}
```

Adapters
--------

[](#adapters)

The HTTP client uses a three-tier adapter system for maximum compatibility:

### cURL Adapter

[](#curl-adapter)

**Availability**: When `ext-curl` is loaded **Features**: Full support — redirects, SSL verification, cookies, timeouts **Performance**: Highest — uses native C implementation

```
use Callismart\Http\Adapters\CurlAdapter;

$adapter = new CurlAdapter();
if ( $adapter->is_available() ) {
    echo "cURL is available";
}
```

### fopen Adapter

[](#fopen-adapter)

**Availability**: When `allow_url_fopen = On` in php.ini **Features**: Redirects, SSL verification, cookies, timeouts **Limitation**: May not work with restrictive `open_basedir` settings

```
use Callismart\Http\Adapters\FopenAdapter;

$adapter = new FopenAdapter();
if ( $adapter->is_available() ) {
    echo "fopen streaming is available";
}
```

### Socket Adapter

[](#socket-adapter)

**Availability**: Always (when `fsockopen` function exists) **Features**: Redirects (manual), SSL via `ssl://` wrapper, cookies **Limitation**: No proxy support, manual socket handling

```
use Callismart\Http\Adapters\SocketAdapter;

$adapter = new SocketAdapter();
if ( $adapter->is_available() ) {
    echo "Socket fallback is available";
}
```

### Automatic Selection

[](#automatic-selection)

The HttpClient tests adapters in priority order and uses the first available:

```
$client = new HttpClient();
// Automatically selects cURL → fopen → socket

// If none are available, throws HttpRequestException:
// "HttpClient: no HTTP adapter is available in this environment.
//  Enable cURL, allow_url_fopen, or fsockopen."
```

### Manual Selection

[](#manual-selection)

Force a specific adapter:

```
$client = new HttpClient( new SocketAdapter() );
// Uses socket even if cURL is available
```

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

[](#error-handling)

### Exception Hierarchy

[](#exception-hierarchy)

```
RuntimeException
  └─ HttpRequestException
      └─ HttpTimeoutException

```

### Catch Exceptions

[](#catch-exceptions)

```
use Callismart\Http\Exceptions\HttpRequestException;
use Callismart\Http\Exceptions\HttpTimeoutException;

try {
    $response = $client->get( $url );
} catch ( HttpTimeoutException $e ) {
    // Request exceeded timeout
    echo "Timeout: {$e->getMessage()}";
} catch ( HttpRequestException $e ) {
    // Connection failed, DNS error, socket error, etc.
    echo "Error: {$e->getMessage()}";
}
```

### Common Errors

[](#common-errors)

ErrorCause`HttpTimeoutException`Request exceeded configured timeout`HttpRequestException` (DNS)Domain could not be resolved`HttpRequestException` (connection)TCP connection refused or timeout`HttpRequestException` (SSL)SSL certificate verification failed (when `verify_ssl=true`)`InvalidArgumentException`Invalid URL, unsupported HTTP method, missing sink directoryConfiguration Options
---------------------

[](#configuration-options)

### Request Options

[](#request-options)

```
$request = HttpRequest::get( $url )->with_options( [
    'timeout'       => 30,       // seconds (default: 30)
    'verify_ssl'    => true,     // boolean (default: true)
    'max_redirects' => 5,        // integer (default: 5)
    'cookies'       => [],       // array of name => value
    'sink'          => null,     // absolute path for streaming
] );
```

### Client Configuration

[](#client-configuration)

```
$client = new HttpClient();

// Set default headers
$client->with_default_header( 'Authorization', "Bearer {$token}" );
$client->with_default_header( 'User-Agent', 'MyApp/1.0' );

// Check current adapter
$adapter_id = $client->get_adapter()->get_id(); // "curl", "fopen", or "socket"
```

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

[](#requirements)

- **PHP**: 8.1 or higher
- **ext-openssl**: Recommended for HTTPS support
- **At least one of**:
    - `ext-curl` (preferred)
    - `allow_url_fopen = On` in php.ini
    - `fsockopen()` function enabled

Performance Tips
----------------

[](#performance-tips)

1. **Use Streaming for Large Files**: Use `with_sink()` or `download()` to avoid buffering entire files in memory.
2. **Reuse HttpClient Instances**: Create once, send many requests through it to benefit from connection pooling (cURL).
3. **Set Appropriate Timeouts**: Balance between catching truly broken connections and allowing slow networks:

    ```
    $request = HttpRequest::get( $url )->with_options( [ 'timeout' => 60 ] );
    ```
4. **Disable SSL Verification Only in Development**: Use `without_ssl_verification()` only in trusted environments.
5. **Check Redirect Loops**: Monitor `redirect_history` to detect infinite redirects:

    ```
    if ( count( $response->redirect_history ) > 10 ) {
        // Possible redirect loop
    }
    ```

License
-------

[](#license)

This project is licensed under the MIT License. See LICENSE file for details.

Author
------

[](#author)

Callistus Nwachukwu

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

[](#contributing)

Contributions are welcome. Please ensure all code follows WordPress PHP Coding Standards (K&amp;R braces, spaces inside parentheses, tab indentation).

Support
-------

[](#support)

For issues, feature requests, or questions, please visit the project repository.

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance94

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

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

28d ago

### Community

Maintainers

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

---

Top Contributors

[![CallismartLtd](https://avatars.githubusercontent.com/u/142177518?v=4)](https://github.com/CallismartLtd "CallismartLtd (5 commits)")

---

Tags

httpclientawscurlSocketsigv4

### Embed Badge

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

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

###  Alternatives

[swlib/saber

Swoole coroutine HTTP client

976148.4k28](/packages/swlib-saber)[aplus/http-client

Aplus Framework HTTP Client Library

2171.6M1](/packages/aplus-http-client)

PHPackages © 2026

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