-
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
base: main
Are you sure you want to change the base?
Changes from all commits
9cff1be
501264b
dd8b595
3362723
0a98001
cbacfea
018602c
fb305aa
4e73fe3
7f17abc
0c182a3
9652041
9fe5a7a
2e2cb20
0ef30e8
7763987
7d1f9ea
94543f1
0e36e12
01f0ef0
00792fd
51ef689
7c7214d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,23 +1,36 @@ | ||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||
| sleep 5 | ||||||||||||||||||||||||||||
| - name: Run Tests | ||||||||||||||||||||||||||||
| run: docker compose exec -T php vendor/bin/phpunit --configuration phpunit.xml | ||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Short answer: Yes — docker compose's up --wait is part of Compose V2 (documented in Docker CLI) and runners for ubuntu-latest include Docker Compose V2, but the exact shipped version varies. To guarantee --wait is available in GitHub Actions, install a recent Compose V2 on the runner (for example via docker/setup-compose-action) or explicitly install a known Compose v2 release that supports --wait. [1][2][3] Sources:
🌐 Web query:
💡 Result: Short answer: docker compose up supports a wait timeout via --wait-timeout (seconds) when using the v2 "docker compose" CLI — e.g. docker compose up --detach --wait --wait-timeout 60. Use the v2 CLI (not the legacy docker-compose) because GitHub-hosted runners may have the older binary without --wait/--wait-timeout. [1][2] References:
🌐 Web query:
💡 Result: Short answer:
Sources: Replace fixed sleep with service readiness wait to avoid CI flakes. A hard ✅ Example using Compose wait - name: Start Server
run: |
- docker compose up -d
- sleep 5
+ docker compose up -d --wait --wait-timeout 30Note: GitHub Actions 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,4 @@ vendor | |
| *.cache | ||
| composer.lock | ||
| state.json | ||
| .idea | ||
| 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="[email protected]" | ||
|
|
||
| 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"] |
| 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 |
| 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 |
| 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; | ||
| } |
| 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; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
GitHub Actions security best practices pinning commit SHA vs tags💡 Result:
Short answer
Why (concise)
Practical recommendations
Tradeoffs
Sources
[1] GitHub Well‑Architected / Actions security (pin to commit SHA).
[4] pinsha (tool & guidance for pinning).
[5] gha‑pinner / similar tools (automation).
[6] Palo Alto Networks blog on pinning and supply‑chain risks.
[7] GitHub Changelog: Allowed actions policy & SHA pinning enforcement.
Pin GitHub Actions to immutable commit SHAs.
Tags can be retargeted, creating a supply-chain risk. Pin to commit SHAs and annotate with the tag for clarity.
🔐 Example pinning pattern
Consider using Dependabot or Renovate to automate finding and updating SHAs as new versions are released.
🤖 Prompt for AI Agents