callismart/http
最新稳定版本:v1.0.0
Composer 安装命令:
composer require callismart/http
包简介
A lightweight, multi-adapter HTTP client for PHP 8.1+ with AWS SigV4 support and fallbacks for restricted environments.
README 文档
README
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?
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-curlisn't available, the client automatically fails over tofopenorsocketcommunication. - 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
Install the package via Composer:
composer require callismart/http
Quick Start
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
$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
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)
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
Auto-Detection (Recommended)
The client automatically detects the best available adapter in this priority order:
- cURL (
ext-curl) — Highest performance, full feature support - fopen (
allow_url_fopen = On) — Fallback for shared hosting - Socket (
fsockopen) — Last resort, always available
$client = new HttpClient(); // Automatically uses the first available adapter
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
$adapter = $client->get_adapter(); echo "Using adapter: " . $adapter->get_id(); // "curl", "fopen", or "socket"
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
Requests are immutable value objects built with a fluent API.
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
All withers return a new immutable request copy:
Add a Header
$request = HttpRequest::get( $url ) ->with_header( 'Authorization', "Bearer {$token}" ) ->with_header( 'X-Custom-Header', 'value' );
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
$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
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
Use only in development or testing:
$request = HttpRequest::get( 'https://self-signed.example.com' ) ->without_ssl_verification();
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
Via HttpClient
$response = $client->send( $request );
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
Responses are immutable value objects with helper methods for common patterns.
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
// 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
// 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
// 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
// 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
$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
$response = $client->get( $url ); echo "Request took {$response->latency} seconds";
HTTP Methods with Examples
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
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
Replace an entire resource:
$response = $client->put_json( 'https://api.example.com/posts/42', [ 'title' => 'Updated Title', 'content' => 'Updated content.', 'author' => 'Alice', ] );
PATCH
Partially update a resource:
$response = $client->patch_json( 'https://api.example.com/posts/42', [ 'title' => 'New Title Only', ] );
DELETE
Remove a resource:
$response = $client->delete( 'https://api.example.com/posts/42' ); if ( $response->is_success() ) { echo "Post deleted"; }
JSON Support
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
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
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)
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
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
Sign requests to AWS services without the full AWS SDK:
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
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
The signer adds or overwrites three headers:
- X-Amz-Date: Request timestamp in
YYYYMMDDTHHmmssZformat (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
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
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
The HTTP client uses a three-tier adapter system for maximum compatibility:
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
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
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
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
Force a specific adapter:
$client = new HttpClient( new SocketAdapter() ); // Uses socket even if cURL is available
Error Handling
Exception Hierarchy
RuntimeException
└─ HttpRequestException
└─ HttpTimeoutException
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
| Error | Cause |
|---|---|
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 directory |
Configuration 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 = 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
- PHP: 8.1 or higher
- ext-openssl: Recommended for HTTPS support
- At least one of:
ext-curl(preferred)allow_url_fopen = Onin php.inifsockopen()function enabled
Performance Tips
-
Use Streaming for Large Files: Use
with_sink()ordownload()to avoid buffering entire files in memory. -
Reuse HttpClient Instances: Create once, send many requests through it to benefit from connection pooling (cURL).
-
Set Appropriate Timeouts: Balance between catching truly broken connections and allowing slow networks:
$request = HttpRequest::get( $url )->with_options( [ 'timeout' => 60 ] );
-
Disable SSL Verification Only in Development: Use
without_ssl_verification()only in trusted environments. -
Check Redirect Loops: Monitor
redirect_historyto detect infinite redirects:if ( count( $response->redirect_history ) > 10 ) { // Possible redirect loop }
License
This project is licensed under the MIT License. See LICENSE file for details.
Author
Callistus Nwachukwu
Contributing
Contributions are welcome. Please ensure all code follows WordPress PHP Coding Standards (K&R braces, spaces inside parentheses, tab indentation).
Support
For issues, feature requests, or questions, please visit the project repository.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 5
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-05-12