-
Notifications
You must be signed in to change notification settings - Fork 3
Add curl/swoole adapters #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
9cff1be
Add curl/swoole adapters
abnegate 501264b
Fix PR review issues and failing tests
abnegate dd8b595
Add Docker setup for Swoole testing
abnegate 3362723
Fix Dockerfile build failure - remove composer.lock dependency
abnegate 0a98001
Use utopia-php/base image instead of appwrite/base
abnegate cbacfea
Refactor adapters to use imports and reuse client handles
abnegate 018602c
Add constructor config options for HTTP client customization
abnegate fb305aa
Fix handle init failure
abnegate 4e73fe3
Add coroutines flag to Swoole and fix Curl error handling
abnegate 7f17abc
Replace config arrays with named constructor parameters
abnegate 0c182a3
Use Swoole\Http\Client when coroutines=false
abnegate 9652041
Use imports instead of FQNs for Swoole clients
abnegate 9fe5a7a
Use ::class refs for Swoole clients
abnegate 2e2cb20
Add swoole/ide-helper for PHPStan stubs
abnegate 0ef30e8
Update src/Adapter/Swoole.php
abnegate 7763987
Fix stan
abnegate 7d1f9ea
Normalise headers
abnegate 94543f1
Update CI workflow action versions
abnegate 0e36e12
Fix URL query building and cross-origin header leak
abnegate 01f0ef0
Remove deprecated install parameter from setup-buildx-action
abnegate 00792fd
Refactor request and adapter options into dedicated classes
abnegate 51ef689
Simplify Swoole sync client instantiation
abnegate 7c7214d
Remove sync client support from Swoole adapter
abnegate 1efb370
Update .github/workflows/tests.yml
abnegate 9060573
Initial plan
Copilot 47c46fc
Replace socket_strerror with Swoole built-in errMsg
Copilot ae9cc01
Merge pull request #17 from utopia-php/copilot/sub-pr-16
abnegate 5a430b7
Initial plan
Copilot 632486c
Address PR feedback: Import Swoole\Coroutine\run and fix GraphQL hand…
Copilot c6e1292
Merge pull request #18 from utopia-php/copilot/sub-pr-16
abnegate 59088bb
Use const
abnegate 93ca185
Merge branch 'feat-adapters' of github.com:utopia-php/fetch into feat…
abnegate 37ca2e7
Fix headers
abnegate File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,35 @@ | ||
| name: "Tests" | ||
|
|
||
| on: [ pull_request ] | ||
| on: [pull_request] | ||
| jobs: | ||
| lint: | ||
| tests: | ||
| name: Tests | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v3 | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 2 | ||
|
|
||
| - run: git checkout HEAD^2 | ||
|
|
||
| - name: Install dependencies | ||
| run: composer install --profile --ignore-platform-reqs | ||
|
|
||
| - name: Run Tests | ||
| run: php -S localhost:8000 tests/router.php & | ||
| composer test | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Build image | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: . | ||
| push: false | ||
| tags: fetch-dev | ||
| load: true | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
|
|
||
| - name: Start Server | ||
| run: | | ||
| docker compose up -d --wait --wait-timeout 30 | ||
|
|
||
| - name: Run Tests | ||
| run: docker compose exec -T php vendor/bin/phpunit --configuration phpunit.xml | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,4 @@ vendor | |
| *.cache | ||
| composer.lock | ||
| state.json | ||
| .idea | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| FROM composer:2.0 AS step0 | ||
|
|
||
| WORKDIR /src/ | ||
|
|
||
| COPY ./composer.json /src/ | ||
|
|
||
| RUN composer update --ignore-platform-reqs --optimize-autoloader \ | ||
| --no-plugins --no-scripts --prefer-dist | ||
|
|
||
| FROM appwrite/utopia-base:php-8.4-0.2.1 AS final | ||
|
|
||
| LABEL maintainer="team@utopia.io" | ||
|
|
||
| WORKDIR /code | ||
|
|
||
| COPY --from=step0 /src/vendor /code/vendor | ||
|
|
||
| # Add Source Code | ||
| COPY ./src /code/src | ||
| COPY ./tests /code/tests | ||
| COPY ./phpunit.xml /code/ | ||
|
|
||
| EXPOSE 8000 | ||
|
|
||
| CMD [ "php", "-S", "0.0.0.0:8000", "tests/router.php"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| services: | ||
| php: | ||
| image: fetch-dev | ||
| build: | ||
| context: . | ||
| ports: | ||
| - 8000:8000 | ||
| volumes: | ||
| - ./tests:/code/tests | ||
| - ./src:/code/src |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,9 @@ | ||
| parameters: | ||
| level: 8 | ||
| level: max | ||
| paths: | ||
| - src | ||
| - tests | ||
| - tests | ||
| scanFiles: | ||
| - vendor/swoole/ide-helper/src/swoole_library/src/core/Coroutine/functions.php | ||
| scanDirectories: | ||
| - vendor/swoole/ide-helper/src/swoole |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Utopia\Fetch; | ||
|
|
||
| use Utopia\Fetch\Options\Request as RequestOptions; | ||
|
|
||
| /** | ||
| * Adapter interface | ||
| * Defines the contract for HTTP adapters | ||
| * @package Utopia\Fetch | ||
| */ | ||
| interface Adapter | ||
| { | ||
| /** | ||
| * Send an HTTP request | ||
| * | ||
| * @param string $url The URL to send the request to | ||
| * @param string $method The HTTP method (GET, POST, etc.) | ||
| * @param mixed $body The request body (string, array, or null) | ||
| * @param array<string, string> $headers The request headers (formatted as key-value pairs) | ||
| * @param RequestOptions $options Request options (timeout, connectTimeout, maxRedirects, allowRedirects, userAgent) | ||
| * @param callable|null $chunkCallback Optional callback for streaming chunks | ||
| * @return Response The HTTP response | ||
| * @throws Exception If the request fails | ||
| */ | ||
| public function send( | ||
| string $url, | ||
| string $method, | ||
| mixed $body, | ||
| array $headers, | ||
| RequestOptions $options, | ||
| ?callable $chunkCallback = null | ||
| ): Response; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Utopia\Fetch\Adapter; | ||
|
|
||
| use CurlHandle; | ||
| use Utopia\Fetch\Adapter; | ||
| use Utopia\Fetch\Chunk; | ||
| use Utopia\Fetch\Exception; | ||
| use Utopia\Fetch\Options\Curl as CurlOptions; | ||
| use Utopia\Fetch\Options\Request as RequestOptions; | ||
| use Utopia\Fetch\Response; | ||
|
|
||
| /** | ||
| * Curl Adapter | ||
| * HTTP adapter using PHP's cURL extension | ||
| * @package Utopia\Fetch\Adapter | ||
| */ | ||
| class Curl implements Adapter | ||
| { | ||
| private ?CurlHandle $handle = null; | ||
|
|
||
| /** | ||
| * @var array<int, mixed> | ||
| */ | ||
| private array $config = []; | ||
|
|
||
| /** | ||
| * Create a new Curl adapter | ||
| * | ||
| * @param CurlOptions|null $options Curl adapter options | ||
| */ | ||
| public function __construct(?CurlOptions $options = null) | ||
| { | ||
| $options ??= new CurlOptions(); | ||
|
|
||
| $this->config[CURLOPT_SSL_VERIFYPEER] = $options->getSslVerifyPeer(); | ||
| $this->config[CURLOPT_SSL_VERIFYHOST] = $options->getSslVerifyHost() ? 2 : 0; | ||
|
|
||
| if ($options->getSslCertificate() !== null) { | ||
| $this->config[CURLOPT_SSLCERT] = $options->getSslCertificate(); | ||
| } | ||
|
|
||
| if ($options->getSslKey() !== null) { | ||
| $this->config[CURLOPT_SSLKEY] = $options->getSslKey(); | ||
| } | ||
|
|
||
| if ($options->getCaInfo() !== null) { | ||
| $this->config[CURLOPT_CAINFO] = $options->getCaInfo(); | ||
| } | ||
|
|
||
| if ($options->getCaPath() !== null) { | ||
| $this->config[CURLOPT_CAPATH] = $options->getCaPath(); | ||
| } | ||
|
|
||
| if ($options->getProxy() !== null) { | ||
| $this->config[CURLOPT_PROXY] = $options->getProxy(); | ||
| $this->config[CURLOPT_PROXYTYPE] = $options->getProxyType(); | ||
|
|
||
| if ($options->getProxyUserPwd() !== null) { | ||
| $this->config[CURLOPT_PROXYUSERPWD] = $options->getProxyUserPwd(); | ||
| } | ||
| } | ||
|
|
||
| $this->config[CURLOPT_HTTP_VERSION] = $options->getHttpVersion(); | ||
| $this->config[CURLOPT_TCP_KEEPALIVE] = $options->getTcpKeepAlive() ? 1 : 0; | ||
| $this->config[CURLOPT_TCP_KEEPIDLE] = $options->getTcpKeepIdle(); | ||
| $this->config[CURLOPT_TCP_KEEPINTVL] = $options->getTcpKeepInterval(); | ||
| $this->config[CURLOPT_BUFFERSIZE] = $options->getBufferSize(); | ||
| $this->config[CURLOPT_VERBOSE] = $options->getVerbose(); | ||
| } | ||
|
|
||
| /** | ||
| * Get or create the cURL handle | ||
| * | ||
| * @return CurlHandle | ||
| * @throws Exception If cURL initialization fails | ||
| */ | ||
| private function getHandle(): CurlHandle | ||
| { | ||
| if ($this->handle === null) { | ||
| $handle = curl_init(); | ||
| if ($handle === false) { | ||
| throw new Exception('Failed to initialize cURL handle'); | ||
| } | ||
| $this->handle = $handle; | ||
| } else { | ||
| curl_reset($this->handle); | ||
| } | ||
|
|
||
| return $this->handle; | ||
| } | ||
|
|
||
| /** | ||
| * Send an HTTP request using cURL | ||
| * | ||
| * @param string $url The URL to send the request to | ||
| * @param string $method The HTTP method (GET, POST, etc.) | ||
| * @param mixed $body The request body (string, array, or null) | ||
| * @param array<string, string> $headers The request headers (formatted as key-value pairs) | ||
| * @param RequestOptions $options Request options (timeout, connectTimeout, maxRedirects, allowRedirects, userAgent) | ||
| * @param callable|null $chunkCallback Optional callback for streaming chunks | ||
| * @return Response The HTTP response | ||
| * @throws Exception If the request fails | ||
| */ | ||
| public function send( | ||
| string $url, | ||
| string $method, | ||
| mixed $body, | ||
| array $headers, | ||
| RequestOptions $options, | ||
| ?callable $chunkCallback = null | ||
| ): Response { | ||
| $formattedHeaders = array_map(function ($key, $value) { | ||
| return $key . ':' . $value; | ||
| }, array_keys($headers), $headers); | ||
|
|
||
| $responseHeaders = []; | ||
| $responseBody = ''; | ||
| $chunkIndex = 0; | ||
|
|
||
| $ch = $this->getHandle(); | ||
| $curlOptions = [ | ||
| CURLOPT_URL => $url, | ||
| CURLOPT_HTTPHEADER => $formattedHeaders, | ||
| CURLOPT_CUSTOMREQUEST => $method, | ||
| CURLOPT_HEADERFUNCTION => function ($curl, $header) use (&$responseHeaders) { | ||
| $len = strlen($header); | ||
| $header = explode(':', $header, 2); | ||
| if (count($header) < 2) { | ||
| return $len; | ||
| } | ||
| $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); | ||
| return $len; | ||
| }, | ||
| CURLOPT_WRITEFUNCTION => function ($ch, $data) use ($chunkCallback, &$responseBody, &$chunkIndex) { | ||
| if ($chunkCallback !== null) { | ||
| $chunk = new Chunk( | ||
| data: $data, | ||
| size: strlen($data), | ||
| timestamp: microtime(true), | ||
| index: $chunkIndex++ | ||
| ); | ||
| $chunkCallback($chunk); | ||
| } else { | ||
| $responseBody .= $data; | ||
| } | ||
| return strlen($data); | ||
| }, | ||
| CURLOPT_CONNECTTIMEOUT_MS => $options->getConnectTimeout(), | ||
| CURLOPT_TIMEOUT_MS => $options->getTimeout(), | ||
| CURLOPT_MAXREDIRS => $options->getMaxRedirects(), | ||
| CURLOPT_FOLLOWLOCATION => $options->getAllowRedirects(), | ||
| CURLOPT_USERAGENT => $options->getUserAgent() | ||
| ]; | ||
|
|
||
| if ($body !== null && $body !== [] && $body !== '') { | ||
| $curlOptions[CURLOPT_POSTFIELDS] = $body; | ||
| } | ||
|
|
||
| // Merge adapter config (adapter config takes precedence) | ||
| $curlOptions = $this->config + $curlOptions; | ||
|
|
||
| foreach ($curlOptions as $option => $value) { | ||
| curl_setopt($ch, $option, $value); | ||
| } | ||
|
|
||
| $success = curl_exec($ch); | ||
| if ($success === false) { | ||
| $errorMsg = curl_error($ch); | ||
| throw new Exception($errorMsg); | ||
| } | ||
|
|
||
| $responseStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | ||
|
|
||
| return new Response( | ||
| statusCode: $responseStatusCode, | ||
| headers: $responseHeaders, | ||
| body: $responseBody | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Close the cURL handle when the adapter is destroyed | ||
| */ | ||
| public function __destruct() | ||
| { | ||
| if ($this->handle !== null) { | ||
| curl_close($this->handle); | ||
| $this->handle = null; | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.