Skip to content

Commit 334b99d

Browse files
authored
Merge pull request #182 from utopia-php/feat-array-headers
Feat: Allow array for headers
2 parents be935b1 + d19da13 commit 334b99d

6 files changed

Lines changed: 80 additions & 12 deletions

File tree

src/Http/Adapter/FPM/Response.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,18 @@ protected function sendStatus(int $statusCode): void
5353
* Output Header
5454
*
5555
* @param string $key
56-
* @param string $value
56+
* @param string|array<string> $value
5757
* @return void
5858
*/
59-
public function sendHeader(string $key, string $value): void
59+
public function sendHeader(string $key, mixed $value): void
6060
{
61-
\header($key.': '.$value);
61+
if (\is_array($value)) {
62+
foreach ($value as $v) {
63+
\header($key.': '.$v, false);
64+
}
65+
} else {
66+
\header($key.': '.$value);
67+
}
6268
}
6369

6470
/**

src/Http/Adapter/Swoole/Response.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ protected function sendStatus(int $statusCode): void
6565
* Send Header
6666
*
6767
* @param string $key
68-
* @param string $value
68+
* @param string|array<string> $value
6969
* @return void
7070
*/
71-
public function sendHeader(string $key, string $value): void
71+
public function sendHeader(string $key, mixed $value): void
7272
{
7373
$this->swoole->header($key, $value);
7474
}

src/Http/Response.php

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ abstract class Response
225225
protected bool $sent = false;
226226

227227
/**
228-
* @var array
228+
* @var array<string, string|array<string>>
229229
*/
230230
protected array $headers = [];
231231

@@ -365,7 +365,15 @@ public function enablePayload(): static
365365
*/
366366
public function addHeader(string $key, string $value): static
367367
{
368-
$this->headers[$key] = $value;
368+
if (\array_key_exists($key, $this->headers)) {
369+
if (\is_array($this->headers[$key])) {
370+
$this->headers[$key][] = $value;
371+
} else {
372+
$this->headers[$key] = [$this->headers[$key], $value];
373+
}
374+
} else {
375+
$this->headers[$key] = $value;
376+
}
369377

370378
return $this;
371379
}
@@ -391,7 +399,7 @@ public function removeHeader(string $key): static
391399
*
392400
* Return array of all response headers
393401
*
394-
* @return array
402+
* @return array<string, array<string, string>>
395403
*/
396404
public function getHeaders(): array
397405
{
@@ -483,7 +491,19 @@ public function send(string $body = ''): void
483491
if (!$this->disablePayload) {
484492
$length = strlen($body);
485493

486-
$this->size = $this->size + strlen(implode("\n", $this->headers)) + $length;
494+
$headersSize = 0;
495+
foreach ($this->headers as $name => $values) {
496+
if (\is_array($values)) {
497+
foreach ($values as $value) {
498+
$headersSize += \strlen($name . ': ' . $value);
499+
}
500+
$headersSize += (\count($values) - 1) * 2; // linebreaks
501+
} else {
502+
$headersSize += \strlen($name . ': ' . $values);
503+
}
504+
}
505+
$headersSize += (\count($this->headers) - 1) * 2; // linebreaks
506+
$this->size = $this->size + $headersSize + $length;
487507

488508
if (array_key_exists(
489509
$this->contentType,
@@ -599,10 +619,10 @@ abstract protected function sendStatus(int $statusCode): void;
599619
* Output Header
600620
*
601621
* @param string $key
602-
* @param string $value
622+
* @param string|array<string> $value
603623
* @return void
604624
*/
605-
abstract public function sendHeader(string $key, string $value): void;
625+
abstract public function sendHeader(string $key, mixed $value): void;
606626

607627
/**
608628
* Send Cookie

tests/e2e/BaseTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,12 @@ public function testCookie()
6868
$this->assertEquals(200, $response['headers']['status-code']);
6969
$this->assertEquals($cookie, $response['body']);
7070
}
71+
72+
public function testSetCookie()
73+
{
74+
$response = $this->client->call(Client::METHOD_GET, '/set-cookie');
75+
$this->assertEquals(200, $response['headers']['status-code']);
76+
$this->assertEquals('value1', $response['cookies']['key1']);
77+
$this->assertEquals('value2', $response['cookies']['key2']);
78+
}
7179
}

tests/e2e/Client.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,29 @@ public function call(string $method, string $path = '', array $headers = [], arr
6161
$responseType = '';
6262
$responseBody = '';
6363

64+
$cookies = [];
65+
6466
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
6567
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
6668
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
6769
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36');
6870
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
6971
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
7072
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
71-
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
73+
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders, &$cookies) {
7274
$len = strlen($header);
7375
$header = explode(':', $header, 2);
7476

7577
if (count($header) < 2) { // ignore invalid headers
7678
return $len;
7779
}
7880

81+
if (strtolower(trim($header[0])) == 'set-cookie') {
82+
$parsed = $this->parseCookie((string)trim($header[1]));
83+
$name = array_key_first($parsed);
84+
$cookies[$name] = $parsed[$name];
85+
}
86+
7987
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
8088

8189
return $len;
@@ -99,6 +107,23 @@ public function call(string $method, string $path = '', array $headers = [], arr
99107
return [
100108
'headers' => $responseHeaders,
101109
'body' => $responseBody,
110+
'cookies' => $cookies,
102111
];
103112
}
113+
114+
/**
115+
* Parse Cookie String
116+
*
117+
* @param string $cookie
118+
* @return array
119+
*/
120+
public function parseCookie(string $cookie): array
121+
{
122+
$cookies = [];
123+
124+
parse_str(strtr($cookie, ['&' => '%26', '+' => '%2B', ';' => '&']), $cookies);
125+
126+
return $cookies;
127+
}
128+
104129
}

tests/e2e/init.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@
3434
$response->send($request->getHeaders()['cookie'] ?? '');
3535
});
3636

37+
Http::get('/set-cookie')
38+
->inject('request')
39+
->inject('response')
40+
->action(function (Request $request, Response $response) {
41+
$response->addHeader('Set-Cookie', 'key1=value1');
42+
$response->addHeader('Set-Cookie', 'key2=value2');
43+
$response->send('OK');
44+
});
45+
3746
Http::get('/chunked')
3847
->inject('response')
3948
->action(function (Response $response) {

0 commit comments

Comments
 (0)