From d19da136fc560eaf7c2137c4448809b5c7eeebf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 3 Sep 2025 19:05:57 +0200 Subject: [PATCH] Allow array for headers --- src/Http/Adapter/FPM/Response.php | 12 ++++++++--- src/Http/Adapter/Swoole/Response.php | 4 ++-- src/Http/Response.php | 32 ++++++++++++++++++++++------ tests/e2e/BaseTest.php | 8 +++++++ tests/e2e/Client.php | 27 ++++++++++++++++++++++- tests/e2e/init.php | 9 ++++++++ 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/Http/Adapter/FPM/Response.php b/src/Http/Adapter/FPM/Response.php index 6c877918..6a5d4dd4 100644 --- a/src/Http/Adapter/FPM/Response.php +++ b/src/Http/Adapter/FPM/Response.php @@ -53,12 +53,18 @@ protected function sendStatus(int $statusCode): void * Output Header * * @param string $key - * @param string $value + * @param string|array $value * @return void */ - public function sendHeader(string $key, string $value): void + public function sendHeader(string $key, mixed $value): void { - \header($key.': '.$value); + if (\is_array($value)) { + foreach ($value as $v) { + \header($key.': '.$v, false); + } + } else { + \header($key.': '.$value); + } } /** diff --git a/src/Http/Adapter/Swoole/Response.php b/src/Http/Adapter/Swoole/Response.php index b0b99978..be48a027 100644 --- a/src/Http/Adapter/Swoole/Response.php +++ b/src/Http/Adapter/Swoole/Response.php @@ -65,10 +65,10 @@ protected function sendStatus(int $statusCode): void * Send Header * * @param string $key - * @param string $value + * @param string|array $value * @return void */ - public function sendHeader(string $key, string $value): void + public function sendHeader(string $key, mixed $value): void { $this->swoole->header($key, $value); } diff --git a/src/Http/Response.php b/src/Http/Response.php index d0e6e793..2bc4a5f4 100755 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -225,7 +225,7 @@ abstract class Response protected bool $sent = false; /** - * @var array + * @var array> */ protected array $headers = []; @@ -365,7 +365,15 @@ public function enablePayload(): static */ public function addHeader(string $key, string $value): static { - $this->headers[$key] = $value; + if (\array_key_exists($key, $this->headers)) { + if (\is_array($this->headers[$key])) { + $this->headers[$key][] = $value; + } else { + $this->headers[$key] = [$this->headers[$key], $value]; + } + } else { + $this->headers[$key] = $value; + } return $this; } @@ -391,7 +399,7 @@ public function removeHeader(string $key): static * * Return array of all response headers * - * @return array + * @return array> */ public function getHeaders(): array { @@ -483,7 +491,19 @@ public function send(string $body = ''): void if (!$this->disablePayload) { $length = strlen($body); - $this->size = $this->size + strlen(implode("\n", $this->headers)) + $length; + $headersSize = 0; + foreach ($this->headers as $name => $values) { + if (\is_array($values)) { + foreach ($values as $value) { + $headersSize += \strlen($name . ': ' . $value); + } + $headersSize += (\count($values) - 1) * 2; // linebreaks + } else { + $headersSize += \strlen($name . ': ' . $values); + } + } + $headersSize += (\count($this->headers) - 1) * 2; // linebreaks + $this->size = $this->size + $headersSize + $length; if (array_key_exists( $this->contentType, @@ -599,10 +619,10 @@ abstract protected function sendStatus(int $statusCode): void; * Output Header * * @param string $key - * @param string $value + * @param string|array $value * @return void */ - abstract public function sendHeader(string $key, string $value): void; + abstract public function sendHeader(string $key, mixed $value): void; /** * Send Cookie diff --git a/tests/e2e/BaseTest.php b/tests/e2e/BaseTest.php index 7279ee41..04b4249d 100644 --- a/tests/e2e/BaseTest.php +++ b/tests/e2e/BaseTest.php @@ -68,4 +68,12 @@ public function testCookie() $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($cookie, $response['body']); } + + public function testSetCookie() + { + $response = $this->client->call(Client::METHOD_GET, '/set-cookie'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('value1', $response['cookies']['key1']); + $this->assertEquals('value2', $response['cookies']['key2']); + } } diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index 5bf15b2e..42fdd5a8 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -61,6 +61,8 @@ public function call(string $method, string $path = '', array $headers = [], arr $responseType = ''; $responseBody = ''; + $cookies = []; + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); @@ -68,7 +70,7 @@ public function call(string $method, string $path = '', array $headers = [], arr curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); curl_setopt($ch, CURLOPT_TIMEOUT, 15); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders, &$cookies) { $len = strlen($header); $header = explode(':', $header, 2); @@ -76,6 +78,12 @@ public function call(string $method, string $path = '', array $headers = [], arr return $len; } + if (strtolower(trim($header[0])) == 'set-cookie') { + $parsed = $this->parseCookie((string)trim($header[1])); + $name = array_key_first($parsed); + $cookies[$name] = $parsed[$name]; + } + $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); return $len; @@ -99,6 +107,23 @@ public function call(string $method, string $path = '', array $headers = [], arr return [ 'headers' => $responseHeaders, 'body' => $responseBody, + 'cookies' => $cookies, ]; } + + /** + * Parse Cookie String + * + * @param string $cookie + * @return array + */ + public function parseCookie(string $cookie): array + { + $cookies = []; + + parse_str(strtr($cookie, ['&' => '%26', '+' => '%2B', ';' => '&']), $cookies); + + return $cookies; + } + } diff --git a/tests/e2e/init.php b/tests/e2e/init.php index 2c85bd13..54638fd9 100644 --- a/tests/e2e/init.php +++ b/tests/e2e/init.php @@ -34,6 +34,15 @@ $response->send($request->getHeaders()['cookie'] ?? ''); }); +Http::get('/set-cookie') + ->inject('request') + ->inject('response') + ->action(function (Request $request, Response $response) { + $response->addHeader('Set-Cookie', 'key1=value1'); + $response->addHeader('Set-Cookie', 'key2=value2'); + $response->send('OK'); + }); + Http::get('/chunked') ->inject('response') ->action(function (Response $response) {