Skip to content

Commit f9ff73f

Browse files
fix zlib mem level (#5961)
1 parent 7c66a1b commit f9ff73f

File tree

7 files changed

+171
-21
lines changed

7 files changed

+171
-21
lines changed

ext-src/php_swoole_http.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
#define SW_ZLIB_ENCODING_GZIP 0x1f
3535
#define SW_ZLIB_ENCODING_DEFLATE 0x0f
3636
#define SW_ZLIB_ENCODING_ANY 0x2f
37+
#if MAX_MEM_LEVEL >= 8
38+
#define SW_ZLIB_DEF_MEM_LEVEL 8
39+
#else
40+
#define SW_ZLIB_DEF_MEM_LEVEL MAX_MEM_LEVEL
41+
#endif
3742
#endif
3843

3944
#ifdef SW_HAVE_BROTLI
@@ -170,7 +175,7 @@ struct Context {
170175
std::shared_ptr<String> zlib_buffer;
171176
#endif
172177

173-
std::shared_ptr<String> frame_buffer;
178+
std::shared_ptr<String> continue_frame_buffer;
174179
WebSocketSettings websocket_settings;
175180

176181
Request request;

ext-src/php_swoole_websocket.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace swoole {
2828
namespace websocket {
2929
void apply_setting(WebSocketSettings &settings, zend_array *vht, bool in_server);
3030
void recv_frame(const WebSocketSettings &settings,
31-
std::shared_ptr<String> &frame_buffer,
31+
std::shared_ptr<String> &continue_frame_buffer,
3232
coroutine::Socket *sock,
3333
zval *return_value,
3434
double timeout);

ext-src/swoole_http_client_coro.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ class Client {
139139
* allowing access to the sent Request data even after the connection has been closed.
140140
*/
141141
String *tmp_write_buffer = nullptr;
142-
std::shared_ptr<String> frame_buffer;
142+
std::shared_ptr<String> continue_frame_buffer;
143143
bool connection_close = false;
144144
bool completed = false;
145145
bool event_stream = false;
@@ -1517,7 +1517,7 @@ bool Client::recv_response(double timeout) {
15171517
}
15181518

15191519
void Client::recv_websocket_frame(zval *return_value, double timeout) {
1520-
WebSocket::recv_frame(websocket_settings, frame_buffer, socket, return_value, timeout);
1520+
WebSocket::recv_frame(websocket_settings, continue_frame_buffer, socket, return_value, timeout);
15211521
if (ZVAL_IS_EMPTY_STRING(return_value)) {
15221522
close();
15231523
return;

ext-src/swoole_http_response.cc

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ zend_string *php_swoole_http_get_date() {
271271
}
272272

273273
static void http_response_set_date_header(String *response) {
274-
auto date_str = php_swoole_http_get_date();
274+
auto date_str = php_swoole_http_get_date();
275275
response->append(ZEND_STRL("Date: "));
276276
response->append(ZSTR_VAL(date_str), ZSTR_LEN(date_str));
277277
response->append(ZEND_STRL("\r\n"));
@@ -605,15 +605,16 @@ bool HttpContext::compress(const char *data, size_t length) {
605605
compression_level = Z_BEST_COMPRESSION;
606606
}
607607

608-
size_t memory_size = ((size_t) ((double) length * (double) 1.015)) + 10 + 8 + 4 + 1;
608+
size_t memory_size = ((size_t)((double) length * (double) 1.015)) + 10 + 8 + 4 + 1;
609609
zlib_buffer = std::make_shared<String>(memory_size);
610610

611611
z_stream zstream = {};
612612

613613
zstream.zalloc = php_zlib_alloc;
614614
zstream.zfree = php_zlib_free;
615615

616-
int status = deflateInit2(&zstream, compression_level, Z_DEFLATED, encoding, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
616+
int status =
617+
deflateInit2(&zstream, compression_level, Z_DEFLATED, encoding, SW_ZLIB_DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
617618
if (status != Z_OK) {
618619
swoole_warning("deflateInit2() failed, Error: [%d]", status);
619620
return false;
@@ -1302,10 +1303,10 @@ ssize_t WebSocket::send_frame(const swoole::WebSocketSettings &settings,
13021303
* return_value is false means socket.read() method returning -1.
13031304
* return_value is null means other error.
13041305
* return_value is empry string means socket is closed.
1305-
* the opcode is returned so the caller can decide when to release the frame_buffer.
1306+
* the opcode is returned so the caller can decide when to release the continue_frame_buffer.
13061307
*/
13071308
void WebSocket::recv_frame(const WebSocketSettings &settings,
1308-
std::shared_ptr<String> &frame_buffer,
1309+
std::shared_ptr<String> &continue_frame_buffer,
13091310
Socket *sock,
13101311
zval *return_value,
13111312
double timeout) {
@@ -1366,39 +1367,46 @@ void WebSocket::recv_frame(const WebSocketSettings &settings,
13661367
}
13671368

13681369
if (opcode == WebSocket::OPCODE_CONTINUATION) {
1369-
if (sw_unlikely(!frame_buffer)) {
1370+
if (sw_unlikely(!continue_frame_buffer)) {
13701371
swoole_warning("A continuation frame cannot stand alone and MUST be preceded by an initial frame whose "
13711372
"opcode indicates either text or binary data.");
13721373
RETURN_NULL();
13731374
}
13741375

13751376
if (sw_likely(frame.payload)) {
1376-
frame_buffer->append(frame.payload, frame.payload_length);
1377+
continue_frame_buffer->append(frame.payload, frame.payload_length);
13771378
}
13781379

13791380
if (frame.header.FIN) {
13801381
uchar complete_opcode = 0;
13811382
uchar complete_flags = 0;
1382-
WebSocket::parse_ext_flags(frame_buffer->offset, &complete_opcode, &complete_flags);
1383+
WebSocket::parse_ext_flags(continue_frame_buffer->offset, &complete_opcode, &complete_flags);
13831384

13841385
if (complete_flags & WebSocket::FLAG_RSV1) {
1385-
if (sw_unlikely(!FrameObject::uncompress(&zpayload, frame_buffer->str, frame_buffer->length))) {
1386+
if (sw_unlikely(!FrameObject::uncompress(
1387+
&zpayload, continue_frame_buffer->str, continue_frame_buffer->length))) {
1388+
continue_frame_buffer.reset();
13861389
swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR);
13871390
RETURN_NULL();
13881391
}
13891392
} else {
1390-
zend::assign_zend_string_by_val(&zpayload, frame_buffer->str, frame_buffer->length);
1393+
zend::assign_zend_string_by_val(
1394+
&zpayload, continue_frame_buffer->str, continue_frame_buffer->length);
13911395
Z_TRY_ADDREF(zpayload);
1392-
frame_buffer.reset();
13931396
}
13941397

13951398
WebSocket::construct_frame(return_value, complete_opcode, &zpayload, complete_flags);
13961399
zend::object_set(return_value, ZEND_STRL("fd"), sock->get_fd());
13971400
zval_ptr_dtor(&zpayload);
1401+
/**
1402+
* The final frame of the continuous frame sequence has been received,
1403+
* and the complete message has been assembled. Memory can be released immediately.
1404+
*/
1405+
continue_frame_buffer.reset();
13981406
return;
13991407
}
14001408
} else {
1401-
if (sw_unlikely(frame_buffer)) {
1409+
if (sw_unlikely(continue_frame_buffer)) {
14021410
swoole_warning("All fragments of a message, except for the initial frame, must use the continuation "
14031411
"frame opcode(0).");
14041412
RETURN_NULL();
@@ -1417,12 +1425,12 @@ void WebSocket::recv_frame(const WebSocketSettings &settings,
14171425
zval_ptr_dtor(&zpayload);
14181426
return;
14191427
} else {
1420-
frame_buffer = std::make_shared<String>(
1428+
continue_frame_buffer = std::make_shared<String>(
14211429
(frame.payload_length > 0 ? frame.payload_length : SW_WEBSOCKET_DEFAULT_BUFFER),
14221430
sw_zend_string_allocator());
1423-
frame_buffer->offset = WebSocket::get_ext_flags(frame.header.OPCODE, frame.get_flags());
1431+
continue_frame_buffer->offset = WebSocket::get_ext_flags(frame.header.OPCODE, frame.get_flags());
14241432
if (sw_likely(frame.payload)) {
1425-
frame_buffer->append(frame.payload, frame.payload_length);
1433+
continue_frame_buffer->append(frame.payload, frame.payload_length);
14261434
}
14271435
}
14281436
}
@@ -1447,7 +1455,8 @@ static PHP_METHOD(swoole_http_response, recv) {
14471455
Z_PARAM_DOUBLE(timeout)
14481456
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
14491457

1450-
WebSocket::recv_frame(ctx->websocket_settings, ctx->frame_buffer, ctx->get_co_socket(), return_value, timeout);
1458+
WebSocket::recv_frame(
1459+
ctx->websocket_settings, ctx->continue_frame_buffer, ctx->get_co_socket(), return_value, timeout);
14511460
if (ZVAL_IS_EMPTY_STRING(return_value)) {
14521461
ctx->close(ctx);
14531462
return;

ext-src/swoole_websocket_server.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ bool FrameObject::pack(String *buffer) {
155155
sw_set_bit(flags, WebSocket::FLAG_FIN);
156156
need_compress = false;
157157
} else if (opcode == WebSocket::OPCODE_CONTINUATION || !(flags & WebSocket::FLAG_FIN)) {
158+
// Continuous frames and WebSocket message frames without the FLAG_FIN flag do not require compression.
158159
need_compress = false;
159160
}
160161

@@ -408,7 +409,7 @@ bool WebSocket::message_compress(String *buffer, const char *data, size_t length
408409
zstream.zalloc = php_zlib_alloc;
409410
zstream.zfree = php_zlib_free;
410411

411-
status = deflateInit2(&zstream, level, Z_DEFLATED, SW_ZLIB_ENCODING_RAW, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
412+
status = deflateInit2(&zstream, level, Z_DEFLATED, SW_ZLIB_ENCODING_RAW, SW_ZLIB_DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
412413
if (status != Z_OK) {
413414
php_swoole_fatal_error(E_WARNING, "deflateInit2() failed, Error: [%d]", status);
414415
return false;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
--TEST--
2+
swoole_http_client_coro/websocket: send more continue frames - websocket server
3+
--SKIPIF--
4+
<?php require __DIR__ . '/../../include/skipif.inc';?>
5+
--FILE--
6+
<?php
7+
require __DIR__ . '/../../include/bootstrap.php';
8+
use Swoole\WebSocket\Server;
9+
use Swoole\Coroutine\Http\Client;
10+
use Swoole\WebSocket\Frame;
11+
use SwooleTest\ProcessManager as ProcessManager;
12+
13+
$data1 = bin2hex(random_bytes(10 * 1024));
14+
$data2 = bin2hex(random_bytes(20 * 2048));
15+
$data3 = bin2hex(random_bytes(40 * 4096));
16+
17+
$pm = new ProcessManager;
18+
$pm->parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) {
19+
Co\run(function () use ($pm, $data1, $data2, $data3) {
20+
$results = [];
21+
$client = new Client('127.0.0.1', $pm->getFreePort());
22+
$client->set(['websocket_compression' => true]);
23+
$ret = $client->upgrade('/');
24+
Assert::assert($ret);
25+
$client->push('111', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN);
26+
$frame = $client->recv();
27+
Assert::true($frame->data == $data1 . $data2 . $data2 . $data2 . $data3);
28+
$frame = $client->recv();
29+
Assert::true($frame->data == $data2 . $data1 . $data3);
30+
$frame = $client->recv();
31+
Assert::true($frame->data == $data3 . $data2 . $data1);
32+
});
33+
$pm->kill();
34+
};
35+
36+
$pm->childFunc = function () use ($pm, $data1, $data2, $data3) {
37+
$server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE);
38+
$server->set([
39+
'worker_num' => 1,
40+
'package_max_length' => 100 * 1024 * 1024,
41+
'websocket_compression' => true
42+
]);
43+
44+
$server->on('workerStart', function () use ($pm) {
45+
$pm->wakeup();
46+
});
47+
48+
$server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) {
49+
$context = deflate_init(ZLIB_ENCODING_RAW);
50+
$server->push($frame->fd, deflate_add($context, $data1, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
51+
$server->push($frame->fd, deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
52+
$server->push($frame->fd, deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
53+
$server->push($frame->fd, deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
54+
$server->push($frame->fd, deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
55+
56+
$server->push($frame->fd, deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
57+
$server->push($frame->fd, deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
58+
$server->push($frame->fd, deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
59+
60+
$server->push($frame->fd, deflate_add($context, $data3, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
61+
$server->push($frame->fd, deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
62+
$server->push($frame->fd, deflate_add($context, $data1, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
63+
});
64+
65+
$server->start();
66+
};
67+
$pm->childFirst();
68+
$pm->run();
69+
?>
70+
--EXPECT--
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
--TEST--
2+
swoole_http_client_coro/websocket: send more continue frames - coroutine websocket server
3+
--SKIPIF--
4+
<?php require __DIR__ . '/../../include/skipif.inc';?>
5+
--FILE--
6+
<?php
7+
require __DIR__ . '/../../include/bootstrap.php';
8+
use Swoole\Coroutine\Http\Server;
9+
use Swoole\Coroutine\Http\Client;
10+
use Swoole\WebSocket\Frame;
11+
use SwooleTest\ProcessManager as ProcessManager;
12+
13+
$data1 = bin2hex(random_bytes(10 * 1024));
14+
$data2 = bin2hex(random_bytes(20 * 2048));
15+
$data3 = bin2hex(random_bytes(40 * 4096));
16+
17+
$pm = new ProcessManager;
18+
$pm->parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) {
19+
Co\run(function () use ($pm, $data1, $data2, $data3) {
20+
$results = [];
21+
$client = new Client('127.0.0.1', $pm->getFreePort());
22+
$client->set(['websocket_compression' => true]);
23+
$ret = $client->upgrade('/');
24+
Assert::assert($ret);
25+
$client->push('111', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN);
26+
$frame = $client->recv();
27+
Assert::true($frame->data == $data1 . $data2 . $data2 . $data2 . $data3);
28+
$frame = $client->recv();
29+
Assert::true($frame->data == $data2 . $data1 . $data3);
30+
$frame = $client->recv();
31+
Assert::true($frame->data == $data3 . $data2 . $data1);
32+
});
33+
$pm->kill();
34+
};
35+
36+
$pm->childFunc = function () use ($pm, $data1, $data2, $data3) {
37+
Co\run(function () use ($pm, $data1, $data2, $data3) {
38+
$server = new Server("127.0.0.1", $pm->getFreePort(), false);
39+
$server->set(['websocket_compression' => true]);
40+
$server->handle('/', function ($request, $response) use ($data1, $data2, $data3) {
41+
$response->upgrade();
42+
$frame = $response->recv();
43+
$context = deflate_init(ZLIB_ENCODING_RAW);
44+
$response->push(deflate_add($context, $data1, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
45+
$response->push(deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
46+
$response->push(deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
47+
$response->push(deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
48+
$response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
49+
50+
$response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
51+
$response->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
52+
$response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
53+
54+
$response->push(deflate_add($context, $data3, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
55+
$response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
56+
$response->push(deflate_add($context, $data1, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
57+
});
58+
$pm->wakeup();
59+
$server->start();
60+
});
61+
};
62+
$pm->childFirst();
63+
$pm->run();
64+
?>
65+
--EXPECT--

0 commit comments

Comments
 (0)