trying to port web app starter from PHP
This commit is contained in:
+126
-74
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
/* ================ sha1.h ================ */
|
||||
/*
|
||||
SHA-1 in C
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
struct MySQLFieldInfo {
|
||||
String name;
|
||||
String table;
|
||||
|
||||
+45
-9
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
#include "sys.cpp"
|
||||
#include "uri.cpp"
|
||||
#include "compiler.cpp"
|
||||
#include "markdown.cpp"
|
||||
#include "mysql-connector.cpp"
|
||||
|
||||
+3
-2
@@ -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
@@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#include "uce_lib.h"
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
String var_dump(URI uri, String prefix = "", String postfix = "\n");
|
||||
String uri_decode(String q);
|
||||
|
||||
+181
-34
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user