Add WebSocket support and enhance chat functionality
- Implement WebSocket handling in the FastCGI server, allowing for real-time communication. - Introduce functions for managing WebSocket connections, broadcasting messages, and sending to specific connections. - Create a chat interface in the `websockets.ws.uce` file, including message handling and user notifications. - Refactor existing code to accommodate WebSocket integration, ensuring compatibility with HTTP requests. - Update documentation to reflect new features and usage instructions for WebSocket functionality.
This commit is contained in:
+29
-1
@@ -202,6 +202,7 @@ void load_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
//setup_unit_paths(context, su, file_name);
|
||||
|
||||
su->on_render = 0;
|
||||
su->on_websocket = 0;
|
||||
su->on_setup = 0;
|
||||
su->compiler_messages = "";
|
||||
|
||||
@@ -223,6 +224,9 @@ void load_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
if ((error = dlerror()) != NULL)
|
||||
printf("Error - %s in %s\n", error, su->file_name.c_str());
|
||||
su->on_render = (call_handler)dlsym(su->so_handle, "render");
|
||||
dlerror();
|
||||
su->on_websocket = (call_handler)dlsym(su->so_handle, "websocket");
|
||||
dlerror();
|
||||
su->api_declarations = split(file_get_contents(su->api_file_name), "\n");
|
||||
//else
|
||||
// printf("(i) loaded unit %s\n", su->file_name.c_str());
|
||||
@@ -403,6 +407,31 @@ void compiler_invoke(Request* context, String file_name, DTree& call_param)
|
||||
}
|
||||
}
|
||||
|
||||
void compiler_invoke_websocket(Request* context, String file_name, DTree& call_param)
|
||||
{
|
||||
auto su = compiler_load_shared_unit(context, file_name, "", false);
|
||||
if(!su)
|
||||
return;
|
||||
|
||||
if(!su->on_setup)
|
||||
{
|
||||
printf("internal error: set_current_request() not defined in %s\n", file_name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if(!su->on_websocket)
|
||||
{
|
||||
printf("no WS() entry point in %s\n", file_name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
String prev_wd = get_cwd();
|
||||
set_cwd(su->src_path);
|
||||
su->on_setup(context);
|
||||
su->on_websocket(call_param);
|
||||
set_cwd(prev_wd);
|
||||
}
|
||||
|
||||
void render_file(String file_name)
|
||||
{
|
||||
//printf("(i) render_file(%s)\n", file_name.c_str());
|
||||
@@ -461,4 +490,3 @@ DTree* call_file(String file_name, String function_name, DTree* call_param)
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#define RENDER() extern "C" void render(DTree& call)
|
||||
#define WS() extern "C" void websocket(DTree& call)
|
||||
#define EXPORT extern "C"
|
||||
|
||||
String process_html_literal(Request* context, SharedUnit* su, String content);
|
||||
@@ -8,6 +9,7 @@ void load_shared_unit(Request* context, SharedUnit* su, String file_name);
|
||||
void compile_shared_unit(Request* context, SharedUnit* su, String file_name);
|
||||
SharedUnit* get_shared_unit(Request* context, String file_name, bool opt_so_optional = false);
|
||||
void compiler_invoke(Request* context, String file_name, DTree& call_param);
|
||||
void compiler_invoke_websocket(Request* context, String file_name, DTree& call_param);
|
||||
SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String current_path = "", bool opt_so_optional = false);
|
||||
|
||||
SharedUnit* load_file(String file_name);
|
||||
|
||||
+3
-1
@@ -431,7 +431,7 @@ std::map<pid_t, Worker> workers;
|
||||
#include <sys/resource.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
void spawn_subprocess(std::function<void()> exec_after_spawn)
|
||||
pid_t spawn_subprocess(std::function<void()> exec_after_spawn)
|
||||
{
|
||||
parent_pid = getpid();
|
||||
pid_t p;
|
||||
@@ -442,6 +442,7 @@ void spawn_subprocess(std::function<void()> exec_after_spawn)
|
||||
//printf("(C) child procress started, PID:%i\n", my_pid);
|
||||
prctl(PR_SET_PDEATHSIG, SIGHUP);
|
||||
exec_after_spawn();
|
||||
return(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -449,6 +450,7 @@ void spawn_subprocess(std::function<void()> exec_after_spawn)
|
||||
w.pid = p;
|
||||
workers[w.pid] = w;
|
||||
printf("(P) child procress spawned: PID %i\n", p);
|
||||
return(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,16 @@ void socket_close(u64 sockfd);
|
||||
bool socket_write(u64 sockfd, String data);
|
||||
String socket_read(u64 sockfd, u32 max_length = 1024*128, u32 timeout = 1);
|
||||
|
||||
String ws_message();
|
||||
String ws_connection_id();
|
||||
String ws_scope();
|
||||
StringList ws_connections(String scope = "");
|
||||
u64 ws_connection_count(String scope = "");
|
||||
bool ws_send(String message, String scope = "");
|
||||
u64 ws_broadcast(String message, String scope = "");
|
||||
bool ws_send_to(String connection_id, String message);
|
||||
bool ws_close(String connection_id = "");
|
||||
|
||||
String memcache_escape_key(String key);
|
||||
StringList memcache_escape_keys(StringList keys);
|
||||
u64 memcache_connect(String host = "127.0.0.1", short port = 11211);
|
||||
|
||||
+4
-1
@@ -47,7 +47,10 @@ void Request::ob_start()
|
||||
|
||||
Request::~Request()
|
||||
{
|
||||
for(auto* stream : ob_stack)
|
||||
delete stream;
|
||||
ob_stack.clear();
|
||||
ob = 0;
|
||||
for(auto& sockfd : resources.sockets)
|
||||
close(sockfd);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ struct SharedUnit {
|
||||
|
||||
request_handler on_setup;
|
||||
call_handler on_render;
|
||||
call_handler on_websocket;
|
||||
|
||||
String compiler_messages;
|
||||
time_t last_compiled;
|
||||
@@ -169,6 +170,9 @@ struct Request {
|
||||
std::vector<void*> mysql_connections;
|
||||
u64 client_socket = 0;
|
||||
u64 server_socket = 0;
|
||||
bool is_websocket = false;
|
||||
String websocket_connection_id = "";
|
||||
String websocket_scope = "";
|
||||
std::string params_buffer;
|
||||
} resources;
|
||||
|
||||
|
||||
+130
@@ -1,5 +1,32 @@
|
||||
#include "uri.h"
|
||||
|
||||
static String base64_encode(String raw)
|
||||
{
|
||||
static const char* chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
String result;
|
||||
int val = 0;
|
||||
int bits = -6;
|
||||
for(unsigned char c : raw)
|
||||
{
|
||||
val = (val << 8) + c;
|
||||
bits += 8;
|
||||
while(bits >= 0)
|
||||
{
|
||||
result.append(1, chars[(val >> bits) & 0x3F]);
|
||||
bits -= 6;
|
||||
}
|
||||
}
|
||||
if(bits > -6)
|
||||
result.append(1, chars[((val << 8) >> (bits + 8)) & 0x3F]);
|
||||
while(result.length() % 4 != 0)
|
||||
result.append(1, '=');
|
||||
return(result);
|
||||
}
|
||||
|
||||
String var_dump(URI uri, String prefix, String postfix)
|
||||
{
|
||||
return(
|
||||
@@ -427,3 +454,106 @@ void session_destroy(String session_name)
|
||||
context->session_id = "";
|
||||
}
|
||||
}
|
||||
|
||||
String ws_make_accept_key(String client_key)
|
||||
{
|
||||
return(base64_encode(gen_sha1(
|
||||
client_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
|
||||
true
|
||||
)));
|
||||
}
|
||||
|
||||
String ws_encode_frame(String payload, u8 opcode, bool is_final_fragment)
|
||||
{
|
||||
String frame;
|
||||
u8 first_byte = opcode & 0x0F;
|
||||
if(is_final_fragment)
|
||||
first_byte |= 0x80;
|
||||
frame.append(1, first_byte);
|
||||
|
||||
u64 payload_length = payload.length();
|
||||
if(payload_length <= 125)
|
||||
{
|
||||
frame.append(1, (char)payload_length);
|
||||
}
|
||||
else if(payload_length <= 0xFFFF)
|
||||
{
|
||||
frame.append(1, 126);
|
||||
frame.append(1, (char)((payload_length >> 8) & 0xFF));
|
||||
frame.append(1, (char)(payload_length & 0xFF));
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.append(1, 127);
|
||||
for(int shift = 56; shift >= 0; shift -= 8)
|
||||
frame.append(1, (char)((payload_length >> shift) & 0xFF));
|
||||
}
|
||||
|
||||
frame += payload;
|
||||
return(frame);
|
||||
}
|
||||
|
||||
String ws_close_frame(u16 status_code, String reason)
|
||||
{
|
||||
String payload;
|
||||
payload.append(1, (char)((status_code >> 8) & 0xFF));
|
||||
payload.append(1, (char)(status_code & 0xFF));
|
||||
payload += reason;
|
||||
return(ws_encode_frame(payload, 0x8));
|
||||
}
|
||||
|
||||
bool WSFrame::parse(const String& buffer, String& error)
|
||||
{
|
||||
error = "";
|
||||
payload = "";
|
||||
|
||||
if(buffer.length() < 2)
|
||||
return(false);
|
||||
|
||||
const unsigned char* raw = (const unsigned char*)buffer.data();
|
||||
opcode = raw[0] & 0x0F;
|
||||
is_final_fragment = (raw[0] & 0x80) != 0;
|
||||
mask_bit = (raw[1] & 0x80) != 0;
|
||||
payload_length = raw[1] & 0x7F;
|
||||
header_length = 2;
|
||||
|
||||
if(payload_length == 126)
|
||||
{
|
||||
if(buffer.length() < 4)
|
||||
return(false);
|
||||
payload_length = ((u64)raw[2] << 8) | (u64)raw[3];
|
||||
header_length = 4;
|
||||
}
|
||||
else if(payload_length == 127)
|
||||
{
|
||||
if(buffer.length() < 10)
|
||||
return(false);
|
||||
payload_length = 0;
|
||||
for(u32 i = 0; i < 8; i++)
|
||||
payload_length = (payload_length << 8) | (u64)raw[2 + i];
|
||||
header_length = 10;
|
||||
}
|
||||
|
||||
u64 mask_offset = header_length;
|
||||
if(mask_bit)
|
||||
header_length += 4;
|
||||
|
||||
frame_length = header_length + payload_length;
|
||||
if(frame_length < header_length)
|
||||
{
|
||||
error = "invalid websocket frame length";
|
||||
return(false);
|
||||
}
|
||||
if(buffer.length() < frame_length)
|
||||
return(false);
|
||||
|
||||
payload.assign(buffer.data() + header_length, payload_length);
|
||||
if(mask_bit)
|
||||
{
|
||||
const unsigned char* mask = raw + mask_offset;
|
||||
for(u64 i = 0; i < payload.length(); i++)
|
||||
payload[i] = payload[i] ^ mask[i % 4];
|
||||
}
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
+11
-60
@@ -17,69 +17,20 @@ StringMap load_session_data(String session_id);
|
||||
void save_session_data(String session_id, StringMap data);
|
||||
String session_start(String session_name = "uce-session");
|
||||
void session_destroy(String session_name = "uce-session");
|
||||
String ws_make_accept_key(String client_key);
|
||||
String ws_encode_frame(String payload, u8 opcode = 0x1, bool is_final_fragment = true);
|
||||
String ws_close_frame(u16 status_code = 1000, String reason = "");
|
||||
|
||||
struct WSFrame {
|
||||
|
||||
u8 opcode;
|
||||
u8 is_final_fragment;
|
||||
u8 mask_bit;
|
||||
u64 payload_length;
|
||||
u64 offset;
|
||||
u8* payload;
|
||||
u8 opcode = 0;
|
||||
bool is_final_fragment = false;
|
||||
bool mask_bit = false;
|
||||
u64 payload_length = 0;
|
||||
u64 header_length = 0;
|
||||
u64 frame_length = 0;
|
||||
String payload;
|
||||
|
||||
WSFrame(String& bufstr)
|
||||
{
|
||||
const char* buffer = bufstr.c_str();
|
||||
u64 buffer_size = bufstr.size();
|
||||
opcode = buffer[0] & 0x0F;
|
||||
is_final_fragment = buffer[0] & 0x80;
|
||||
mask_bit = buffer[1] & 0x80;
|
||||
payload_length = buffer[1] & 0x7F;
|
||||
|
||||
offset = 2;
|
||||
if (payload_length == 126) {
|
||||
payload_length = ((u64) buffer[2] << 8) | buffer[3];
|
||||
offset = 4;
|
||||
} else if (payload_length == 127) {
|
||||
payload_length = ((u64) buffer[2] << 56) |
|
||||
((u64) buffer[3] << 48) |
|
||||
((u64) buffer[4] << 40) |
|
||||
((u64) buffer[5] << 32) |
|
||||
((u64) buffer[6] << 24) |
|
||||
((u64) buffer[7] << 16) |
|
||||
((u64) buffer[8] << 8) |
|
||||
buffer[9];
|
||||
offset = 10;
|
||||
}
|
||||
|
||||
uint8_t* maskingKey = nullptr;
|
||||
if (mask_bit) {
|
||||
maskingKey = reinterpret_cast<uint8_t*>(const_cast<char*>(buffer + offset));
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
// Parse the frame payload
|
||||
payload = reinterpret_cast<uint8_t*>(const_cast<char*>(buffer + offset));
|
||||
if (mask_bit) {
|
||||
for (int i = 0; i < payload_length; i++) {
|
||||
payload[i] = payload[i] ^ maskingKey[i % 4];
|
||||
}
|
||||
}
|
||||
|
||||
// Print the parsed frame
|
||||
std::cout << "Parsed WebSocket frame:" << std::endl;
|
||||
std::cout << " Opcode: " << (int) opcode << std::endl;
|
||||
std::cout << " Is final fragment: " << (is_final_fragment ? "true" : "false") << std::endl;
|
||||
std::cout << " Payload length: " << payload_length << std::endl;
|
||||
if (mask_bit) {
|
||||
std::cout << " Masking key: ";
|
||||
for (int i = 0; i < 4; i++) {
|
||||
std::cout << std::hex << (int) maskingKey[i] << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << " Payload: " << std::endl << std::string((const char*) payload, payload_length) << std::endl;
|
||||
|
||||
}
|
||||
bool parse(const String& buffer, String& error);
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user