Skip to content

Commit

Permalink
HTTP client supports receiving duplicate headers, automatically mergi…
Browse files Browse the repository at this point in the history
…ng them into an array.
  • Loading branch information
matyhtf committed Aug 27, 2024
1 parent 6a32be4 commit f4df7c9
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 3 deletions.
32 changes: 29 additions & 3 deletions ext-src/swoole_http_client_coro.cc
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ class Client {
bool exec(std::string _path);
bool recv_response(double timeout = 0);
bool recv_websocket_frame(zval *zframe, double timeout = 0);
void add_header(const char *key, size_t key_len, const char *str, size_t length);
bool upgrade(std::string path);
bool push(zval *zdata, zend_long opcode = websocket::OPCODE_TEXT, uint8_t flags = websocket::FLAG_FIN);
bool close(const bool should_be_reset = true);
Expand Down Expand Up @@ -406,8 +407,7 @@ static int http_parser_on_header_field(swoole_http_parser *parser, const char *a
static int http_parser_on_header_value(swoole_http_parser *parser, const char *at, size_t length) {
Client *http = (Client *) parser->data;
zval *zobject = (zval *) http->zobject;
zval *zheaders =
sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("headers"), 0);

char *header_name = http->tmp_header_field_name;
size_t header_len = http->tmp_header_field_name_len;
zend::CharPtr _header_name;
Expand All @@ -417,7 +417,7 @@ static int http_parser_on_header_value(swoole_http_parser *parser, const char *a
header_name = _header_name.get();
}

add_assoc_stringl_ex(zheaders, header_name, header_len, (char *) at, length);
http->add_header(header_name, header_len, (char *) at, length);

if (parser->status_code == SW_HTTP_SWITCHING_PROTOCOLS && SW_STREQ(header_name, header_len, "upgrade")) {
if (swoole_http_token_list_contains_value(at, length, "websocket")) {
Expand Down Expand Up @@ -768,6 +768,32 @@ void Client::set_basic_auth(const std::string &username, const std::string &pass
}
}

void Client::add_header(const char *key, size_t key_len, const char *str, size_t length) {
zval *zheaders =
sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("headers"), 0);
zend_array *array = Z_ARRVAL_P(zheaders);

zval zvalue_new;
ZVAL_STRINGL(&zvalue_new, str, length);
zval *zresult = zend_hash_str_add(array, key, key_len, &zvalue_new);
/**
* Adding failed, indicating that this header already exists and must be converted to an array
*/
if (!zresult) {
zval *zvalue_found = zend_hash_str_find(array, key, key_len);
if (ZVAL_IS_ARRAY(zvalue_found)) {
add_next_index_zval(zvalue_found, &zvalue_new);
} else {
zval zvalue_array;
array_init_size(&zvalue_array, 2);
Z_ADDREF_P(zvalue_found);
add_next_index_zval(&zvalue_array, zvalue_found);
add_next_index_zval(&zvalue_array, &zvalue_new);
zend_hash_str_update(array, key, key_len, &zvalue_array);
}
}
}

bool Client::connect() {
if (socket) {
return true;
Expand Down
38 changes: 38 additions & 0 deletions tests/swoole_http_client_coro/duplicate_header.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
swoole_http_server: duplicate header
--SKIPIF--
<?php require __DIR__ . '/../include/skipif.inc'; ?>
--FILE--
<?php
require __DIR__ . '/../include/bootstrap.php';

$uuid = uniqid();
$pm = new ProcessManager;
$pm->parentFunc = function () use ($pm, $uuid) {
Co\run(function () use ($pm, $uuid) {
$client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort());
Assert::true($client->get('/'));
Assert::eq($client->headers['values-1'], ['hello', 'swoole', $uuid]);
Assert::eq($client->headers['values-2'], ['hello', $uuid]);
$pm->kill();
});
echo "DONE\n";
};
$pm->childFunc = function () use ($pm, $uuid) {
$http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP);
$http->on("WorkerStart", function ($serv, $wid) {
global $pm;
$pm->wakeup();
});
$http->on("request", function ($request, Swoole\Http\Response $response) use ($uuid) {
$response->header('values-1', ['hello', 'swoole', $uuid]);
$response->header('values-2', ['hello', $uuid]);
$response->end('OK');
});
$http->start();
};
$pm->childFirst();
$pm->run();
?>
--EXPECT--
DONE

0 comments on commit f4df7c9

Please sign in to comment.