Add adapter pattern with Curl and Swoole HTTP client implementations#19
Conversation
- replace assertEquals with assertSame
Refactor: assertions
- Added removeHeader() method to remove specific headers - Added clearHeaders() method to clear all headers - Updated PHP requirement to >=8.1 - Added return type annotation to Exception::__toString() - Improved code formatting in Response class
Add removeHeader and clearHeaders methods to Client
Support JSON string body in fetch
- Fix port mismatch: update all test URLs from 8001 to 8000 to match CI server - Fix Curl adapter: only set CURLOPT_POSTFIELDS when body is non-empty - Fix Swoole adapter: correct addData parameter order (should be value, key) - Fix Swoole adapter: rename $body to $currentResponseBody to avoid shadowing - Fix phpstan.neon: add reportUnmatchedIgnoredErrors: false - Fix router.php: add Content-Type header for JSON responses - Fix ClientTest: handle missing content-type header for GET requests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Dockerfile using appwrite/base:0.10.4 which includes Swoole - Add docker-compose.yml for running tests - Update tests workflow to use Docker for full Swoole test coverage Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove COPY ./composer.lock line since file is in .gitignore - Use composer update instead of install (generates lock at build time) - Fix FROM...AS casing inconsistency Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Curl: Store and reuse CurlHandle with curl_reset() between requests - Swoole: Cache clients by host:port:ssl key for connection reuse - Replace FQNs with proper use statements - Extract configureBody() helper method to reduce duplication - Enable keep_alive for Swoole clients - Add __destruct() to properly close handles/clients Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Curl: Accept array of CURLOPT_* options in constructor - Swoole: Accept array of client settings in constructor - Config options are merged with defaults (user config takes precedence) - Extracted buildClientSettings() helper in Swoole adapter Example usage: // Curl with custom SSL options new Curl([CURLOPT_SSL_VERIFYPEER => false]); // Swoole with long connections new Swoole(['keep_alive' => true, 'timeout' => 60]); Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Swoole adapter: - Add `coroutines` boolean constructor parameter (default: true) - When true: automatically wraps requests in coroutine scheduler - When false: executes directly (for use inside Swoole servers) - Refactored request logic into executeRequest() method Curl adapter: - Fix getHandle() to properly handle curl_init() returning false - Throw descriptive Exception instead of causing TypeError Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Both adapters now use discoverable named parameters instead of opaque arrays: Curl adapter parameters: - sslVerifyPeer, sslVerifyHost, sslCertificate, sslKey - caInfo, caPath - proxy, proxyUserPwd, proxyType - httpVersion, tcpKeepAlive, tcpKeepIdle, tcpKeepInterval - bufferSize, verbose Swoole adapter parameters: - coroutines, keepAlive, socketBufferSize, httpCompression - sslVerifyPeer, sslHostName, sslCafile, sslAllowSelfSigned - packageMaxLength, websocketMask, websocketCompression - bindAddress, bindPort, lowaterMark All parameters have sensible defaults, enabling IDE autocompletion and discoverability without requiring documentation lookup. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- coroutines=true: Uses Swoole\Coroutine\Http\Client (default) - coroutines=false: Uses Swoole\Http\Client (sync/blocking) Updated type hints to support both client types via union types. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Import Swoole\Http\Client as SyncClient - Remove @phpstan-ignore annotations - Use @var CoClient annotation for type compatibility Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Import Swoole\Http\Client as SyncClient - Use SyncClient::class instead of string names - Add PHPStan ignore pattern for missing Swoole\Http\Client stubs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Swoole extension isn't installed in CI for static analysis, causing PHPStan to not find Swoole classes. The ide-helper package provides stubs so PHPStan can analyze the code properly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Upgrade to newer versions of GitHub Actions for better stability: - checkout v3 -> v4 - setup-buildx-action v2 -> v3 (with install: true) - build-push-action v3 -> v6 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix query string URL building: trim trailing '?' and '&' before determining separator to avoid invalid URLs like example.com&foo=bar - Fix Swoole adapter: filter sensitive headers (Authorization, Cookie, Proxy-Authorization, Host) on cross-origin redirects - Add regression tests for query string edge cases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Options\Request class for request options (timeout, redirects, etc.) - Add Options\Curl class for Curl adapter configuration (SSL, proxy, etc.) - Add Options\Swoole class for Swoole adapter configuration (coroutines, keep-alive, etc.) - Update Adapter interface to use RequestOptions - Update Curl and Swoole adapters to use options classes with getters - Update tests to use RequestOptions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Swoole\Http\Client was removed in Swoole 4.x - only the coroutine client (Swoole\Coroutine\Http\Client) is available. Simplified the adapter to always use the coroutine client. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
…ling Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
Greptile SummaryThis PR introduces a well-designed adapter pattern for the Fetch library, providing Curl and Swoole HTTP client implementations backed by clean options value objects and an updated
Confidence Score: 3/5Not safe to merge until the three P1 Swoole issues (concurrency, redirect body, streaming contract) are fixed. Three P1 findings in the new Swoole adapter represent real defects: incorrect HTTP semantics on redirects, potential request corruption under concurrent coroutine usage, and a broken contract with the Curl adapter for streaming. The Curl adapter and all other changed files are in good shape, but the Swoole implementation needs targeted fixes before production use. src/Adapter/Swoole.php requires attention for all three P1 issues. Important Files Changed
|
| private function getClient(string $host, int $port, bool $ssl): CoClient | ||
| { | ||
| $key = "{$host}:{$port}:" . ($ssl ? '1' : '0'); | ||
|
|
||
| if (!isset($this->clients[$key])) { | ||
| $this->clients[$key] = new CoClient($host, $port, $ssl); | ||
| } | ||
|
|
||
| return $this->clients[$key]; | ||
| } |
There was a problem hiding this comment.
Shared client pool is not coroutine-safe
$this->clients is a single shared pool on the adapter instance. When two concurrent coroutines both target the same host:port:ssl key, they get the same CoClient object. One coroutine's setMethod(), setHeaders(), and configureBody() calls then overwrite the other's configuration before execute() is called, resulting in requests being sent with the wrong method, wrong headers, or the wrong body.
A minimal safe fix is to never reuse a client that is currently executing a request. One approach: remove the client from the pool when it starts executing and re-add it (or a new one) when the response is received. Alternatively, restrict the pool to be per-coroutine (e.g. via Coroutine::getCid() as part of the cache key), or drop the pool entirely and create a new CoClient per request.
| $formattedHeaders = array_map(function ($key, $value) { | ||
| return $key . ':' . $value; | ||
| }, array_keys($headers), $headers); |
There was a problem hiding this comment.
Missing space after colon in request header formatting
The formatted header is Key:Value but the standard HTTP/1.1 header field syntax (RFC 7230 §3.2) uses Key: Value (OWS after the colon). While most servers tolerate the compact form, some origin servers and proxies are strict and will reject or misparse headers without the space.
| $formattedHeaders = array_map(function ($key, $value) { | |
| return $key . ':' . $value; | |
| }, array_keys($headers), $headers); | |
| $formattedHeaders = array_map(function ($key, $value) { | |
| return $key . ': ' . $value; | |
| }, array_keys($headers), $headers); |
Summary
This PR introduces an adapter pattern to the Fetch library, allowing multiple HTTP client implementations. Two concrete adapters are provided: a Curl adapter (using PHP's cURL extension) and a Swoole adapter (using Swoole's coroutine HTTP client).
Key Changes
Added Adapter Interface (
src/Adapter.php): Defines the contract for HTTP adapters with asend()methodCurl Adapter (
src/Adapter/Curl.php): Implements HTTP requests using PHP's cURL extension with support for:Swoole Adapter (
src/Adapter/Swoole.php): Implements HTTP requests using Swoole's coroutine HTTP client with support for:Options Classes: Created configuration classes for each adapter:
src/Options/Request.php: Common request options (timeout, redirects, user agent)src/Options/Curl.php: Curl-specific configurationsrc/Options/Swoole.php: Swoole-specific configurationClient Updates (
src/Client.php):Adapterinstance (defaults to Curl)removeHeader()andclearHeaders()methodsfetch()method to support string bodies in addition to arrayssetJsonEncodeFlags()to properly combine flags using bitwise ORconnectTimeoutfrom 60s to 5sResponse Improvements (
src/Response.php):text()method to handle null, string, scalar, and object typesjson()method with better type handling and error messagesTest Coverage:
tests/Adapter/CurlTest.php: Comprehensive tests for Curl adaptertests/Adapter/SwooleTest.php: Comprehensive tests for Swoole adapter (skipped if extension unavailable)tests/ClientTest.php: Added tests for default adapter, custom adapter, and Swoole adapterassertSameinstead ofassertEquals)Infrastructure:
Dockerfileanddocker-compose.ymlfor containerized testingphpstan.neonto include Swoole IDE helper for static analysiscomposer.jsonto require PHP 8.1+ and added phpstan dev dependencyNotable Implementation Details
Chunkobjects with data, size, timestamp, and index informationhttps://claude.ai/code/session_01YNnPHeCYQYWibVJ3m6uKht