Skip to content

Commit b5c0c20

Browse files
authored
Optimize websocket tests (#5747)
* Optimize code * optimize websocket tests * optimize websocket tests --filter=[core]
1 parent 516fa16 commit b5c0c20

File tree

12 files changed

+190
-19
lines changed

12 files changed

+190
-19
lines changed

.github/workflows/core.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
- uses: actions/checkout@v4
3535

3636
- name: install dependencies
37-
run: sudo apt update -y && sudo apt install -y googletest libgtest-dev redis-server libboost-stacktrace-dev libbrotli-dev
37+
run: sudo apt update -y && sudo apt install -y googletest libgtest-dev redis-server libboost-stacktrace-dev libbrotli-dev nodejs npm
3838

3939
- name: configure
4040
run: phpize && ./configure --enable-sockets --enable-mysqlnd --enable-openssl

core-tests/include/test_core.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const std::string &get_root_path();
5555
std::string get_ssl_dir();
5656
std::string get_jpg_file();
5757
bool is_github_ci();
58-
58+
int exec_js_script(const std::string &file, const std::string &args);
5959
int get_random_port();
6060

6161
Socks5Proxy *create_socks5_proxy();

core-tests/js/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
package-lock.json

core-tests/js/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"dependencies": {
3+
"pino": "^6.14.0",
4+
"ws": "^8.18.2"
5+
}
6+
}

core-tests/js/ws_1.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const WebSocket = require('ws');
2+
const pino = require('pino');
3+
const port = process.argv[2];
4+
const logger = pino(pino.destination('/tmp/swoole.log'));
5+
6+
const ws_1 = new WebSocket(`ws://127.0.0.1:${port}/`);
7+
8+
function delay(ms) {
9+
return new Promise(resolve => setTimeout(resolve, ms));
10+
}
11+
12+
ws_1.on('error', console.error);
13+
14+
ws_1.on('close', function () {
15+
logger.info('the node websocket client is closed');
16+
})
17+
18+
ws_1.on('open', async () => {
19+
ws_1.send('hello', {fin: false});
20+
await delay(50);
21+
ws_1.send(' ', {fin: false});
22+
await delay(50);
23+
ws_1.send('world', {fin: true});
24+
25+
await delay(200);
26+
ws_1.ping("keep alive")
27+
28+
await delay(200);
29+
ws_1.close()
30+
});
31+
32+
ws_1.on('message', function message(data) {
33+
logger.info('received: ' + data);
34+
});

core-tests/js/ws_2.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const WebSocket = require('ws');
2+
const pino = require('pino');
3+
const port = process.argv[2];
4+
const logger = pino(pino.destination('/tmp/swoole.log'));
5+
6+
const ws = new WebSocket(`ws://127.0.0.1:${port}/ws/close`);
7+
8+
function delay(ms) {
9+
return new Promise(resolve => setTimeout(resolve, ms));
10+
}
11+
12+
ws.on('error', console.error);
13+
14+
ws.on('close', function (code, reason) {
15+
logger.info('the node websocket client is closed, code: ' + code + ', reason: ' + reason.toString());
16+
})
17+
18+
ws.on('open', async () => {
19+
});
20+
21+
ws.on('message', function message(data) {
22+
});

core-tests/run.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ fi
1010
make -j8
1111
ipcs -q
1212

13+
cd ${__DIR__}/js
14+
npm install
15+
cd ${__DIR__}
16+
1317
tasks=$(./bin/core_tests --gtest_list_tests | awk '/\./')
1418
for task in $tasks; do
1519

core-tests/src/main.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ bool is_github_ci() {
6262
return getenv("GITHUB_ACTIONS") != nullptr;
6363
}
6464

65+
int exec_js_script(const std::string &file, const std::string &args) {
66+
std::string command = "bash -c 'node " + test::get_root_path() + "/core-tests/js/" + file + " " + args + "'";
67+
return std::system(command.c_str());
68+
}
69+
6570
Socks5Proxy *create_socks5_proxy() {
6671
auto socks5_proxy = new Socks5Proxy();
6772
socks5_proxy->host = std::string(TEST_SOCKS5_PROXY_HOST);

core-tests/src/server/http.cpp

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
#include "swoole_http.h"
2727
#include "swoole_util.h"
2828

29+
#include <openssl/sha.h>
30+
#include <openssl/bio.h>
31+
#include <openssl/evp.h>
32+
#include <openssl/buffer.h>
33+
2934
using namespace swoole;
3035
using namespace std;
3136
using http_server::Context;
@@ -68,6 +73,41 @@ struct http_context {
6873
server->send(fd, buf->str, buf->length);
6974
delete buf;
7075
}
76+
77+
void dump_headers() {
78+
for (auto kv : headers) {
79+
std::cout << kv.first << ": " << kv.second << "\n";
80+
}
81+
}
82+
83+
static std::string base64Encode(const unsigned char *input, int length) {
84+
BIO *bmem = nullptr;
85+
BIO *b64 = nullptr;
86+
BUF_MEM *bptr = nullptr;
87+
88+
b64 = BIO_new(BIO_f_base64());
89+
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
90+
bmem = BIO_new(BIO_s_mem());
91+
b64 = BIO_push(b64, bmem);
92+
93+
BIO_write(b64, input, length);
94+
BIO_flush(b64);
95+
BIO_get_mem_ptr(b64, &bptr);
96+
97+
std::string encoded(bptr->data, bptr->length);
98+
BIO_free_all(b64);
99+
100+
return encoded;
101+
}
102+
103+
std::string createWebSocketAccept() {
104+
std::string keyWithMagic = headers["Sec-WebSocket-Key"] + SW_WEBSOCKET_GUID;
105+
106+
unsigned char sha1Result[SHA_DIGEST_LENGTH];
107+
SHA1(reinterpret_cast<const unsigned char *>(keyWithMagic.c_str()), keyWithMagic.length(), sha1Result);
108+
109+
return base64Encode(sha1Result, SHA_DIGEST_LENGTH);
110+
}
71111
};
72112

73113
static int handle_on_message_complete(llhttp_t *parser) {
@@ -139,9 +179,23 @@ static void test_base_server(function<void(Server *)> fn) {
139179

140180
if (conn->websocket_status == websocket::STATUS_ACTIVE) {
141181
sw_tg_buffer()->clear();
142-
std::string resp = "Swoole: " + string(req->data, req->info.len);
143-
websocket::encode(sw_tg_buffer(), resp.c_str(), resp.length(), websocket::OPCODE_TEXT, websocket::FLAG_FIN);
144-
serv->send(session_id, sw_tg_buffer()->str, sw_tg_buffer()->length);
182+
uchar flags = 0;
183+
uchar opcode = 0;
184+
websocket::parse_ext_flags(req->info.ext_flags, &opcode, &flags);
185+
186+
if (opcode == websocket::OPCODE_PING) {
187+
websocket::encode(
188+
sw_tg_buffer(), req->data, req->info.len, websocket::OPCODE_PONG, websocket::FLAG_FIN);
189+
serv->send(session_id, sw_tg_buffer()->str, sw_tg_buffer()->length);
190+
} else if (opcode == websocket::OPCODE_CLOSE) {
191+
// pass
192+
} else {
193+
std::string resp = "Swoole: " + string(req->data, req->info.len);
194+
websocket::encode(
195+
sw_tg_buffer(), resp.c_str(), resp.length(), websocket::OPCODE_TEXT, websocket::FLAG_FIN);
196+
serv->send(session_id, sw_tg_buffer()->str, sw_tg_buffer()->length);
197+
}
198+
145199
return SW_OK;
146200
}
147201

@@ -163,7 +217,7 @@ static void test_base_server(function<void(Server *)> fn) {
163217

164218
if (err == HPE_PAUSED_UPGRADE) {
165219
ctx.setHeader("Connection", "Upgrade");
166-
ctx.setHeader("Sec-WebSocket-Accept", "IIRiohCjop4iJrmvySrFcwcXpHo=");
220+
ctx.setHeader("Sec-WebSocket-Accept", ctx.createWebSocketAccept());
167221
ctx.setHeader("Sec-WebSocket-Version", "13");
168222
ctx.setHeader("Upgrade", "websocket");
169223
ctx.setHeader("Content-Length", "0");
@@ -172,6 +226,15 @@ static void test_base_server(function<void(Server *)> fn) {
172226

173227
conn->websocket_status = websocket::STATUS_ACTIVE;
174228

229+
if (ctx.url == "/ws/close") {
230+
swoole_timer_after(200, [serv](Timer *, TimerNode *) {
231+
sw_tg_buffer()->clear();
232+
websocket::pack_close_frame(
233+
sw_tg_buffer(), websocket::CLOSE_POLICY_ERROR, SW_STRL("swoole close"), 0);
234+
serv->send(session_id, sw_tg_buffer()->str, sw_tg_buffer()->length);
235+
});
236+
}
237+
175238
return SW_OK;
176239
}
177240

@@ -237,11 +300,10 @@ static Server *test_process_server(Server::DispatchMode dispatch_mode = Server::
237300
// printf("onClose\n");
238301
};
239302

240-
server->onConnect = [](Server *serv, DataHead *info) -> void {
303+
server->onConnect = [](Server *serv, DataHead *info) -> void {
241304
// printf("onConnect\n");
242305
};
243306

244-
245307
server->onReceive = [&](Server *serv, RecvData *req) -> int {
246308
session_id = req->info.fd;
247309
conn = serv->get_connection_by_session_id(session_id);
@@ -656,6 +718,39 @@ TEST(http_server, websocket_mask) {
656718
});
657719
}
658720

721+
TEST(http_server, node_websocket_client_1) {
722+
test_base_server([](Server *serv) {
723+
swoole_signal_block_all();
724+
725+
EXPECT_EQ(test::exec_js_script("ws_1.js", std::to_string(serv->get_primary_port()->get_port())), 0);
726+
727+
kill(serv->get_master_pid(), SIGTERM);
728+
});
729+
730+
File fp("/tmp/swoole.log", O_RDONLY);
731+
EXPECT_TRUE(fp.ready());
732+
auto str = fp.read_content();
733+
ASSERT_TRUE(str->contains("received: Swoole: hello world"));
734+
ASSERT_TRUE(str->contains("the node websocket client is closed"));
735+
unlink("/tmp/swoole.log");
736+
}
737+
738+
TEST(http_server, node_websocket_client_2) {
739+
test_base_server([](Server *serv) {
740+
swoole_signal_block_all();
741+
742+
EXPECT_EQ(test::exec_js_script("ws_2.js", std::to_string(serv->get_primary_port()->get_port())), 0);
743+
744+
kill(serv->get_master_pid(), SIGTERM);
745+
});
746+
747+
File fp("/tmp/swoole.log", O_RDONLY);
748+
EXPECT_TRUE(fp.ready());
749+
auto str = fp.read_content();
750+
ASSERT_TRUE(str->contains("the node websocket client is closed, code: 1008, reason: swoole close"));
751+
unlink("/tmp/swoole.log");
752+
}
753+
659754
TEST(http_server, parser1) {
660755
std::thread t;
661756
auto server = http_server::listen(":0", [](Context &ctx) {

ext-src/swoole_websocket_server.cc

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -505,21 +505,16 @@ static bool websocket_message_compress(String *buffer, const char *data, size_t
505505
int swoole_websocket_onMessage(Server *serv, RecvData *req) {
506506
SessionId fd = req->info.fd;
507507
uchar flags = 0;
508-
zend_long opcode = 0;
508+
uchar opcode = 0;
509509
auto port = serv->get_port_by_session_id(fd);
510510
if (!port) {
511511
return SW_ERR;
512512
}
513513

514514
zval zdata;
515-
char frame_header[2];
516-
memcpy(frame_header, &req->info.ext_flags, sizeof(frame_header));
517-
518515
php_swoole_get_recv_data(serv, &zdata, req);
519516

520-
// frame info has already decoded in websocket::dispatch_frame
521-
flags = frame_header[0];
522-
opcode = frame_header[1];
517+
WebSocket::parse_ext_flags(req->info.ext_flags, &opcode, &flags);
523518

524519
if ((opcode == WebSocket::OPCODE_CLOSE && !port->open_websocket_close_frame) ||
525520
(opcode == WebSocket::OPCODE_PING && !port->open_websocket_ping_frame) ||

0 commit comments

Comments
 (0)