Skip to content
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

feat: add http 1.1 trailer #3485

Merged
merged 1 commit into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions swoole_http.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ struct http_context
#endif
uchar send_chunked :1;
uchar recv_chunked :1;
uchar send_trailer :1;
uchar keepalive :1;
uchar websocket :1;
#ifdef SW_HAVE_ZLIB
Expand Down Expand Up @@ -254,6 +255,7 @@ size_t swoole_http_requset_parse(http_context *ctx, const char *data, size_t len

bool swoole_http_response_set_header(http_context *ctx, const char *k, size_t klen, const char *v, size_t vlen, bool ucwords);
void swoole_http_response_end(http_context *ctx, zval *zdata, zval *return_value);
void swoole_http_response_send_trailer(http_context *ctx, zval *return_value);

#ifdef SW_HAVE_COMPRESSION
int swoole_http_response_compress(const char *data, size_t length, int method, int level);
Expand Down
74 changes: 66 additions & 8 deletions swoole_http_response.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ using swoole::coroutine::Socket;
zend_class_entry *swoole_http_response_ce;
static zend_object_handlers swoole_http_response_handlers;

static void http_build_header(http_context *, swString *response, size_t body_length);
static void http_build_header(http_context *ctx, swString *response, size_t body_length);
static ssize_t http_build_trailer(http_context *ctx, swString *response);

static inline void http_header_key_format(char *key, int length) {
int i, state = 0;
Expand Down Expand Up @@ -279,9 +280,7 @@ void php_swoole_http_response_minit(int module_number) {
zend_declare_property_null(swoole_http_response_ce, ZEND_STRL("socket"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_response_ce, ZEND_STRL("header"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_response_ce, ZEND_STRL("cookie"), ZEND_ACC_PUBLIC);
#ifdef SW_USE_HTTP2
zend_declare_property_null(swoole_http_response_ce, ZEND_STRL("trailer"), ZEND_ACC_PUBLIC);
#endif
}

static PHP_METHOD(swoole_http_response, write) {
Expand Down Expand Up @@ -483,6 +482,43 @@ static void http_build_header(http_context *ctx, swString *response, size_t body
ctx->send_header = 1;
}

static ssize_t http_build_trailer(http_context *ctx, swString *response) {
char *buf = SwooleTG.buffer_stack->str;
size_t l_buf = SwooleTG.buffer_stack->size;
int n;
ssize_t ret = 0;

zval *ztrailer =
sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0);
uint32_t size = php_swoole_array_length_safe(ztrailer);

if (size > 0) {
const char *key;
uint32_t keylen;
int type;
zval *zvalue;

SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(ztrailer), key, keylen, type, zvalue) {
if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) {
continue;
}

if (!ZVAL_IS_NULL(zvalue)) {
zend::String str_value(zvalue);
n = sw_snprintf(
buf, l_buf, "%.*s: %.*s\r\n", (int) keylen, key, (int) str_value.len(), str_value.val());
swString_append_ptr(response, buf, n);
ret += n;
}
}
SW_HASHTABLE_FOREACH_END();
(void) type;
swString_append_ptr(response, ZEND_STRL("\r\n"));
}

return ret;
}

#ifdef SW_HAVE_ZLIB
voidpf php_zlib_alloc(voidpf opaque, uInt items, uInt size) {
return (voidpf) safe_emalloc(items, size, 0);
Expand Down Expand Up @@ -616,10 +652,8 @@ static PHP_METHOD(swoole_http_response, initHeader) {
swoole_http_response_ce, zresponse_object, &ctx->response.zheader, ZEND_STRL("header"));
swoole_http_init_and_read_property(
swoole_http_response_ce, zresponse_object, &ctx->response.zcookie, ZEND_STRL("cookie"));
#ifdef SW_USE_HTTP2
swoole_http_init_and_read_property(
swoole_http_response_ce, zresponse_object, &ctx->response.ztrailer, ZEND_STRL("trailer"));
#endif
RETURN_TRUE;
}

Expand All @@ -646,6 +680,20 @@ static PHP_METHOD(swoole_http_response, end) {
}
}

void swoole_http_response_send_trailer(http_context *ctx, zval *return_value) {
swString *http_buffer = http_get_write_buffer(ctx);

swString_clear(http_buffer);
if (http_build_trailer(ctx, http_buffer) == 0) {
return;
}
if (!ctx->send(ctx, http_buffer->str, http_buffer->length)) {
ctx->end = 1;
ctx->close(ctx);
RETURN_FALSE;
}
}

void swoole_http_response_end(http_context *ctx, zval *zdata, zval *return_value) {
struct {
char *str;
Expand All @@ -659,8 +707,17 @@ void swoole_http_response_end(http_context *ctx, zval *zdata, zval *return_value
}

if (ctx->send_chunked) {
if (!ctx->send(ctx, ZEND_STRL("0\r\n\r\n"))) {
RETURN_FALSE;
if (ctx->send_trailer) {
if (!ctx->send(ctx, ZEND_STRL("0\r\n"))) {
RETURN_FALSE;
}
swoole_http_response_send_trailer(ctx, return_value);
ctx->send_trailer = 0;
}
else {
if (!ctx->send(ctx, ZEND_STRL("0\r\n\r\n"))) {
RETURN_FALSE;
}
}
ctx->send_chunked = 0;
}
Expand Down Expand Up @@ -1020,7 +1077,7 @@ static PHP_METHOD(swoole_http_response, trailer) {
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);

http_context *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS);
if (!ctx || !ctx->http2) {
if (!ctx) {
RETURN_FALSE;
}
if (UNEXPECTED(klen > SW_HTTP_HEADER_KEY_SIZE - 1)) {
Expand All @@ -1036,6 +1093,7 @@ static PHP_METHOD(swoole_http_response, trailer) {
} else {
add_assoc_stringl_ex(ztrailer, key_buf, klen, v, vlen);
}
ctx->send_trailer = 1;
RETURN_TRUE;
}

Expand Down
43 changes: 43 additions & 0 deletions tests/swoole_http_server/trailer.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
swoole_http_server: trailer
--SKIPIF--
<?php require __DIR__ . '/../include/skipif.inc'; ?>
--FILE--
<?php
require __DIR__ . '/../include/bootstrap.php';
$pm = new SwooleTest\ProcessManager;
$pm->parentFunc = function () use ($pm) {
Swoole\Coroutine\run(function () use ($pm) {
$cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort());
$cli->get('/');
Assert::eq(md5('hello world'), $cli->headers['content-md5']);
$pm->kill();
echo "DONE\n";
});
};
$pm->childFunc = function () use ($pm) {
$http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort());

$http->set([
'worker_num' => 1,
'log_file' => '/dev/null'
]);

$http->on('workerStart', function () use ($pm) {
$pm->wakeup();
});

$http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) {
$response->header('trailer', 'Content-MD5');
$data = 'hello world';
$response->write($data);
$response->trailer('Content-MD5', md5($data));
$response->end();
});
$http->start();
};
$pm->childFirst();
$pm->run();
?>
--EXPECT--
DONE