From 3cdb86347a4af0a3d6223602ebeb7420f343d80c Mon Sep 17 00:00:00 2001 From: MARiA so cute <33935209+NathanFreeman@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:22:19 +0800 Subject: [PATCH] support zstd (#5599) --- config.m4 | 12 +++++ ext-src/php_swoole.cc | 10 ++++ ext-src/php_swoole_http.h | 10 ++++ ext-src/swoole_http_client_coro.cc | 72 +++++++++++++++++++++++---- ext-src/swoole_http_request.cc | 18 ++++--- ext-src/swoole_http_response.cc | 33 ++++++++---- package.xml | 1 + scripts/docker-compile-with-thread.sh | 2 + scripts/docker-compile.sh | 2 + scripts/library.sh | 2 +- tests/swoole_http_server/zstd.phpt | 48 ++++++++++++++++++ 11 files changed, 181 insertions(+), 29 deletions(-) create mode 100644 tests/swoole_http_server/zstd.phpt diff --git a/config.m4 b/config.m4 index 25420f7fca8..ef61105a89b 100644 --- a/config.m4 +++ b/config.m4 @@ -71,6 +71,11 @@ PHP_ARG_WITH([brotli_dir], [AS_HELP_STRING([[--with-brotli-dir[=DIR]]], [Include Brotli support])], [no], [no]) +PHP_ARG_ENABLE([zstd], + [enable zstd support], + [AS_HELP_STRING([[--enable-zstd]], + [Use zstd])], [no], [no]) + PHP_ARG_WITH([nghttp2_dir], [dir of nghttp2], [AS_HELP_STRING([[--with-nghttp2-dir[=DIR]]], @@ -873,6 +878,13 @@ EOF fi fi + if test "$PHP_ZSTD" = "yes"; then + PKG_CHECK_MODULES([ZSTD], [libzstd >= 1.4.0]) + AC_DEFINE(SW_HAVE_ZSTD, 1, [have zstd]) + PHP_EVAL_LIBLINE($ZSTD_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($ZSTD_CFLAGS) + fi + PHP_ADD_LIBRARY(pthread) PHP_SUBST(SWOOLE_SHARED_LIBADD) diff --git a/ext-src/php_swoole.cc b/ext-src/php_swoole.cc index 10f253535d0..80f5d3b3c23 100644 --- a/ext-src/php_swoole.cc +++ b/ext-src/php_swoole.cc @@ -54,6 +54,9 @@ END_EXTERN_C() #include #include #endif +#ifdef SW_HAVE_ZSTD +#include +#endif #ifdef SW_USE_CARES #include @@ -928,6 +931,13 @@ PHP_MINFO_FUNCTION(swoole) { snprintf(buf, sizeof(buf), "E%u/D%u", BrotliEncoderVersion(), BrotliDecoderVersion()); php_info_print_table_row(2, "brotli", buf); #endif +#ifdef SW_HAVE_ZSTD +#ifdef ZSTD_VERSION_NUMBER + php_info_print_table_row(2, "zstd", ZSTD_VERSION_STRING); +#else + php_info_print_table_row(2, "zstd", "enabled"); +#endif +#endif #ifdef HAVE_MUTEX_TIMEDLOCK php_info_print_table_row(2, "mutex_timedlock", "enabled"); #endif diff --git a/ext-src/php_swoole_http.h b/ext-src/php_swoole_http.h index 91754ed2d72..bf378da0323 100644 --- a/ext-src/php_swoole_http.h +++ b/ext-src/php_swoole_http.h @@ -34,6 +34,15 @@ #define SW_ZLIB_ENCODING_ANY 0x2f #endif +#ifdef SW_HAVE_BROTLI +#include +#include +#endif + +#ifdef SW_HAVE_ZSTD +#include +#endif + #include enum swHttpHeaderFlag { @@ -52,6 +61,7 @@ enum swHttpCompressMethod { HTTP_COMPRESS_GZIP, HTTP_COMPRESS_DEFLATE, HTTP_COMPRESS_BR, + HTTP_COMPRESS_ZSTD, }; namespace swoole { diff --git a/ext-src/swoole_http_client_coro.cc b/ext-src/swoole_http_client_coro.cc index 19cfce895f8..2a80811b206 100644 --- a/ext-src/swoole_http_client_coro.cc +++ b/ext-src/swoole_http_client_coro.cc @@ -32,21 +32,11 @@ SW_EXTERN_C_BEGIN #include "thirdparty/swoole_http_parser.h" +#include "stubs/php_swoole_http_client_coro_arginfo.h" #include "ext/standard/base64.h" -#ifdef SW_HAVE_ZLIB -#include -#endif - -#include "stubs/php_swoole_http_client_coro_arginfo.h" - SW_EXTERN_C_END - -#ifdef SW_HAVE_BROTLI -#include -#endif - using swoole::File; using swoole::String; using swoole::coroutine::Socket; @@ -156,6 +146,9 @@ class Client { #endif #ifdef SW_HAVE_BROTLI BrotliDecoderState *brotli_decoder_state = nullptr; +#endif +#ifdef SW_HAVE_ZSTD + ZSTD_DStream *zstd_stream = nullptr; #endif bool bind(std::string address, int port = 0); bool connect(); @@ -456,6 +449,11 @@ static int http_parser_on_header_value(swoole_http_parser *parser, const char *a } else if (SW_STR_ISTARTS_WITH(at, length, "deflate")) { http->compress_method = HTTP_COMPRESS_DEFLATE; } +#endif +#ifdef SW_HAVE_ZSTD + else if (SW_STR_ISTARTS_WITH(at, length, "zstd")) { + http->compress_method = HTTP_COMPRESS_ZSTD; + } #endif } #endif @@ -689,6 +687,48 @@ bool Client::decompress_response(const char *in, size_t in_len) { body->length = reserved_body_length; return false; } +#endif +#ifdef SW_HAVE_ZSTD + case HTTP_COMPRESS_ZSTD: { + size_t zstd_result = 0; + if (zstd_stream == nullptr) { + zstd_stream = ZSTD_createDStream(); + if (!zstd_stream) { + swoole_warning("ZSTD_createDStream() failed, can not create ZSTD stream"); + return false; + } + + zstd_result = ZSTD_initDStream(zstd_stream); + if (ZSTD_isError(zstd_result)) { + swoole_warning("ZSTD_initDStream() failed, Error: [%s]", ZSTD_getErrorName(zstd_result)); + return false; + } + } + + size_t recommended_size = ZSTD_DStreamOutSize(); + ZSTD_inBuffer in_buffer = {in, in_len, 0}; + ZSTD_outBuffer out_buffer = {body->str + body->length, body->size - body->length, 0}; + while (in_buffer.pos < in_buffer.size) { + if (sw_unlikely(out_buffer.pos == out_buffer.size)) { + if (!body->extend(recommended_size + body->size)) { + swoole_warning("ZSTD_decompressStream() failed, no memory is available"); + return false; + } + + body->length += out_buffer.pos; + out_buffer = {body->str + body->length, body->size - body->length, 0}; + } + + zstd_result = ZSTD_decompressStream(zstd_stream, &out_buffer, &in_buffer); + if (ZSTD_isError(zstd_result)) { + swoole_warning("ZSTD_decompressStream() failed, Error: [%s]", ZSTD_getErrorName(zstd_result)); + return false; + } + } + + body->length += out_buffer.pos; + return true; + } #endif default: break; @@ -1112,6 +1152,10 @@ bool Client::send_request() { #else #ifdef SW_HAVE_BROTLI ZEND_STRL("br") +#else +#ifdef SW_HAVE_ZSTD + ZEND_STRL("zstd") +#endif #endif #endif #endif @@ -1627,6 +1671,12 @@ void Client::reset() { BrotliDecoderDestroyInstance(brotli_decoder_state); brotli_decoder_state = nullptr; } +#endif +#ifdef SW_HAVE_ZSTD + if (zstd_stream) { + ZSTD_freeDStream(zstd_stream); + zstd_stream = nullptr; + } #endif if (has_upload_files) { zend_update_property_null(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("uploadFiles")); diff --git a/ext-src/swoole_http_request.cc b/ext-src/swoole_http_request.cc index c543c5b9d04..56da5845a96 100644 --- a/ext-src/swoole_http_request.cc +++ b/ext-src/swoole_http_request.cc @@ -22,14 +22,6 @@ SW_EXTERN_C_BEGIN #include "thirdparty/php/main/SAPI.h" SW_EXTERN_C_END -#ifdef SW_HAVE_ZLIB -#include -#endif - -#ifdef SW_HAVE_BROTLI -#include -#endif - enum http_upload_errno { HTTP_UPLOAD_ERR_OK = 0, HTTP_UPLOAD_ERR_INI_SIZE, @@ -833,6 +825,11 @@ void HttpContext::set_compression_method(const char *accept_encoding, size_t len } else if (swoole_strnpos(accept_encoding, length, ZEND_STRL("deflate")) >= 0) { accept_compression = 1; compression_method = HTTP_COMPRESS_DEFLATE; +#ifdef SW_HAVE_ZSTD + } else if (swoole_strnpos(accept_encoding, length, ZEND_STRL("zstd")) >= 0) { + accept_compression = 1; + compression_method = HTTP_COMPRESS_ZSTD; +#endif } else { accept_compression = 0; } @@ -848,6 +845,11 @@ const char *HttpContext::get_content_encoding() { else if (compression_method == HTTP_COMPRESS_BR) { return "br"; } +#endif +#ifdef SW_HAVE_ZSTD + else if (compression_method == HTTP_COMPRESS_ZSTD) { + return "zstd"; + } #endif else { return nullptr; diff --git a/ext-src/swoole_http_response.cc b/ext-src/swoole_http_response.cc index 493652b822d..ec05b9d61a3 100644 --- a/ext-src/swoole_http_response.cc +++ b/ext-src/swoole_http_response.cc @@ -15,17 +15,8 @@ */ #include "php_swoole_http_server.h" - #include "swoole_util.h" -#ifdef SW_HAVE_ZLIB -#include -#endif - -#ifdef SW_HAVE_BROTLI -#include -#endif - BEGIN_EXTERN_C() #include "stubs/php_swoole_http_response_arginfo.h" END_EXTERN_C() @@ -565,6 +556,30 @@ bool HttpContext::compress(const char *data, size_t length) { return true; } } +#endif +#ifdef SW_HAVE_ZSTD + else if (compression_method == HTTP_COMPRESS_ZSTD) { + int zstd_compress_level = compression_level; + int zstd_max_level = ZSTD_maxCLevel(); + int zstd_min_level = ZSTD_minCLevel(); + zstd_compress_level = (zstd_compress_level > zstd_max_level) + ? zstd_max_level + : (zstd_compress_level < zstd_min_level ? zstd_min_level : zstd_compress_level); + + size_t compress_size = ZSTD_compressBound(length); + zlib_buffer = std::make_shared(compress_size);; + size_t zstd_compress_result = + ZSTD_compress((void *) zlib_buffer->str, compress_size, (void *) data, length, zstd_compress_level); + + if (ZSTD_isError(zstd_compress_result)) { + swoole_warning("ZSTD_compress() failed, Error: [%s]", ZSTD_getErrorName(zstd_compress_result)); + return false; + } + + zlib_buffer->length = zstd_compress_result; + content_compressed = 1; + return true; + } #endif else { swoole_warning("Unknown compression method"); diff --git a/package.xml b/package.xml index b7eb5b7242f..b4ff8eeb475 100644 --- a/package.xml +++ b/package.xml @@ -2652,6 +2652,7 @@ + diff --git a/scripts/docker-compile-with-thread.sh b/scripts/docker-compile-with-thread.sh index 99c32bc48b3..4f0f5a6c6e4 100755 --- a/scripts/docker-compile-with-thread.sh +++ b/scripts/docker-compile-with-thread.sh @@ -13,6 +13,8 @@ cd "${__DIR__}" && cd .. ./scripts/clear.sh phpize ./configure \ +--enable-brotli \ +--enable-zstd \ --enable-openssl \ --enable-sockets \ --enable-mysqlnd \ diff --git a/scripts/docker-compile.sh b/scripts/docker-compile.sh index a7141baf0aa..d3f5f367f6a 100755 --- a/scripts/docker-compile.sh +++ b/scripts/docker-compile.sh @@ -13,6 +13,8 @@ cd "${__DIR__}" && cd .. ./scripts/clear.sh phpize ./configure \ +--enable-brotli \ +--enable-zstd \ --enable-openssl \ --enable-sockets \ --enable-mysqlnd \ diff --git a/scripts/library.sh b/scripts/library.sh index fe85638c640..12d01138aba 100755 --- a/scripts/library.sh +++ b/scripts/library.sh @@ -1,6 +1,6 @@ #!/bin/sh -e apt update -apt install -y libaio-dev libaio1 sqlite3 libsqlite3-dev unixodbc unixodbc-dev odbc-mariadb +apt install -y libaio-dev libaio1 sqlite3 libsqlite3-dev unixodbc unixodbc-dev odbc-mariadb libzstd-dev wget -nv https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip unzip instantclient-basiclite-linuxx64.zip && rm instantclient-basiclite-linuxx64.zip wget -nv https://download.oracle.com/otn_software/linux/instantclient/instantclient-sdk-linuxx64.zip diff --git a/tests/swoole_http_server/zstd.phpt b/tests/swoole_http_server/zstd.phpt new file mode 100644 index 00000000000..79d9d1753c7 --- /dev/null +++ b/tests/swoole_http_server/zstd.phpt @@ -0,0 +1,48 @@ +--TEST-- +swoole_http_server: support zstd compress +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm, $data) { + run(function () use ($pm, $data) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->setHeaders(['Accept-Encoding' => 'zstd']); + $client->get('/'); + Assert::true($client->body == $data); + Assert::true($client->headers['content-encoding'] == 'zstd'); + Assert::true($client->headers['content-length'] != strlen($client->body)); + }); + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data) { + $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort()); + $serv->set([ + 'compression_level' => 20 + ]); + $serv->on("workerStart", function ($serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('request', function ($req, $resp) use ($data) { + $resp->end($data); + }); + $serv->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE