trying to port web app starter from PHP

This commit is contained in:
udo
2026-04-19 09:38:23 +00:00
parent 46d98a092f
commit be514d63d6
546 changed files with 76910 additions and 2807 deletions
+126 -74
View File
@@ -38,6 +38,7 @@
#include <stdexcept>
#include <errno.h> // E*
#include <fcntl.h>
#include <unistd.h> // read, write, close, unlink
#include <arpa/inet.h> // hton*
#include <netinet/in.h> // sockaddr_in, INADDR_*
@@ -74,6 +75,16 @@ is_valid_close_code(u16 status_code)
return(false);
}
static void
set_socket_nonblocking(int socket_handle)
{
int flags = fcntl(socket_handle, F_GETFL, 0);
if(flags == -1)
throw std::runtime_error("fcntl(F_GETFL) failed");
if(fcntl(socket_handle, F_SETFL, flags | O_NONBLOCK) == -1)
throw std::runtime_error("fcntl(F_SETFL) failed");
}
void
FastCGIServer::shutdown()
{
@@ -145,6 +156,7 @@ FastCGIServer::listen(unsigned tcp_port)
if (::listen(server_socket, 100))
throw std::runtime_error("listen() failed");
set_socket_nonblocking(server_socket);
server_sockets.push_back(server_socket);
} catch (...) {
close(server_socket);
@@ -185,6 +197,7 @@ FastCGIServer::listen(const std::string& local_path)
if (::listen(server_socket, 100))
throw std::runtime_error("listen() failed");
set_socket_nonblocking(server_socket);
server_sockets.push_back(server_socket);
listen_unlink.push_back(local_path);
@@ -208,8 +221,14 @@ FastCGIServer::send_output_buffer(Connection& con)
int write_result = write(con.client_socket,
con.output_buffer.data(),
con.output_buffer.size());
if (write_result == -1)
if(write_result == -1)
{
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
return(0);
if(errno == ECONNRESET || errno == EPIPE)
return(-1);
throw std::runtime_error("write() failed");
}
con.output_buffer.erase(0, write_result);
return write_result;
}
@@ -255,6 +274,8 @@ FastCGIServer::dispatch_websocket_message(Connection& connection, RequestID requ
it->second->resources.is_websocket = true;
it->second->resources.websocket_connection_id = connection.websocket_connection_id;
it->second->resources.websocket_scope = connection.websocket_scope;
it->second->resources.websocket_connection_state = &connection.websocket_state;
it->second->connection.set_reference(&connection.websocket_state);
it->second->resources.websocket_opcode = opcode;
it->second->resources.websocket_is_binary = (opcode == 0x2);
it->second->resources.websocket_is_text = (opcode == 0x1);
@@ -282,125 +303,152 @@ FastCGIServer::process(int timeout_ms)
for(auto con : client_sockets)
{
FD_SET(con.first, &fs_read);
if (!con.second->output_buffer.empty() || con.second->close_socket)
if(!con.second->output_buffer.empty() || con.second->close_socket)
FD_SET(con.first, &fs_write);
nfd = std::max(nfd, con.first);
}
int select_result = select(nfd + 1, &fs_read, &fs_write, NULL,
timeout_ms < 0 ? NULL : &tv);
if (select_result == -1)
if (errno == EINTR)
return;
else
int select_result = select(
nfd + 1,
&fs_read,
&fs_write,
NULL,
timeout_ms < 0 ? NULL : &tv
);
if(select_result == -1)
{
if(errno == EINTR)
return;
throw std::runtime_error("select() failed");
}
for(auto socket_handle : server_sockets)
if (FD_ISSET(socket_handle, &fs_read))
{
int client_socket = accept(socket_handle, NULL, NULL);
if (client_socket == -1)
throw std::runtime_error("accept() failed");
printf("Opening socket %i\n", client_socket);
client_sockets[client_socket] = new Connection();
client_sockets[client_socket]->client_socket = client_socket;
client_sockets[client_socket]->server_socket = socket_handle;
client_sockets[client_socket]->type = server_socket_types[socket_handle];
if(client_sockets[client_socket]->type == 'H')
if(!FD_ISSET(socket_handle, &fs_read))
continue;
for(;;)
{
FastCGIRequest* new_request = new FastCGIRequest();
new_request->resources.client_socket = client_socket;
new_request->resources.server_socket = socket_handle;
new_request->stats.time_init = microtime();
client_sockets[client_socket]->requests[client_socket] = new_request;
int client_socket = accept(socket_handle, NULL, NULL);
if(client_socket == -1)
{
if(errno == EAGAIN || errno == EWOULDBLOCK)
break;
if(errno == EINTR)
continue;
throw std::runtime_error("accept() failed");
}
set_socket_nonblocking(client_socket);
printf("Opening socket %i\n", client_socket);
client_sockets[client_socket] = new Connection();
client_sockets[client_socket]->client_socket = client_socket;
client_sockets[client_socket]->server_socket = socket_handle;
client_sockets[client_socket]->type = server_socket_types[socket_handle];
if(client_sockets[client_socket]->type == 'H')
{
FastCGIRequest* new_request = new FastCGIRequest();
new_request->resources.client_socket = client_socket;
new_request->resources.server_socket = socket_handle;
new_request->stats.time_init = microtime();
client_sockets[client_socket]->requests[client_socket] = new_request;
}
}
}
for (std::map<int, Connection*>::iterator it = client_sockets.begin();
for(std::map<int, Connection*>::iterator it = client_sockets.begin();
it != client_sockets.end();)
{
int read_socket = it->first;
Connection* connection = it->second;
if (FD_ISSET(read_socket, &fs_read))
if(FD_ISSET(read_socket, &fs_read))
{
int read_result = read(read_socket, buffer, sizeof(buffer));
if (read_result == -1)
if (errno == ECONNRESET)
if(read_result == -1)
{
if(errno == ECONNRESET)
goto close_socket;
else
if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
throw std::runtime_error("read() on socket failed");
if (read_result == 0)
}
else if(read_result == 0)
{
if(connection->type == 'H' && connection->is_websocket)
{
if(it->second->type == 'H' && it->second->is_websocket)
{
it->second->close_socket = true;
}
else if(it->second->type == 'H' && it->second->input_buffer != "")
{
process_http_request(
*client_sockets[it->second->client_socket]->requests[it->second->client_socket],
it->second->input_buffer);
if(it->second->close_socket || !it->second->output_buffer.empty())
it->second->input_buffer = "";
else
it->second->close_socket = true;
}
connection->close_socket = true;
}
else if(connection->type == 'H' && connection->input_buffer != "")
{
process_http_request(
*client_sockets[connection->client_socket]->requests[connection->client_socket],
connection->input_buffer
);
if(connection->close_socket || !connection->output_buffer.empty())
connection->input_buffer = "";
else
{
it->second->close_socket = true;
connection->close_socket = true;
}
else
{
connection->close_socket = true;
}
}
else
{
it->second->input_buffer.append(buffer, read_result);
if(it->second->type == 'H')
connection->input_buffer.append(buffer, read_result);
if(connection->type == 'H')
{
if(connection->is_websocket)
{
if(it->second->is_websocket)
{
process_websocket_input(*it->second);
}
else
{
process_http_request(
*client_sockets[it->second->client_socket]->requests[it->second->client_socket],
it->second->input_buffer);
if(it->second->close_socket || !it->second->output_buffer.empty())
it->second->input_buffer = "";
}
process_websocket_input(*connection);
}
else
{
read_fgci(*it->second);
process_http_request(
*client_sockets[connection->client_socket]->requests[connection->client_socket],
connection->input_buffer
);
if(connection->close_socket || !connection->output_buffer.empty())
connection->input_buffer = "";
}
}
else
{
read_fgci(*connection);
}
}
}
if (!it->second->output_buffer.empty() &&
FD_ISSET(read_socket, &fs_write))
if(!connection->output_buffer.empty() && FD_ISSET(read_socket, &fs_write))
{
if(it->second->type == 'F')
write_fgci(*it->second);
send_output_buffer(*it->second);
if(connection->type == 'F')
write_fgci(*connection);
if(send_output_buffer(*connection) == -1)
goto close_socket;
}
if (it->second->close_socket && it->second->output_buffer.empty())
if(connection->close_socket && connection->output_buffer.empty())
{
close_socket:
printf("Closing socket %i\n", it->first);
int close_result = close(it->first);
if (close_result == -1 && errno != ECONNRESET)
if(close_result == -1 && errno != ECONNRESET)
throw std::runtime_error("close() failed");
Connection* connection = it->second;
Connection* doomed_connection = it->second;
client_sockets.erase(it++);
delete connection;
delete doomed_connection;
if(calls_until_termination != -1 && client_sockets.size() == 0)
{
calls_until_termination -= 1;
if(calls_until_termination <= 0)
exit(0);
}
} else
}
else
{
++it;
}
}
}
@@ -528,6 +576,8 @@ FastCGIServer::process_http_request(FastCGIRequest& request, String& data)
request.resources.is_websocket = true;
request.resources.websocket_connection_id = connection->websocket_connection_id;
request.resources.websocket_scope = connection->websocket_scope;
request.resources.websocket_connection_state = &connection->websocket_state;
request.connection.set_reference(&connection->websocket_state);
connection->output_buffer +=
"HTTP/1.1 101 Switching Protocols\r\n"
@@ -682,14 +732,15 @@ FastCGIServer::process_websocket_input(Connection& connection)
}
bool
FastCGIServer::websocket_send_to(String connection_id, String message)
FastCGIServer::websocket_send_to(String connection_id, String message, bool binary)
{
u8 opcode = binary ? 0x2 : 0x1;
for(auto& item : client_sockets)
{
Connection* connection = item.second;
if(connection->is_websocket && connection->websocket_connection_id == connection_id)
{
connection->output_buffer += ws_encode_frame(message);
connection->output_buffer += ws_encode_frame(message, opcode);
return(true);
}
}
@@ -697,9 +748,10 @@ FastCGIServer::websocket_send_to(String connection_id, String message)
}
u64
FastCGIServer::websocket_broadcast(String scope, String message)
FastCGIServer::websocket_broadcast(String scope, String message, bool binary)
{
u64 sent = 0;
u8 opcode = binary ? 0x2 : 0x1;
for(auto& item : client_sockets)
{
Connection* connection = item.second;
@@ -707,7 +759,7 @@ FastCGIServer::websocket_broadcast(String scope, String message)
continue;
if(scope != "" && connection->websocket_scope != scope)
continue;
connection->output_buffer += ws_encode_frame(message);
connection->output_buffer += ws_encode_frame(message, opcode);
sent += 1;
}
return(sent);
+3 -2
View File
@@ -72,6 +72,7 @@ public:
bool is_websocket = false;
String websocket_connection_id;
String websocket_scope;
DTree websocket_state;
String websocket_fragment_buffer;
u8 websocket_fragment_opcode = 0;
char type = 'F'; // F = FastCGI, H = HttpServer
@@ -91,8 +92,8 @@ public:
void fail_websocket_connection(Connection& connection, u16 status_code, String reason = "");
void close_websocket_connection(Connection& connection, u16 status_code = 1000, String reason = "");
void dispatch_websocket_message(Connection& connection, RequestID request_id, String payload, u8 opcode);
bool websocket_send_to(String connection_id, String message);
u64 websocket_broadcast(String scope, String message);
bool websocket_send_to(String connection_id, String message, bool binary = false);
u64 websocket_broadcast(String scope, String message, bool binary = false);
StringList websocket_connection_ids(String scope = "");
bool websocket_close(String connection_id, u16 status_code = 1000, String reason = "");
static void request_write_fgci(Connection&, RequestID, FastCGIRequest&);
+324 -53
View File
@@ -12,25 +12,36 @@ String process_text_literal(Request* context, SharedUnit* su, String content)
bool inside_quote = false;
String code_buffer = "";
bool is_field = false;
bool escape_field = false;
for(u32 i = 0; i < content.length(); i++)
{
char c = content[i];
char c1 = (i + 1 < content.length()) ? content[i + 1] : '\0';
char c2 = (i + 2 < content.length()) ? content[i + 2] : '\0';
switch(mode)
{
case(0):
if(c == '<' && content[i+1] == '?')
if(c == '<' && c1 == '?')
{
code_buffer = "";
if(content[i+2] == '=')
if(c2 == '=')
{
is_field = true;
escape_field = true;
i += 2;
}
else if(c2 == ':')
{
is_field = true;
escape_field = false;
i += 2;
}
else
{
is_field = false;
escape_field = false;
i += 1;
}
mode = 1; // code-parsing mode
@@ -43,7 +54,7 @@ String process_text_literal(Request* context, SharedUnit* su, String content)
case(1):
if(inside_quote)
{
if(quote_char == c && content[i-1] != '\\')
if(quote_char == c && (i == 0 || content[i-1] != '\\'))
inside_quote = false;
code_buffer.append(1, c);
}
@@ -55,19 +66,32 @@ String process_text_literal(Request* context, SharedUnit* su, String content)
quote_char = c;
code_buffer.append(1, c);
}
else if(c == '?' && content[i+1] == '>')
else if(c == '?' && c1 == '>')
{
mode = 0;
i += 1;
if(is_field)
{
pc.append(
HT_END +
"print(html_escape( " +
code_buffer +
" )); " +
HT_START
);
if(escape_field)
{
pc.append(
HT_END +
"print(html_escape( " +
code_buffer +
" )); " +
HT_START
);
}
else
{
pc.append(
HT_END +
"print( " +
code_buffer +
" ); " +
HT_START
);
}
}
else
{
@@ -87,6 +111,57 @@ String process_text_literal(Request* context, SharedUnit* su, String content)
return(HT_START + pc + HT_END);
}
String preprocess_named_render_syntax(String content)
{
String result = "";
String current_line = "";
auto flush_line = [&]() {
if(current_line.length() == 0)
return;
String line = current_line;
String line_break = "";
if(line.length() > 0 && line.back() == '\n')
{
line_break = "\n";
line.pop_back();
}
u32 indent_length = 0;
while(indent_length < line.length() && isspace(line[indent_length]))
indent_length += 1;
String indent = line.substr(0, indent_length);
String trimmed = trim(line);
if(trimmed.rfind("RENDER:", 0) == 0)
{
String signature = trimmed.substr(7);
auto open_paren_pos = signature.find("(");
if(open_paren_pos != String::npos)
{
String render_name = trim(signature.substr(0, open_paren_pos));
String render_signature = signature.substr(open_paren_pos);
if(render_name != "")
line = indent + "EXPORT void render_" + safe_name(render_name) + render_signature;
}
}
result += line + line_break;
current_line = "";
};
for(auto c : content)
{
current_line.append(1, c);
if(c == '\n')
flush_line();
}
flush_line();
return(result);
}
String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String content)
{
String pc =
@@ -104,10 +179,12 @@ String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String
for(u32 i = 0; i < source_length; i++)
{
char c = content[i];
char c1 = (i + 1 < source_length) ? content[i + 1] : '\0';
char c2 = (i + 2 < source_length) ? content[i + 2] : '\0';
current_line.append(1, c);
if(mode == 1)
{
if(c == '<' && (content[i+1] == '/') && (content[i+2] == '>'))
if(c == '<' && c1 == '/' && c2 == '>')
{
i += 2;
pc.append(process_text_literal(context, su, html_buffer));
@@ -119,7 +196,7 @@ String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String
html_buffer.append(1, c);
}
}
else if(!inside_quote && c == '<' && (content[i+1] == '>'))
else if(!inside_quote && c == '<' && c1 == '>')
{
mode = 1;
token = "";
@@ -147,16 +224,20 @@ String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String
pc.append("#include \"" + sub_su->bin_path + "/" + sub_su->pre_file_name + "\"\n");
}
}
else if(current_line.substr(0, 6) == "EXPORT" && isspace(current_line[6]))
else
{
current_line = "";
auto end_declaration_pos = content.find("{", i);
if(end_declaration_pos != std::string::npos)
String trimmed_line = trim(current_line);
if(c == 10 && trimmed_line.length() > 7 && trimmed_line.substr(0, 6) == "EXPORT" && isspace(trimmed_line[6]))
{
pc.append(1, '\n');
String declaration = trim(content.substr(i, end_declaration_pos - i));
su->api_declarations.push_back(declaration+";\n");
//printf("declaration found: %s\n", declaration.c_str());
current_line = "";
auto end_declaration_pos = content.find("{", i);
if(end_declaration_pos != std::string::npos)
{
pc.append(1, '\n');
String declaration = trim(content.substr(i, end_declaration_pos - i));
su->api_declarations.push_back(declaration+";\n");
//printf("declaration found: %s\n", declaration.c_str());
}
}
}
}
@@ -168,11 +249,13 @@ String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String
String preprocess_shared_unit(Request* context, SharedUnit* su)
{
String content = file_get_contents(su->file_name);
content = preprocess_named_render_syntax(content);
return(
preprocess_shared_unit_char_wise(
context,
su,
file_get_contents(su->file_name)
content
)
);
}
@@ -223,9 +306,9 @@ void load_shared_unit(Request* context, SharedUnit* su, String file_name)
su->on_setup = (request_handler)dlsym(su->so_handle, "set_current_request");
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");
su->on_render = (request_ref_handler)dlsym(su->so_handle, "render");
dlerror();
su->on_websocket = (call_handler)dlsym(su->so_handle, "websocket");
su->on_websocket = (request_ref_handler)dlsym(su->so_handle, "websocket");
dlerror();
su->api_declarations = split(file_get_contents(su->api_file_name), "\n");
//else
@@ -291,6 +374,11 @@ SharedUnit* get_shared_unit(Request* context, String file_name, bool opt_so_opti
{
SharedUnit* su = context->server->units[file_name];
auto mod_time = file_mtime(file_name);
auto setup_template_time = file_mtime(
context->server->config["COMPILER_SYS_PATH"] + "/" +
context->server->config["SETUP_TEMPLATE"]);
if(setup_template_time > mod_time)
mod_time = setup_template_time;
auto compiled_time = su ? file_mtime(su->so_name) : 0;
bool do_recompile = false;
if(su && (compiled_time < mod_time || mod_time == 0))
@@ -378,36 +466,141 @@ SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String
}
void compiler_invoke(Request* context, String file_name, DTree& call_param)
String component_normalize_path(String name)
{
name = trim(name);
if(name.length() >= 4 && name.substr(name.length() - 4) == ".uce")
return(name);
return(name + ".uce");
}
void component_parse_target(String target, String& file_name, String& render_name)
{
target = trim(target);
render_name = "render";
auto render_split_pos = target.find(":");
if(render_split_pos != String::npos)
{
render_name = trim(target.substr(render_split_pos + 1));
target = trim(target.substr(0, render_split_pos));
if(render_name == "")
render_name = "render";
}
file_name = target;
}
String component_resolve_path(String name)
{
String file_name;
String render_name;
component_parse_target(name, file_name, render_name);
if(file_name == "")
return("");
StringList candidates;
auto push_candidate = [&] (String candidate) {
if(candidate == "")
return;
candidates.push_back(candidate);
};
push_candidate(file_name);
push_candidate(component_normalize_path(file_name));
if(file_name.rfind("components/", 0) != 0)
{
push_candidate("components/" + file_name);
push_candidate(component_normalize_path("components/" + file_name));
}
std::map<String, bool> seen;
for(auto& candidate : candidates)
{
if(seen[candidate])
continue;
seen[candidate] = true;
String resolved = candidate;
if(resolved[0] != '/')
resolved = expand_path(resolved, get_cwd());
if(file_exists(resolved))
return(resolved);
}
return("");
}
String render_handler_symbol(String render_name)
{
render_name = trim(render_name);
if(render_name == "" || render_name == "render")
return("render");
return("render_" + safe_name(render_name));
}
request_ref_handler get_render_handler(SharedUnit* su, String render_name)
{
String symbol = render_handler_symbol(render_name);
if(symbol == "render")
return(su->on_render);
auto it = su->api_functions.find(symbol);
if(it != su->api_functions.end())
return((request_ref_handler)it->second);
auto handler = (request_ref_handler)dlsym(su->so_handle, symbol.c_str());
dlerror();
su->api_functions[symbol] = (void*)handler;
return(handler);
}
bool compiler_invoke_render(Request* context, String file_name, String render_name, String* error_out = 0)
{
auto su = compiler_load_shared_unit(context, file_name, "", false);
if(!su)
return(false);
if(!su->on_setup)
{
if(error_out)
*error_out = "internal error: set_current_request() not defined in " + file_name;
return(false);
}
auto handler = get_render_handler(su, render_name);
if(!handler)
{
if(error_out)
{
if(trim(render_name) == "" || trim(render_name) == "render")
*error_out = "no RENDER() entry point";
else
*error_out = "no RENDER:" + render_name + "() entry point";
}
return(false);
}
String prev_wd = get_cwd();
set_cwd(su->src_path);
su->on_setup(context);
handler(*context);
set_cwd(prev_wd);
return(true);
}
void compiler_invoke(Request* context, String file_name)
{
printf("(i) compiler_invoke file %s\n", file_name.c_str());
auto su = compiler_load_shared_unit(context, file_name, "", false);
if(su)
String error_message = "";
if(!compiler_invoke_render(context, file_name, "render", &error_message) && error_message != "")
{
if(!su->on_setup)
{
if(context->stats.invoke_count == 1)
context->header["Content-Type"] = "text/plain";
print("internal error: set_current_request() not defined in", file_name, "\n");
}
else if(!su->on_render)
{
if(context->stats.invoke_count == 1)
context->header["Content-Type"] = "text/plain";
print("no RENDER() entry point");
}
else
{
String prev_wd = get_cwd();
set_cwd(su->src_path);
su->on_setup(context);
su->on_render(call_param);
set_cwd(prev_wd);
}
if(context->stats.invoke_count == 1)
context->header["Content-Type"] = "text/plain";
print(error_message);
}
}
void compiler_invoke_websocket(Request* context, String file_name, DTree& call_param)
void compiler_invoke_websocket(Request* context, String file_name)
{
auto su = compiler_load_shared_unit(context, file_name, "", false);
if(!su)
@@ -428,20 +621,98 @@ void compiler_invoke_websocket(Request* context, String file_name, DTree& call_p
String prev_wd = get_cwd();
set_cwd(su->src_path);
su->on_setup(context);
su->on_websocket(call_param);
su->on_websocket(*context);
set_cwd(prev_wd);
}
void render_file(String file_name)
{
//printf("(i) render_file(%s)\n", file_name.c_str());
DTree call_param;
compiler_invoke(context, file_name, call_param);
compiler_invoke(context, file_name);
}
void render_file(String file_name, DTree& call_param)
void render_file(String file_name, Request& context)
{
compiler_invoke(context, file_name, call_param);
compiler_invoke(&context, file_name);
}
String component_resolve(String name)
{
return(component_resolve_path(name));
}
bool component_exists(String name)
{
return(component_resolve(name) != "");
}
String component_error_banner(String message)
{
return("<div class=\"banner\">" + html_escape(message) + "</div>");
}
void render_component(String name)
{
DTree props;
render_component(name, props, *context);
}
void render_component(String name, Request& context)
{
DTree props;
render_component(name, props, context);
}
void render_component(String name, DTree props)
{
render_component(name, props, *context);
}
void render_component(String name, DTree props, Request& context)
{
String file_name;
String render_name;
component_parse_target(name, file_name, render_name);
String resolved_name = component_resolve_path(file_name);
if(resolved_name == "")
{
print(component_error_banner("component not found: " + file_name));
return;
}
DTree previous_call = context.call;
context.call = props;
String error_message = "";
if(!compiler_invoke_render(&context, resolved_name, render_name, &error_message) && error_message != "")
print(component_error_banner(error_message));
context.call = previous_call;
}
String component(String name)
{
DTree props;
return(component(name, props, *context));
}
String component(String name, Request& context)
{
DTree props;
return(component(name, props, context));
}
String component(String name, DTree props)
{
return(component(name, props, *context));
}
String component(String name, DTree props, Request& context)
{
ob_start();
render_component(name, props, context);
return(ob_get_close());
}
SharedUnit* load_file(String file_name)
+17 -5
View File
@@ -1,5 +1,7 @@
#define RENDER() extern "C" void render(DTree& call)
#define WS() extern "C" void websocket(DTree& call)
#pragma once
#define RENDER(X) extern "C" void render(Request& context)
#define WS(X) extern "C" void websocket(Request& context)
#define EXPORT extern "C"
String process_html_literal(Request* context, SharedUnit* su, String content);
@@ -8,13 +10,23 @@ void setup_unit_paths(Request* context, SharedUnit* su, String file_name);
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);
void compiler_invoke(Request* context, String file_name);
void compiler_invoke_websocket(Request* context, String file_name);
SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String current_path = "", bool opt_so_optional = false);
SharedUnit* load_file(String file_name);
void render_file(String file_name);
void render_file(String file_name, DTree& call_param);
void render_file(String file_name, Request& context);
DTree* call_file(String file_name, String function_name, DTree* call_param = 0);
String component_resolve(String name);
bool component_exists(String name);
void render_component(String name);
void render_component(String name, Request& context);
void render_component(String name, DTree props);
void render_component(String name, DTree props, Request& context);
String component(String name);
String component(String name, Request& context);
String component(String name, DTree props);
String component(String name, DTree props, Request& context);
StringList precompile_jobs;
+167 -13
View File
@@ -1,10 +1,30 @@
namespace {
template <typename TreePtr>
TreePtr dtree_resolve_reference(TreePtr tree)
{
u32 depth = 0;
while(tree && tree->type == 'R' && depth < 16)
{
TreePtr target = reinterpret_cast<TreePtr>(tree->_ptr);
if(target == 0 || target == tree)
break;
tree = target;
depth += 1;
}
return(tree);
}
}
void DTree::each(std::function <void (DTree t, String key)> f)
{
switch(type)
const DTree& target = deref();
switch(target.type)
{
case('M'):
for (auto it = _map.begin(); it != _map.end(); ++it)
for (auto it = target._map.begin(); it != target._map.end(); ++it)
{
f(it->second, it->first);
}
@@ -17,43 +37,49 @@ void DTree::each(std::function <void (DTree t, String key)> f)
bool DTree::is_array()
{
return(type == 'M');
return(deref().type == 'M');
}
String DTree::to_string()
{
switch(type)
const DTree& target = deref();
switch(target.type)
{
case('S'):
return(_String);
return(target._String);
break;
case('F'):
return(std::to_string(_float));
return(std::to_string(target._float));
break;
case('B'):
return(_bool ? "(true)" : "(false)");
return(target._bool ? "(true)" : "(false)");
break;
case('M'):
return("");
break;
case('P'):
return(std::to_string((u64)_ptr));
return(std::to_string((u64)target._ptr));
break;
case('R'):
return("");
break;
}
return("");
}
String DTree::to_json()
{
switch(type)
const DTree& target = deref();
switch(target.type)
{
case('S'):
return(json_escape(_String));
return(json_escape(target._String));
break;
case('F'):
return(std::to_string(_float));
return(std::to_string(target._float));
break;
case('B'):
return(_bool ? "true" : "false");
return(target._bool ? "true" : "false");
break;
case('M'):
return("\"(array)\"");
@@ -61,12 +87,17 @@ String DTree::to_json()
case('P'):
return("\"(pointer)\"");
break;
case('R'):
return("\"(reference)\"");
break;
}
return("\"(unknown)\"");
}
String DTree::get_type_name()
{
switch(type)
const DTree& target = deref();
switch(target.type)
{
case('S'):
return("String");
@@ -83,11 +114,62 @@ String DTree::get_type_name()
case('P'):
return("pointer");
break;
case('R'):
return("reference");
break;
}
return("unknown");
}
bool DTree::is_reference()
{
return(type == 'R');
}
DTree* DTree::reference_target()
{
if(type != 'R')
return(0);
DTree* target = dtree_resolve_reference(this);
if(target == 0 || target == this || target->type == 'R')
return(0);
return(target);
}
const DTree* DTree::reference_target() const
{
if(type != 'R')
return(0);
const DTree* target = dtree_resolve_reference(this);
if(target == 0 || target == this || target->type == 'R')
return(0);
return(target);
}
DTree& DTree::deref()
{
DTree* target = dtree_resolve_reference(this);
if(target == 0)
return(*this);
return(*target);
}
const DTree& DTree::deref() const
{
const DTree* target = dtree_resolve_reference(this);
if(target == 0)
return(*this);
return(*target);
}
void DTree::set_type(char t)
{
DTree* target = reference_target();
if(target)
{
target->set_type(t);
return;
}
if(type != t)
{
type = t;
@@ -103,30 +185,60 @@ void DTree::set_type(char t)
void DTree::set(String s)
{
DTree* target = reference_target();
if(target)
{
target->set(s);
return;
}
set_type('S');
_String = s;
}
void DTree::set(void* p)
{
DTree* target = reference_target();
if(target)
{
target->set(p);
return;
}
set_type('P');
_ptr = p;
}
void DTree::set(f64 f)
{
DTree* target = reference_target();
if(target)
{
target->set(f);
return;
}
set_type('F');
_float = f;
}
void DTree::set_bool(bool b)
{
DTree* target = reference_target();
if(target)
{
target->set_bool(b);
return;
}
set_type('B');
_bool = b;
}
void DTree::set(DTree source)
{
DTree* target = reference_target();
if(target)
{
target->set(source);
return;
}
set_type(source.type);
switch(type)
{
@@ -145,11 +257,20 @@ void DTree::set(DTree source)
case('P'):
_ptr = source._ptr;
break;
case('R'):
_ptr = source._ptr;
break;
}
}
void DTree::set(StringMap source)
{
DTree* target = reference_target();
if(target)
{
target->set(source);
return;
}
set_type('M');
for (auto it = source.begin(); it != source.end(); ++it)
{
@@ -157,13 +278,25 @@ void DTree::set(StringMap source)
}
}
void DTree::set_reference(DTree* target)
{
type = 'R';
_ptr = target;
}
DTree* DTree::key(String s)
{
DTree* target = reference_target();
if(target)
return(target->key(s));
set_type('M');
return(&_map[s]);
}
DTree& DTree::operator [] (String s) {
DTree* target = reference_target();
if(target)
return((*target)[s]);
set_type('M');
return(_map[s]);
}
@@ -176,6 +309,12 @@ void DTree::operator = (StringMap v) { set(v); }
void DTree::push(DTree& child)
{
DTree* target = reference_target();
if(target)
{
target->push(child);
return;
}
set_type('M');
_map[std::to_string(_array_index)] = child;
_array_index += 1;
@@ -183,6 +322,9 @@ void DTree::push(DTree& child)
DTree DTree::pop()
{
DTree* target = reference_target();
if(target)
return(target->pop());
set_type('M');
auto last = _map.rbegin();
DTree result = last->second;
@@ -192,12 +334,24 @@ DTree DTree::pop()
void DTree::remove(String s)
{
DTree* target = reference_target();
if(target)
{
target->remove(s);
return;
}
set_type('M');
_map.erase(s);
}
void DTree::clear()
{
DTree* target = reference_target();
if(target)
{
target->clear();
return;
}
set_type('M');
_map.clear();
}
+8
View File
@@ -1,3 +1,5 @@
#pragma once
String json_escape(String s);
struct DTree {
@@ -16,6 +18,11 @@ struct DTree {
String to_string();
String to_json();
String get_type_name();
bool is_reference();
DTree* reference_target();
const DTree* reference_target() const;
DTree& deref();
const DTree& deref() const;
void set_type(char t);
void set(String s);
void set(void* p);
@@ -23,6 +30,7 @@ struct DTree {
void set_bool(bool b);
void set(DTree source);
void set(StringMap source);
void set_reference(DTree* target);
DTree* key(String s);
DTree& operator [] (String s);
void operator = (String v);
+9 -3
View File
@@ -77,9 +77,9 @@ String replace(String s, String search, String replace_with)
String trim(String raw)
{
u32 len = raw.length();
u32 start_pos = 0;
u32 end_pos = len - 1;
s64 len = raw.length();
s64 start_pos = 0;
s64 end_pos = len - 1;
if(len == 0 || (len == 1 && isspace(raw[0])))
return("");
while(start_pos < len && isspace(raw[start_pos]))
@@ -610,7 +610,13 @@ void ob_close()
delete context->ob;
context->ob_stack.pop_back();
if(context->ob_stack.size() == 0)
{
ob_start();
}
else
{
context->ob = context->ob_stack.back();
}
}
String ob_get()
+2
View File
@@ -1,4 +1,6 @@
#pragma once
u8 char_to_u8(char input);
u8 hex_to_u8(String src);
u64 int_val(String s, u32 base = 10);
+2
View File
@@ -1,3 +1,5 @@
#pragma once
/* ================ sha1.h ================ */
/*
SHA-1 in C
+1378
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
#pragma once
DTree markdown_to_ast(String src);
DTree markdown_to_ast(String src, DTree options);
String markdown_to_html(String src);
String markdown_to_html(String src, DTree options);
+2
View File
@@ -1,3 +1,5 @@
#pragma once
struct MySQLFieldInfo {
String name;
String table;
+45 -9
View File
@@ -10,6 +10,45 @@
#include <sys/file.h>
#include "sys.h"
String capture_backtrace_string(u32 max_frames, u32 skip_frames)
{
if(max_frames == 0)
return("");
std::vector<void*> frames(max_frames);
size_t size = backtrace(frames.data(), max_frames);
if(size == 0)
return("");
char** symbols = backtrace_symbols(frames.data(), size);
if(!symbols)
return("");
String trace;
for(size_t i = skip_frames; i < size; i++)
{
trace += symbols[i];
trace += "\n";
}
free(symbols);
return(trace);
}
String signal_name(int sig)
{
switch(sig)
{
case SIGABRT: return("SIGABRT");
case SIGBUS: return("SIGBUS");
case SIGFPE: return("SIGFPE");
case SIGILL: return("SIGILL");
case SIGINT: return("SIGINT");
case SIGSEGV: return("SIGSEGV");
case SIGTERM: return("SIGTERM");
default: return("");
}
}
String shell_exec(String cmd)
{
//printf("(i) shell_exec(%s)\n", cmd.c_str());
@@ -410,15 +449,12 @@ StringMap memcache_get_multiple(u64 connection, StringList keys)
void on_segfault(int sig)
{
void *array[10];
size_t size;
// get void*'s for all entries on the stack
size = backtrace(array, 10);
// print out all the frames to stderr
fprintf(stderr, "SEG FAULT: %d:\n", sig);
backtrace_symbols_fd(array, size, STDERR_FILENO);
String trace = capture_backtrace_string(32, 1);
String sig_label = signal_name(sig);
if(sig_label != "")
fprintf(stderr, "SEG FAULT: %d (%s):\n%s", sig, sig_label.c_str(), trace.c_str());
else
fprintf(stderr, "SEG FAULT: %d:\n%s", sig, trace.c_str());
exit(1);
}
+7 -3
View File
@@ -1,3 +1,5 @@
#pragma once
#include <signal.h>
String shell_exec(String cmd);
@@ -43,11 +45,13 @@ u8 ws_opcode();
bool ws_is_binary();
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_send(String message, bool binary = false, String scope = "");
bool ws_send_to(String connection_id, String message, bool binary = false);
bool ws_close(String connection_id = "");
String capture_backtrace_string(u32 max_frames = 32, u32 skip_frames = 0);
String signal_name(int sig);
String memcache_escape_key(String key);
StringList memcache_escape_keys(StringList keys);
u64 memcache_connect(String host = "127.0.0.1", short port = 11211);
+10 -5
View File
@@ -1,3 +1,5 @@
#pragma once
#include <string>
#include <map>
#include <list>
@@ -52,7 +54,7 @@ typedef std::ostringstream ByteStream;
struct Request;
struct DTree;
typedef void (*call_handler)(DTree& call_param);
typedef void (*request_ref_handler)(Request& request);
typedef DTree* (*dtree_call_handler)(DTree* call_param);
typedef void (*request_handler)(Request* request);
@@ -77,8 +79,8 @@ struct SharedUnit {
void* so_handle;
request_handler on_setup;
call_handler on_render;
call_handler on_websocket;
request_ref_handler on_render;
request_ref_handler on_websocket;
String compiler_messages;
time_t last_compiled;
@@ -113,7 +115,7 @@ String nibble(String div, String& haystack);
#include "dtree.h"
void compiler_invoke(Request* context, String file_name, DTree& call_param);
void compiler_invoke(Request* context, String file_name);
struct Request {
@@ -126,6 +128,8 @@ struct Request {
StringMap session;
DTree var;
DTree call;
DTree connection;
String session_id = "";
String session_name = "";
@@ -148,7 +152,7 @@ struct Request {
struct Flags {
bool log_request = true;
bool is_finished = false;
int status;
int status = 0;
bool output_closed = false;
bool params_closed = false;
bool input_closed = false;
@@ -173,6 +177,7 @@ struct Request {
bool is_websocket = false;
String websocket_connection_id = "";
String websocket_scope = "";
DTree* websocket_connection_state = 0;
u8 websocket_opcode = 0;
bool websocket_is_binary = false;
bool websocket_is_text = false;
+1
View File
@@ -7,4 +7,5 @@
#include "sys.cpp"
#include "uri.cpp"
#include "compiler.cpp"
#include "markdown.cpp"
#include "mysql-connector.cpp"
+3 -2
View File
@@ -1,10 +1,11 @@
#pragma once
#include "types.h"
#include "hash.h"
#include "functionlib.h"
#include "sys.h"
#include "uri.h"
#include "compiler.h"
#include "markdown.h"
#include "mysql-connector.h"
+2 -1
View File
@@ -1,3 +1,4 @@
#pragma once
#include "uce_lib.h"
+2
View File
@@ -1,4 +1,6 @@
#pragma once
String var_dump(URI uri, String prefix = "", String postfix = "\n");
String uri_decode(String q);
+181 -34
View File
@@ -1,4 +1,5 @@
#include "lib/uce_lib.cpp"
#include <csetjmp>
ServerState server_state;
@@ -7,6 +8,11 @@ ServerState server_state;
FastCGIServer server;
pid_t http_worker_pid = 0;
bool worker_accepts_http = false;
static sigjmp_buf request_fault_jmp;
static volatile sig_atomic_t request_fault_active = 0;
static volatile sig_atomic_t request_fault_signal = 0;
static Request* request_fault_request = 0;
static String request_fault_trace = "";
Request* set_active_request(Request& request)
{
@@ -20,6 +26,98 @@ void restore_active_request(Request* previous_context)
context = previous_context;
}
String request_status_line(Request& request, int status_code, String reason)
{
String status = std::to_string(status_code) + " " + reason;
if(request.params["GATEWAY_INTERFACE"] != "")
return("Status: " + status);
return("HTTP/1.1 " + status);
}
void clear_request_output(Request& request)
{
for(auto* stream : request.ob_stack)
delete stream;
request.ob_stack.clear();
request.ob_start();
}
void render_request_failure(Request& request, String title, String details, String trace, int status_code = 500)
{
request.response_code = request_status_line(request, status_code, "Internal Server Error");
request.header.clear();
request.set_cookies.clear();
request.header["Content-Type"] = "text/plain; charset=utf-8";
request.err.clear();
Request* previous_context = set_active_request(request);
clear_request_output(request);
print("UCE runtime error\n");
print("Request: ", first(request.params["REQUEST_URI"], request.params["SCRIPT_FILENAME"]), "\n");
print("Script: ", request.params["SCRIPT_FILENAME"], "\n");
print("Error: ", title, "\n");
if(details != "")
print("Details: ", details, "\n");
if(request_fault_signal != 0)
{
String sig_label = signal_name((int)request_fault_signal);
print("Signal: ", (s64)request_fault_signal);
if(sig_label != "")
print(" (", sig_label, ")");
print("\n");
}
if(trace != "")
print("\nTrace:\n", trace);
request.err += "UCE runtime error\n";
request.err += "Request: " + first(request.params["REQUEST_URI"], request.params["SCRIPT_FILENAME"]) + "\n";
request.err += "Script: " + request.params["SCRIPT_FILENAME"] + "\n";
request.err += "Error: " + title + "\n";
if(details != "")
request.err += "Details: " + details + "\n";
if(request_fault_signal != 0)
{
String sig_label = signal_name((int)request_fault_signal);
request.err += "Signal: " + std::to_string((int)request_fault_signal);
if(sig_label != "")
request.err += " (" + sig_label + ")";
request.err += "\n";
}
if(trace != "")
request.err += "\nTrace:\n" + trace;
request.flags.status = status_code;
restore_active_request(previous_context);
}
void on_request_fault_signal(int sig)
{
request_fault_signal = sig;
request_fault_trace = capture_backtrace_string(32, 1);
if(request_fault_active && request_fault_request)
siglongjmp(request_fault_jmp, 1);
on_segfault(sig);
}
void install_request_fault_handlers()
{
signal(SIGSEGV, on_request_fault_signal);
signal(SIGABRT, on_request_fault_signal);
signal(SIGBUS, on_request_fault_signal);
signal(SIGILL, on_request_fault_signal);
signal(SIGFPE, on_request_fault_signal);
}
void restore_request_fault_handlers()
{
signal(SIGSEGV, on_segfault);
signal(SIGABRT, on_segfault);
signal(SIGBUS, on_segfault);
signal(SIGILL, on_segfault);
signal(SIGFPE, on_segfault);
}
String current_ws_scope()
{
if(!context)
@@ -43,7 +141,7 @@ String ws_message()
{
if(!context)
return("");
return(context->var["ws"]["message"].to_string());
return(context->call["message"].to_string());
}
String ws_connection_id()
@@ -82,19 +180,14 @@ u64 ws_connection_count(String scope)
return(ws_connections(scope).size());
}
bool ws_send(String message, String scope)
bool ws_send(String message, bool binary, String scope)
{
return(server.websocket_broadcast(normalize_ws_scope(scope), message) > 0);
return(server.websocket_broadcast(normalize_ws_scope(scope), message, binary) > 0);
}
u64 ws_broadcast(String message, String scope)
bool ws_send_to(String connection_id, String message, bool binary)
{
return(server.websocket_broadcast(normalize_ws_scope(scope), message));
}
bool ws_send_to(String connection_id, String message)
{
return(server.websocket_send_to(connection_id, message));
return(server.websocket_send_to(connection_id, message, binary));
}
bool ws_close(String connection_id)
@@ -147,51 +240,89 @@ int handle_complete(FastCGIRequest& request) {
//request.stats.mem_high = 0;
request.header["Content-Type"] = context->server->config["CONTENT_TYPE"];
request.get = parse_query(request.params["QUERY_STRING"]);
request.random_index = 0;
request.random_seed = gen_noise64(*reinterpret_cast<u64*>(&request.stats.time_start));
request.random_index = 0;
request.random_seed = gen_noise64(*reinterpret_cast<u64*>(&request.stats.time_start));
request.ob_start();
request_fault_request = &request;
request_fault_active = 1;
request_fault_signal = 0;
request_fault_trace = "";
install_request_fault_handlers();
if(request.params["HTTP_COOKIE"].length() > 0)
request.cookies = parse_cookies(request.params["HTTP_COOKIE"]);
String failure_title = "";
String failure_details = "";
String failure_trace = "";
String ct_info = request.params["CONTENT_TYPE"];
String ct_type = nibble(";", ct_info);
if(request.params["REQUEST_METHOD"] == "POST")
if(sigsetjmp(request_fault_jmp, 1) != 0)
{
if(ct_type == "multipart/form-data")
failure_title = "fatal signal during request";
failure_details = "worker recovered before closing the upstream connection";
failure_trace = request_fault_trace;
}
else
{
try
{
nibble("boundary=", ct_info);
request.post = parse_multipart(request.in, String("--")+ct_info, request.uploaded_files);
if(request.params["HTTP_COOKIE"].length() > 0)
request.cookies = parse_cookies(request.params["HTTP_COOKIE"]);
String ct_info = request.params["CONTENT_TYPE"];
String ct_type = nibble(";", ct_info);
if(request.params["REQUEST_METHOD"] == "POST")
{
if(ct_type == "multipart/form-data")
{
nibble("boundary=", ct_info);
request.post = parse_multipart(request.in, String("--")+ct_info, request.uploaded_files);
}
else
{
request.post = parse_query(request.in);
}
}
request.call = DTree();
compiler_invoke(&request, request.params["SCRIPT_FILENAME"]);
}
else
catch(const std::exception& e)
{
request.post = parse_query(request.in);
failure_title = "uncaught exception during request";
failure_details = e.what();
failure_trace = capture_backtrace_string(32, 1);
}
catch(...)
{
failure_title = "unknown uncaught exception during request";
failure_trace = capture_backtrace_string(32, 1);
}
}
DTree call_param;
compiler_invoke(&request, request.params["SCRIPT_FILENAME"], call_param);
request_fault_active = 0;
request_fault_request = 0;
restore_request_fault_handlers();
if(failure_title != "")
render_request_failure(request, failure_title, failure_details, failure_trace, 500);
for( auto &f : request.uploaded_files)
{
unlink(f.tmp_name);
}
if(request.session_id.length() > 0)
if(failure_title == "" && request.session_id.length() > 0)
save_session_data(request.session_id, request.session);
cleanup_mysql_connections();
restore_active_request(previous_context);
return 0;
return request.flags.status;
}
int handle_websocket_message(FastCGIRequest& request, const String& message, u8 opcode)
{
Request event_request;
ByteStream ws_output;
DTree call_param;
Request* previous_context = set_active_request(event_request);
server_state.request_count += 1;
@@ -200,6 +331,8 @@ int handle_websocket_message(FastCGIRequest& request, const String& message, u8
event_request.params["REQUEST_METHOD"] = "WEBSOCKET";
event_request.get = parse_query(event_request.params["QUERY_STRING"]);
event_request.resources = request.resources;
if(event_request.resources.websocket_connection_state)
event_request.connection.set_reference(event_request.resources.websocket_connection_state);
event_request.stats.time_init = microtime();
event_request.stats.time_start = event_request.stats.time_init;
event_request.random_index = 0;
@@ -224,13 +357,13 @@ int handle_websocket_message(FastCGIRequest& request, const String& message, u8
request.params["REQUEST_URI"]
);
call_param["message"] = message;
call_param["connection_id"] = request.resources.websocket_connection_id;
call_param["scope"] = request.resources.websocket_scope;
call_param["opcode"] = (f64)opcode;
call_param["document_uri"] = event_request.var["ws"]["document_uri"].to_string();
event_request.call["message"] = message;
event_request.call["connection_id"] = request.resources.websocket_connection_id;
event_request.call["scope"] = request.resources.websocket_scope;
event_request.call["opcode"] = (f64)opcode;
event_request.call["document_uri"] = event_request.var["ws"]["document_uri"].to_string();
compiler_invoke_websocket(&event_request, request.params["SCRIPT_FILENAME"], call_param);
compiler_invoke_websocket(&event_request, request.params["SCRIPT_FILENAME"]);
if(event_request.session_id.length() > 0)
save_session_data(event_request.session_id, event_request.session);
@@ -268,6 +401,19 @@ void listen_for_connections()
}
signal(SIGSEGV, on_segfault);
signal(SIGABRT, on_segfault);
signal(SIGBUS, on_segfault);
signal(SIGILL, on_segfault);
signal(SIGFPE, on_segfault);
signal(SIGPIPE, SIG_IGN);
if(worker_accepts_http)
{
// Keep the dedicated HTTP/WebSocket worker alive. If it ages out like a
// normal FastCGI worker, nginx can connect to the shared listening socket
// while no child is actively accepting, which makes `.ws.uce` page loads
// appear to hang until the parent respawns a replacement worker.
server.calls_until_termination = -1;
}
if(!worker_accepts_http)
server.close_http_listeners();
server.on_request = &handle_request;
@@ -311,6 +457,7 @@ void init_base_process()
signal(SIGCHLD, on_child_exit);
signal(SIGINT, on_terminate);
signal(SIGPIPE, SIG_IGN);
srand(time());
}