Skip to content

Commit

Permalink
feat: add http 1.1 trailer (#3485)
Browse files Browse the repository at this point in the history
  • Loading branch information
huanghantao authored Jul 21, 2020
1 parent 789c60f commit 9eb89bd
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 8 deletions.
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

0 comments on commit 9eb89bd

Please sign in to comment.