Skip to content

Commit 1687905

Browse files
committed
(fix): Address PR review — configurable cache TTL, SMTP EOF check, raw forwarder headers, non-root Dockerfile
1 parent fe85f89 commit 1687905

11 files changed

Lines changed: 78 additions & 28 deletions

File tree

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ RUN composer install \
3434

3535
COPY . .
3636

37+
RUN addgroup -S app && adduser -S -G app app
38+
USER app
39+
3740
EXPOSE 8080 8081 8025
3841

3942
CMD ["php", "examples/http.php"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ composer check
328328

329329
## Architecture
330330

331-
```
331+
```text
332332
┌─────────────────────────────────────────────────────────────────┐
333333
│ Utopia Proxy │
334334
├─────────────────────────────────────────────────────────────────┤

src/Adapter.php

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class Adapter
2626
/** @var bool Skip SSRF validation for trusted backends */
2727
protected bool $skipValidation = false;
2828

29+
/** @var int Routing cache TTL in seconds (0 disables caching) */
30+
protected int $cacheTTL = 0;
31+
2932
/** @var int Activity tracking interval in seconds */
3033
protected int $interval = 30;
3134

@@ -82,6 +85,13 @@ public function setSkipValidation(bool $skip): static
8285
return $this;
8386
}
8487

88+
public function setCacheTtl(int $seconds): static
89+
{
90+
$this->cacheTTL = $seconds;
91+
92+
return $this;
93+
}
94+
8595
/**
8696
* Notify connect event
8797
*
@@ -174,20 +184,23 @@ public function getProtocol(): Protocol
174184
*/
175185
public function route(string $resourceId): ConnectionResult
176186
{
177-
$cached = $this->router->get($resourceId);
178187
$now = \time();
179188

180-
if ($cached !== false && \is_array($cached)) {
181-
/** @var array{endpoint: string, updated: int} $cached */
182-
if (($now - $cached['updated']) < 1) {
183-
$this->stats['cacheHits']++;
184-
$this->stats['connections']++;
189+
if ($this->cacheTTL > 0) {
190+
$cached = $this->router->get($resourceId);
185191

186-
return new ConnectionResult(
187-
endpoint: $cached['endpoint'],
188-
protocol: $this->getProtocol(),
189-
metadata: ['cached' => true]
190-
);
192+
if ($cached !== false && \is_array($cached)) {
193+
/** @var array{endpoint: string, updated: int} $cached */
194+
if (($now - $cached['updated']) < $this->cacheTTL) {
195+
$this->stats['cacheHits']++;
196+
$this->stats['connections']++;
197+
198+
return new ConnectionResult(
199+
endpoint: $cached['endpoint'],
200+
protocol: $this->getProtocol(),
201+
metadata: ['cached' => true]
202+
);
203+
}
191204
}
192205
}
193206

@@ -227,10 +240,12 @@ public function route(string $resourceId): ConnectionResult
227240
$this->validate($endpoint);
228241
}
229242

230-
$this->router->set($resourceId, [
231-
'endpoint' => $endpoint,
232-
'updated' => $now,
233-
]);
243+
if ($this->cacheTTL > 0) {
244+
$this->router->set($resourceId, [
245+
'endpoint' => $endpoint,
246+
'updated' => $now,
247+
]);
248+
}
234249

235250
$this->stats['connections']++;
236251

src/Server/HTTP/Config.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public function __construct(
4444
public readonly bool $rawBackend = false,
4545
public readonly bool $rawBackendAssumeOk = false,
4646
public readonly bool $skipValidation = false,
47+
public readonly int $cacheTTL = 60,
4748
public readonly ?\Closure $requestHandler = null,
4849
public readonly ?\Closure $workerStart = null,
4950
) {

src/Server/HTTP/Swoole.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public function onStart(Server $server): void
9292
public function onWorkerStart(Server $server, int $workerId): void
9393
{
9494
$this->adapter = new Adapter($this->resolver, name: 'HTTP', protocol: Protocol::HTTP);
95+
$this->adapter->setCacheTtl($this->config->cacheTTL);
9596

9697
if ($this->config->skipValidation) {
9798
$this->adapter->setSkipValidation(true);
@@ -381,14 +382,23 @@ protected function forwardRawRequest(Request $request, Response $response, strin
381382
if (preg_match('/^HTTP\/\\d+\\.\\d+\\s+(\\d+)/', $lines[0], $matches)) {
382383
$statusCode = (int) $matches[1];
383384
}
384-
foreach ($lines as $line) {
385-
if (stripos($line, 'content-length:') === 0) {
386-
$contentLength = (int) trim(substr($line, 15));
387-
break;
385+
$skipHeaders = ['connection', 'keep-alive', 'transfer-encoding', 'content-length'];
386+
for ($i = 1; $i < count($lines); $i++) {
387+
$colonPos = strpos($lines[$i], ':');
388+
if ($colonPos === false) {
389+
continue;
388390
}
389-
if (stripos($line, 'transfer-encoding:') === 0 && stripos($line, 'chunked') !== false) {
391+
$key = substr($lines[$i], 0, $colonPos);
392+
$value = trim(substr($lines[$i], $colonPos + 1));
393+
$lower = strtolower($key);
394+
if ($lower === 'content-length') {
395+
$contentLength = (int) $value;
396+
} elseif ($lower === 'transfer-encoding' && stripos($value, 'chunked') !== false) {
390397
$chunked = true;
391398
}
399+
if (!in_array($lower, $skipHeaders, true)) {
400+
$response->header($key, $value);
401+
}
392402
}
393403

394404
if (!$this->config->rawBackendAssumeOk) {

src/Server/HTTP/SwooleCoroutine.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ protected function configure(): void
8484
protected function initAdapter(): void
8585
{
8686
$this->adapter = new Adapter($this->resolver, name: 'HTTP', protocol: Protocol::HTTP);
87+
$this->adapter->setCacheTtl($this->config->cacheTTL);
8788

8889
if ($this->config->skipValidation) {
8990
$this->adapter->setSkipValidation(true);
@@ -367,14 +368,23 @@ protected function forwardRawRequest(Request $request, Response $response, strin
367368
if (preg_match('/^HTTP\/\\d+\\.\\d+\\s+(\\d+)/', $lines[0], $matches)) {
368369
$statusCode = (int) $matches[1];
369370
}
370-
foreach ($lines as $line) {
371-
if (stripos($line, 'content-length:') === 0) {
372-
$contentLength = (int) trim(substr($line, 15));
373-
break;
371+
$skipHeaders = ['connection', 'keep-alive', 'transfer-encoding', 'content-length'];
372+
for ($i = 1; $i < count($lines); $i++) {
373+
$colonPos = strpos($lines[$i], ':');
374+
if ($colonPos === false) {
375+
continue;
374376
}
375-
if (stripos($line, 'transfer-encoding:') === 0 && stripos($line, 'chunked') !== false) {
377+
$key = substr($lines[$i], 0, $colonPos);
378+
$value = trim(substr($lines[$i], $colonPos + 1));
379+
$lower = strtolower($key);
380+
if ($lower === 'content-length') {
381+
$contentLength = (int) $value;
382+
} elseif ($lower === 'transfer-encoding' && stripos($value, 'chunked') !== false) {
376383
$chunked = true;
377384
}
385+
if (!in_array($lower, $skipHeaders, true)) {
386+
$response->header($key, $value);
387+
}
378388
}
379389

380390
if (!$this->config->rawBackendAssumeOk) {

src/Server/SMTP/Config.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public function __construct(
1717
public readonly int $timeout = 30,
1818
public readonly float $connectTimeout = 5.0,
1919
public readonly bool $skipValidation = false,
20+
public readonly int $cacheTTL = 60,
2021
) {
2122
}
2223
}

src/Server/SMTP/Swoole.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ protected function configure(): void
5959
'tcp_fastopen' => true,
6060
'open_cpu_affinity' => true,
6161
'open_length_check' => false,
62+
'open_eof_check' => true,
6263
'package_eof' => "\r\n",
6364
'package_max_length' => 10 * 1024 * 1024,
6465
'task_enable_coroutine' => true,
@@ -85,6 +86,7 @@ public function onWorkerStart(Server $server, int $workerId): void
8586
name: 'SMTP',
8687
protocol: Protocol::SMTP
8788
);
89+
$this->adapter->setCacheTtl($this->config->cacheTTL);
8890

8991
if ($this->config->skipValidation) {
9092
$this->adapter->setSkipValidation(true);

tests/AdapterStatsTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public function testCacheHitUpdatesStats(): void
2525
$this->resolver->setEndpoint('127.0.0.1:8080');
2626
$adapter = new Adapter($this->resolver, name: 'HTTP', protocol: Protocol::HTTP);
2727
$adapter->setSkipValidation(true);
28+
$adapter->setCacheTTL(60);
2829

2930
$start = time();
3031
while (time() === $start) {

tests/Integration/EdgeIntegrationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ public function testStatsAggregateAcrossOperations(): void
428428

429429
$adapter = new TCPAdapter(port: 5432, resolver: $resolver);
430430
$adapter->setSkipValidation(true);
431+
$adapter->setCacheTTL(60);
431432

432433
// Align to second boundary
433434
$start = time();

0 commit comments

Comments
 (0)