Refactor FastCGI server and compiler, enhance HTTP header parsing, and add WebSocket support
- Refactored FastCGIServer class to improve socket handling and added shutdown functionality. - Updated compiler functions to streamline HTML and text literal processing. - Enhanced split_kv function to support uppercase keys and added split_http_headers for better HTTP header parsing. - Introduced new session management functions to validate and handle session IDs. - Added WebSocket frame parsing and handling in the URI module. - Created systemd service scripts for easier deployment and management of the UCE FastCGI runtime. - Added new test cases for header handling, time parsing, and WebSocket functionality.
This commit is contained in:
+301
-204
@@ -47,138 +47,150 @@
|
||||
|
||||
#include "../fastcgi_devkit/fastcgi.h"
|
||||
|
||||
|
||||
FastCGIServer::RequestInfo::RequestInfo() :
|
||||
params_closed(false),
|
||||
in_closed(false),
|
||||
output_closed(false)
|
||||
void
|
||||
FastCGIServer::shutdown()
|
||||
{
|
||||
|
||||
}
|
||||
if(getpid() != parent_pid) // if we're a child process, we must not close the handles
|
||||
return;
|
||||
|
||||
for (std::vector<int>::iterator it = server_sockets.begin();
|
||||
it != server_sockets.end(); ++it)
|
||||
{
|
||||
printf("Closing server socket %i\n", *it);
|
||||
close(*it);
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
for (std::vector<std::string>::iterator it = listen_unlink.begin();
|
||||
it != listen_unlink.end(); ++it)
|
||||
unlink(it->c_str());
|
||||
|
||||
FastCGIServer::Connection::Connection() :
|
||||
close_responsibility(false),
|
||||
close_socket(false)
|
||||
{
|
||||
for (std::map<int, Connection*>::iterator it = client_sockets.begin();
|
||||
it != client_sockets.end(); ++it)
|
||||
{
|
||||
close(it->first);
|
||||
for (RequestList::iterator req_it = it->second->requests.begin();
|
||||
req_it != it->second->requests.end(); ++req_it)
|
||||
delete req_it->second;
|
||||
delete it->second;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FastCGIServer::FastCGIServer()
|
||||
{
|
||||
sleep(1);
|
||||
server_sockets.clear();
|
||||
|
||||
}
|
||||
|
||||
FastCGIServer::~FastCGIServer()
|
||||
{
|
||||
|
||||
if(my_pid != parent_pid) // if we're a child process, we must not close the handles
|
||||
return;
|
||||
|
||||
for (std::vector<int>::iterator it = listen_sockets.begin();
|
||||
it != listen_sockets.end(); ++it)
|
||||
close(*it);
|
||||
|
||||
for (std::vector<std::string>::iterator it = listen_unlink.begin();
|
||||
it != listen_unlink.end(); ++it)
|
||||
unlink(it->c_str());
|
||||
|
||||
for (std::map<int, Connection*>::iterator it = read_sockets.begin();
|
||||
it != read_sockets.end(); ++it) {
|
||||
close(it->first);
|
||||
for (RequestList::iterator req_it = it->second->requests.begin();
|
||||
req_it != it->second->requests.end(); ++req_it)
|
||||
delete req_it->second;
|
||||
delete it->second;
|
||||
}
|
||||
|
||||
shutdown();
|
||||
}
|
||||
|
||||
int
|
||||
FastCGIServer::listen_http(unsigned tcp_port)
|
||||
{
|
||||
int server_socket = listen(tcp_port);
|
||||
server_socket_types[server_socket] = 'H';
|
||||
return server_socket;
|
||||
}
|
||||
|
||||
void
|
||||
int
|
||||
FastCGIServer::listen(unsigned tcp_port)
|
||||
{
|
||||
int listen_socket = socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (listen_socket == -1)
|
||||
throw std::runtime_error("socket() failed");
|
||||
int server_socket = socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (server_socket == -1)
|
||||
throw std::runtime_error("socket() failed");
|
||||
|
||||
try {
|
||||
struct sockaddr_in sa;
|
||||
bzero(&sa, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(tcp_port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bind(listen_socket, (struct sockaddr*)&sa, sizeof(sa)) == -1)
|
||||
throw std::runtime_error("bind() failed");
|
||||
|
||||
if (::listen(listen_socket, 100))
|
||||
throw std::runtime_error("listen() failed");
|
||||
int iSetOption = 1;
|
||||
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&iSetOption,
|
||||
sizeof(iSetOption));
|
||||
|
||||
listen_sockets.push_back(listen_socket);
|
||||
struct sockaddr_in sa;
|
||||
bzero(&sa, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(tcp_port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bind(server_socket, (struct sockaddr*)&sa, sizeof(sa)) == -1)
|
||||
throw std::runtime_error("bind() failed");
|
||||
|
||||
if (::listen(server_socket, 100))
|
||||
throw std::runtime_error("listen() failed");
|
||||
|
||||
server_sockets.push_back(server_socket);
|
||||
} catch (...) {
|
||||
close(listen_socket);
|
||||
throw;
|
||||
close(server_socket);
|
||||
throw;
|
||||
}
|
||||
|
||||
server_socket_types[server_socket] = 'F';
|
||||
printf("(P) listening to #%i port %i\n", server_socket, tcp_port);
|
||||
return server_socket;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
int
|
||||
FastCGIServer::listen(const std::string& local_path)
|
||||
{
|
||||
int listen_socket = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (listen_socket == -1)
|
||||
int server_socket = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (server_socket == -1)
|
||||
throw std::runtime_error("socket() failed");
|
||||
|
||||
try {
|
||||
struct sockaddr_un sa;
|
||||
bzero(&sa, sizeof(sa));
|
||||
sa.sun_family = AF_LOCAL;
|
||||
struct sockaddr_un sa;
|
||||
bzero(&sa, sizeof(sa));
|
||||
sa.sun_family = AF_LOCAL;
|
||||
|
||||
std::string::size_type size = local_path.size();
|
||||
if (size >= sizeof(sa.sun_path))
|
||||
throw std::runtime_error("path too long");
|
||||
if (local_path.find_first_of('\0') != std::string::npos)
|
||||
throw std::runtime_error("null character in path");
|
||||
std::string::size_type size = local_path.size();
|
||||
if (size >= sizeof(sa.sun_path))
|
||||
throw std::runtime_error("path too long");
|
||||
if (local_path.find_first_of('\0') != std::string::npos)
|
||||
throw std::runtime_error("null character in path");
|
||||
|
||||
std::memcpy(sa.sun_path, local_path.data(), size);
|
||||
std::memcpy(sa.sun_path, local_path.data(), size);
|
||||
|
||||
unlink(local_path.c_str());
|
||||
try {
|
||||
if (bind(listen_socket, (struct sockaddr*)&sa,
|
||||
sizeof(sa) - (sizeof(sa.sun_path) - size - 1)) == -1)
|
||||
throw std::runtime_error("bind() failed");
|
||||
unlink(local_path.c_str());
|
||||
try {
|
||||
if (bind(server_socket, (struct sockaddr*)&sa,
|
||||
sizeof(sa) - (sizeof(sa.sun_path) - size - 1)) == -1)
|
||||
throw std::runtime_error("bind() failed");
|
||||
|
||||
if (::listen(listen_socket, 100))
|
||||
throw std::runtime_error("listen() failed");
|
||||
if (::listen(server_socket, 100))
|
||||
throw std::runtime_error("listen() failed");
|
||||
|
||||
listen_sockets.push_back(listen_socket);
|
||||
listen_unlink.push_back(local_path);
|
||||
server_sockets.push_back(server_socket);
|
||||
listen_unlink.push_back(local_path);
|
||||
|
||||
printf("(P) listening to #%i socket %s\n", server_socket, local_path.c_str());
|
||||
|
||||
} catch (...) {
|
||||
unlink(local_path.c_str());
|
||||
throw;
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
unlink(local_path.c_str());
|
||||
throw;
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
close(listen_socket);
|
||||
throw;
|
||||
close(server_socket);
|
||||
throw;
|
||||
}
|
||||
return server_socket;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::abandon_files()
|
||||
int
|
||||
FastCGIServer::send_output_buffer(Connection& con)
|
||||
{
|
||||
listen_unlink.clear();
|
||||
int write_result = write(con.client_socket,
|
||||
con.output_buffer.data(),
|
||||
con.output_buffer.size());
|
||||
if (write_result == -1)
|
||||
throw std::runtime_error("write() failed");
|
||||
con.output_buffer.erase(0, write_result);
|
||||
return write_result;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::process(int timeout_ms)
|
||||
{
|
||||
char buffer[4096];
|
||||
char buffer[64*1024];
|
||||
fd_set fs_read;
|
||||
fd_set fs_write;
|
||||
int nfd = 0;
|
||||
@@ -187,16 +199,16 @@ FastCGIServer::process(int timeout_ms)
|
||||
FD_ZERO(&fs_read);
|
||||
FD_ZERO(&fs_write);
|
||||
|
||||
for(auto socket_handle : listen_sockets)
|
||||
for(auto socket_handle : server_sockets)
|
||||
{
|
||||
FD_SET(socket_handle, &fs_read);
|
||||
nfd = std::max(nfd, socket_handle);
|
||||
}
|
||||
|
||||
for(auto con : read_sockets)
|
||||
for(auto con : client_sockets)
|
||||
{
|
||||
FD_SET(con.first, &fs_read);
|
||||
if (!con.second->output_buffer.empty())
|
||||
if (!con.second->output_buffer.empty() || con.second->close_socket)
|
||||
FD_SET(con.first, &fs_write);
|
||||
nfd = std::max(nfd, con.first);
|
||||
}
|
||||
@@ -209,18 +221,29 @@ FastCGIServer::process(int timeout_ms)
|
||||
else
|
||||
throw std::runtime_error("select() failed");
|
||||
|
||||
for(auto socket_handle : listen_sockets)
|
||||
for(auto socket_handle : server_sockets)
|
||||
if (FD_ISSET(socket_handle, &fs_read))
|
||||
{
|
||||
int posix_con = accept(socket_handle, NULL, NULL);
|
||||
if (posix_con == -1)
|
||||
int client_socket = accept(socket_handle, NULL, NULL);
|
||||
if (client_socket == -1)
|
||||
throw std::runtime_error("accept() failed");
|
||||
read_sockets[posix_con] = new Connection();
|
||||
read_sockets[posix_con]->posix_con = posix_con;
|
||||
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 = read_sockets.begin();
|
||||
it != read_sockets.end();)
|
||||
for (std::map<int, Connection*>::iterator it = client_sockets.begin();
|
||||
it != client_sockets.end();)
|
||||
{
|
||||
int read_socket = it->first;
|
||||
|
||||
@@ -229,39 +252,62 @@ FastCGIServer::process(int timeout_ms)
|
||||
int read_result = read(read_socket, buffer, sizeof(buffer));
|
||||
if (read_result == -1)
|
||||
if (errno == ECONNRESET)
|
||||
goto close_socket;
|
||||
goto close_socket;
|
||||
else
|
||||
throw std::runtime_error("read() on socket failed");
|
||||
if (read_result == 0)
|
||||
it->second->close_socket = true;
|
||||
else {
|
||||
throw std::runtime_error("read() on socket failed");
|
||||
if (read_result == 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
it->second->close_socket = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
it->second->input_buffer.append(buffer, read_result);
|
||||
process_connection_read(*it->second);
|
||||
if(it->second->type == 'H')
|
||||
{
|
||||
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
|
||||
{
|
||||
read_fgci(*it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!it->second->output_buffer.empty() &&
|
||||
FD_ISSET(read_socket, &fs_write))
|
||||
{
|
||||
process_connection_write(*it->second);
|
||||
int write_result = write(read_socket,
|
||||
it->second->output_buffer.data(),
|
||||
it->second->output_buffer.size());
|
||||
if (write_result == -1)
|
||||
throw std::runtime_error("write() failed");
|
||||
it->second->output_buffer.erase(0, write_result);
|
||||
write_fgci(*it->second);
|
||||
send_output_buffer(*it->second);
|
||||
}
|
||||
|
||||
if (it->second->close_socket && it->second->output_buffer.empty())
|
||||
{
|
||||
close_socket:
|
||||
printf("Closing socket %i\n", it->first);
|
||||
int close_result = close(it->first);
|
||||
if (close_result == -1 && errno != ECONNRESET)
|
||||
throw std::runtime_error("close() failed");
|
||||
Connection* connection = it->second;
|
||||
read_sockets.erase(it++);
|
||||
client_sockets.erase(it++);
|
||||
delete connection;
|
||||
if(calls_until_termination != -1 && read_sockets.size() == 0)
|
||||
if(calls_until_termination != -1 && client_sockets.size() == 0)
|
||||
{
|
||||
calls_until_termination -= 1;
|
||||
if(calls_until_termination <= 0)
|
||||
@@ -272,6 +318,54 @@ FastCGIServer::process(int timeout_ms)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FastCGIServer::process_http_request(FastCGIRequest& request, String& data)
|
||||
{
|
||||
auto header_end = data.find("\r\n\r\n");
|
||||
if(header_end == String::npos)
|
||||
return;
|
||||
|
||||
if(request.params.size() == 0)
|
||||
{
|
||||
request.params = split_http_headers(data.substr(0, header_end));
|
||||
request.flags.params_closed = true;
|
||||
if(request.params["HTTP_SCRIPT_FILENAME"] != "")
|
||||
request.params["SCRIPT_FILENAME"] = request.params["HTTP_SCRIPT_FILENAME"];
|
||||
else if(request.params["SCRIPT_FILENAME"] == "" && request.params["DOCUMENT_URI"] != "")
|
||||
{
|
||||
String document_root = first(request.params["DOCUMENT_ROOT"], get_cwd());
|
||||
if(document_root.length() > 1 && document_root[document_root.length()-1] == '/')
|
||||
document_root.resize(document_root.length()-1);
|
||||
request.params["DOCUMENT_ROOT"] = document_root;
|
||||
request.params["SCRIPT_FILENAME"] = document_root + request.params["DOCUMENT_URI"];
|
||||
}
|
||||
}
|
||||
|
||||
request.in = data.substr(header_end + 4);
|
||||
u64 content_length = int_val(first(request.params["CONTENT_LENGTH"], "0"));
|
||||
if(request.in.length() < content_length)
|
||||
return;
|
||||
request.flags.input_closed = true;
|
||||
|
||||
if(request.params["HTTP_UPGRADE"] == "websocket")
|
||||
{
|
||||
printf("(i) got upgrade request %s\n", var_dump(request.params).c_str());
|
||||
client_sockets[request.resources.client_socket]->close_socket = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
on_complete(request);
|
||||
|
||||
assemble_output_buffer(
|
||||
request,
|
||||
client_sockets[request.resources.client_socket]);
|
||||
|
||||
printf("data written: %i bytes\n",
|
||||
client_sockets[request.resources.client_socket]->output_buffer.size());
|
||||
|
||||
client_sockets[request.resources.client_socket]->close_socket = true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FastCGIServer::process_forever()
|
||||
@@ -280,18 +374,8 @@ FastCGIServer::process_forever()
|
||||
process();
|
||||
}
|
||||
|
||||
int
|
||||
FastCGIServer::call_completion_handler(FastCGIRequest& request)
|
||||
{
|
||||
//printf("- request complete\n");
|
||||
////switch_to_arena(request.mem);
|
||||
auto result = on_complete(request);
|
||||
////switch_to_system_alloc();
|
||||
return(result);
|
||||
}
|
||||
|
||||
void
|
||||
FastCGIServer::process_connection_read(Connection& connection)
|
||||
FastCGIServer::read_fgci(Connection& connection)
|
||||
{
|
||||
std::string::size_type n = 0;
|
||||
while (connection.input_buffer.size() - n >= FCGI_HEADER_LEN) {
|
||||
@@ -316,7 +400,7 @@ FastCGIServer::process_connection_read(Connection& connection)
|
||||
{
|
||||
case FCGI_GET_VALUES:
|
||||
{
|
||||
Pairs pairs = parse_pairs(content, content_length);
|
||||
Pairs pairs = parse_pairs_fcgi(content, content_length);
|
||||
|
||||
std::string::size_type base = connection.output_buffer.size();
|
||||
connection.output_buffer.push_back(FCGI_VERSION_1);
|
||||
@@ -325,13 +409,13 @@ FastCGIServer::process_connection_read(Connection& connection)
|
||||
|
||||
for (Pairs::iterator it = pairs.begin(); it != pairs.end(); ++it)
|
||||
if (it->first == FCGI_MAX_CONNS)
|
||||
write_pair(connection.output_buffer,
|
||||
write_pair_fcgi(connection.output_buffer,
|
||||
it->first, std::string("100"));
|
||||
else if (it->first == FCGI_MAX_REQS)
|
||||
write_pair(connection.output_buffer,
|
||||
write_pair_fcgi(connection.output_buffer,
|
||||
it->first, std::string("1000"));
|
||||
else if (it->first == FCGI_MPXS_CONNS)
|
||||
write_pair(connection.output_buffer,
|
||||
write_pair_fcgi(connection.output_buffer,
|
||||
it->first, std::string("1"));
|
||||
|
||||
std::string::size_type len = connection.output_buffer.size() - base;
|
||||
@@ -361,7 +445,7 @@ FastCGIServer::process_connection_read(Connection& connection)
|
||||
connection.output_buffer.append(
|
||||
reinterpret_cast<const char*>(&unknown), sizeof(unknown));
|
||||
if (connection.close_responsibility)
|
||||
connection.close_socket = true;
|
||||
connection.close_socket = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -382,12 +466,10 @@ FastCGIServer::process_connection_read(Connection& connection)
|
||||
printf("(!) %i requests in flight at the same time!\n", connection.requests.size());
|
||||
}
|
||||
|
||||
//switch_to_arena(request_arena);
|
||||
RequestInfo* new_request = new RequestInfo();
|
||||
new_request->resources.fcgi_socket = connection.posix_con;
|
||||
//new_request->mem = request_arena;
|
||||
FastCGIRequest* new_request = new FastCGIRequest();
|
||||
new_request->resources.client_socket = connection.client_socket;
|
||||
new_request->resources.server_socket = connection.server_socket;
|
||||
new_request->stats.time_init = microtime();
|
||||
//switch_to_system_alloc();
|
||||
connection.requests[request_id] = new_request;
|
||||
|
||||
break;
|
||||
@@ -420,25 +502,26 @@ FastCGIServer::process_connection_read(Connection& connection)
|
||||
if (it == connection.requests.end())
|
||||
break;
|
||||
|
||||
RequestInfo& request = *it->second;
|
||||
FastCGIRequest& request = *it->second;
|
||||
//switch_to_arena(it->second->mem);
|
||||
if (!request.params_closed)
|
||||
if (!request.flags.params_closed)
|
||||
if (content_length != 0)
|
||||
request.params_buffer.append(content, content_length);
|
||||
request.resources.params_buffer.append(content, content_length);
|
||||
else {
|
||||
request.params = parse_pairs(request.params_buffer.data(),
|
||||
request.params_buffer.size());
|
||||
request.params_buffer.clear();
|
||||
request.params_closed = true;
|
||||
|
||||
request.status = on_request(request);
|
||||
if (request.status == 0 && !request.in.empty())
|
||||
{
|
||||
request.status = on_data(request);
|
||||
if (request.status == 0 && request.in_closed)
|
||||
request.status = call_completion_handler(request);
|
||||
}
|
||||
process_write_request(connection, request_id, request);
|
||||
request.params = parse_pairs_fcgi(
|
||||
request.resources.params_buffer.data(),
|
||||
request.resources.params_buffer.size());
|
||||
request.resources.params_buffer.clear();
|
||||
request.flags.params_closed = true;
|
||||
//std::cout << "Params " << var_dump(request.params) << "\n";
|
||||
request.flags.status = on_request(request);
|
||||
if (request.flags.status == 0 && !request.in.empty())
|
||||
{
|
||||
request.flags.status = on_data(request);
|
||||
if (request.flags.status == 0 && request.flags.input_closed)
|
||||
request.flags.status = on_complete(request);
|
||||
}
|
||||
request_write_fgci(connection, request_id, request);
|
||||
}
|
||||
//switch_to_system_alloc();
|
||||
break;
|
||||
@@ -449,22 +532,22 @@ FastCGIServer::process_connection_read(Connection& connection)
|
||||
if (it == connection.requests.end())
|
||||
break;
|
||||
|
||||
RequestInfo& request = *it->second;
|
||||
FastCGIRequest& request = *it->second;
|
||||
//switch_to_arena(it->second->mem);
|
||||
if (!request.in_closed)
|
||||
if (!request.flags.input_closed)
|
||||
if (content_length != 0) {
|
||||
request.in.append(content, content_length);
|
||||
if (request.params_closed && request.status == 0)
|
||||
{
|
||||
request.status = on_data(request);
|
||||
process_write_request(connection, request_id, request);
|
||||
}
|
||||
request.in.append(content, content_length);
|
||||
if (request.flags.params_closed && request.flags.status == 0)
|
||||
{
|
||||
request.flags.status = on_data(request);
|
||||
request_write_fgci(connection, request_id, request);
|
||||
}
|
||||
} else {
|
||||
request.in_closed = true;
|
||||
if (request.params_closed && request.status == 0) {
|
||||
request.status = call_completion_handler(request);
|
||||
process_write_request(connection, request_id, request);
|
||||
}
|
||||
request.flags.input_closed = true;
|
||||
if (request.flags.params_closed && request.flags.status == 0) {
|
||||
request.flags.status = on_complete(request);
|
||||
request_write_fgci(connection, request_id, request);
|
||||
}
|
||||
}
|
||||
//switch_to_system_alloc();
|
||||
break;
|
||||
@@ -490,56 +573,70 @@ FastCGIServer::process_connection_read(Connection& connection)
|
||||
connection.input_buffer.erase(0, n);
|
||||
}
|
||||
|
||||
void
|
||||
FastCGIServer::assemble_output_buffer(FastCGIRequest& request, Connection* connection)
|
||||
{
|
||||
request.out =
|
||||
request.response_code+"\r\n"+
|
||||
var_dump(request.header, "", "\r\n") +
|
||||
var_dump(request.set_cookies, "", "\r\n") +
|
||||
"\r\n";
|
||||
|
||||
for(auto obs : request.ob_stack)
|
||||
{
|
||||
request.out += obs->str();
|
||||
delete obs;
|
||||
}
|
||||
request.ob_stack.clear();
|
||||
request.flags.output_closed = true;
|
||||
request.stats.time_end = microtime();
|
||||
if(request.flags.log_request)
|
||||
printf("(r) pid:%i\t%s\t%0.6fs\tfps:%0.0f\tout:%0.1fkB\tmem:%0.0f/%0.0fkB\n",
|
||||
my_pid,
|
||||
request.params["REQUEST_URI"].c_str(),
|
||||
request.stats.time_end - request.stats.time_start,
|
||||
1.0 / (request.stats.time_end - request.stats.time_start),
|
||||
(f32)(request.out.length()/1024),
|
||||
(f32)(request.stats.mem_high/1024),
|
||||
(f32)(request.stats.mem_alloc/1024)
|
||||
);
|
||||
|
||||
if(connection)
|
||||
{
|
||||
connection->output_buffer.clear();
|
||||
connection->output_buffer.append(request.out);
|
||||
request.out.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
FastCGIServer::process_write_request(Connection& connection, RequestID id,
|
||||
RequestInfo& request)
|
||||
FastCGIServer::request_write_fgci(Connection& connection, RequestID id,
|
||||
FastCGIRequest& request)
|
||||
{
|
||||
if (!request.out.empty())
|
||||
{
|
||||
write_data(connection.output_buffer, id, request.out, FCGI_STDOUT);
|
||||
write_data_fcgi(connection.output_buffer, id, request.out, FCGI_STDOUT);
|
||||
//switch_to_arena(request.mem);
|
||||
request.out.clear();
|
||||
//switch_to_system_alloc();
|
||||
}
|
||||
if (!request.err.empty())
|
||||
{
|
||||
write_data(connection.output_buffer, id, request.err, FCGI_STDERR);
|
||||
write_data_fcgi(connection.output_buffer, id, request.err, FCGI_STDERR);
|
||||
//switch_to_arena(request.mem);
|
||||
request.err.clear();
|
||||
//switch_to_system_alloc();
|
||||
}
|
||||
if ((request.in_closed || request.status != 0) &&
|
||||
!request.output_closed)
|
||||
if ((request.flags.input_closed || request.flags.status != 0) &&
|
||||
!request.flags.output_closed)
|
||||
{
|
||||
//switch_to_arena(request.mem);
|
||||
request.out =
|
||||
var_dump(request.header, "", "\r\n") +
|
||||
var_dump(request.set_cookies, "", "\r\n") +
|
||||
"\r\n";
|
||||
|
||||
for(auto obs : request.ob_stack)
|
||||
{
|
||||
request.out += obs->str();
|
||||
delete obs;
|
||||
}
|
||||
request.ob_stack.clear();
|
||||
assemble_output_buffer(request);
|
||||
|
||||
//switch_to_system_alloc();
|
||||
write_data(connection.output_buffer, id, request.out, FCGI_STDOUT);
|
||||
write_data(connection.output_buffer, id, request.err, FCGI_STDERR);
|
||||
|
||||
request.stats.time_end = microtime();
|
||||
if(request.flags.log_request)
|
||||
printf("(r) pid:%i\t%s\t%0.6fs\tfps:%0.0f\tout:%0.1fkB\tmem:%0.0f/%0.0fkB\n",
|
||||
my_pid,
|
||||
request.params["REQUEST_URI"].c_str(),
|
||||
request.stats.time_end - request.stats.time_start,
|
||||
1.0 / (request.stats.time_end - request.stats.time_start),
|
||||
(f32)(request.out.length()/1024),
|
||||
(f32)(request.stats.mem_high/1024),
|
||||
(f32)(request.stats.mem_alloc/1024)
|
||||
);
|
||||
write_data_fcgi(connection.output_buffer, id, request.out, FCGI_STDOUT);
|
||||
write_data_fcgi(connection.output_buffer, id, request.err, FCGI_STDERR);
|
||||
|
||||
FCGI_EndRequestRecord complete;
|
||||
bzero(&complete, sizeof(complete));
|
||||
@@ -548,17 +645,17 @@ FastCGIServer::process_write_request(Connection& connection, RequestID id,
|
||||
complete.header.requestIdB1 = (id >> 8) & 0xff;
|
||||
complete.header.requestIdB0 = id & 0xff;
|
||||
complete.header.contentLengthB0 = sizeof(complete.body);
|
||||
complete.body.appStatusB3 = (request.status >> 24) & 0xff;
|
||||
complete.body.appStatusB2 = (request.status >> 16) & 0xff;
|
||||
complete.body.appStatusB1 = (request.status >> 8) & 0xff;
|
||||
complete.body.appStatusB0 = request.status & 0xff;
|
||||
complete.body.appStatusB3 = (request.flags.status >> 24) & 0xff;
|
||||
complete.body.appStatusB2 = (request.flags.status >> 16) & 0xff;
|
||||
complete.body.appStatusB1 = (request.flags.status >> 8) & 0xff;
|
||||
complete.body.appStatusB0 = request.flags.status & 0xff;
|
||||
complete.body.protocolStatus = FCGI_REQUEST_COMPLETE;
|
||||
connection.output_buffer.append(
|
||||
reinterpret_cast<const char*>(&complete), sizeof(complete));
|
||||
if (connection.close_responsibility)
|
||||
connection.close_socket = true;
|
||||
|
||||
request.output_closed = true;
|
||||
|
||||
//printf("- output done\n");
|
||||
}
|
||||
//switch_to_system_alloc();
|
||||
@@ -566,16 +663,16 @@ FastCGIServer::process_write_request(Connection& connection, RequestID id,
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::process_connection_write(Connection& connection)
|
||||
FastCGIServer::write_fgci(Connection& connection)
|
||||
{
|
||||
for (RequestList::iterator it = connection.requests.begin();
|
||||
it != connection.requests.end();)
|
||||
{
|
||||
process_write_request(connection, it->first, *it->second);
|
||||
if (it->second->params_closed && it->second->in_closed)
|
||||
request_write_fgci(connection, it->first, *it->second);
|
||||
if (it->second->flags.params_closed && it->second->flags.input_closed)
|
||||
{
|
||||
//switch_to_arena(it->second->mem);
|
||||
//printf("- process_connection_write close\n");
|
||||
//printf("- write_fgci close\n");
|
||||
delete it->second;
|
||||
//switch_to_system_alloc();
|
||||
connection.requests.erase(it++);
|
||||
@@ -587,7 +684,7 @@ FastCGIServer::process_connection_write(Connection& connection)
|
||||
|
||||
|
||||
FastCGIServer::Pairs
|
||||
FastCGIServer::parse_pairs(const char* data, std::string::size_type n)
|
||||
FastCGIServer::parse_pairs_fcgi(const char* data, std::string::size_type n)
|
||||
{
|
||||
Pairs pairs;
|
||||
|
||||
@@ -633,7 +730,7 @@ FastCGIServer::parse_pairs(const char* data, std::string::size_type n)
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::write_pair(std::string& buffer,
|
||||
FastCGIServer::write_pair_fcgi(std::string& buffer,
|
||||
const std::string& key, const std::string& value)
|
||||
{
|
||||
if (key.size() > 0x7f) {
|
||||
@@ -658,7 +755,7 @@ FastCGIServer::write_pair(std::string& buffer,
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::write_data(std::string& buffer, RequestID id,
|
||||
FastCGIServer::write_data_fcgi(std::string& buffer, RequestID id,
|
||||
const std::string& input, unsigned char type)
|
||||
{
|
||||
FCGI_Header header;
|
||||
|
||||
+23
-33
@@ -41,65 +41,55 @@
|
||||
|
||||
class FastCGIServer {
|
||||
public:
|
||||
FastCGIServer();
|
||||
~FastCGIServer();
|
||||
void shutdown();
|
||||
|
||||
// called when the parameters and standard input have been receieved
|
||||
std::function<int(FastCGIRequest&)> on_request = 0;
|
||||
std::function<int(FastCGIRequest&)> on_data = 0;
|
||||
std::function<int(FastCGIRequest&)> on_complete = 0;
|
||||
|
||||
void listen(unsigned tcp_port);
|
||||
void listen(const std::string& local_path);
|
||||
void abandon_files();
|
||||
int listen(unsigned tcp_port);
|
||||
int listen_http(unsigned tcp_port);
|
||||
int listen(const std::string& local_path);
|
||||
|
||||
void process(int timeout_ms = -1); // timeout_ms<0 blocks forever
|
||||
void process_forever();
|
||||
int calls_until_termination = 8; // set this to -1 to never terminate
|
||||
|
||||
int call_completion_handler(FastCGIRequest& request);
|
||||
|
||||
protected:
|
||||
struct RequestInfo : FastCGIRequest {
|
||||
RequestInfo();
|
||||
|
||||
std::string params_buffer;
|
||||
bool params_closed;
|
||||
bool in_closed;
|
||||
int status;
|
||||
bool output_closed;
|
||||
|
||||
friend class FastCGIServer;
|
||||
};
|
||||
|
||||
typedef unsigned RequestID;
|
||||
typedef std::map<RequestID, RequestInfo*> RequestList;
|
||||
struct Connection {
|
||||
Connection();
|
||||
typedef std::map<RequestID, FastCGIRequest*> RequestList;
|
||||
|
||||
struct Connection {
|
||||
RequestList requests;
|
||||
u64 posix_con = 0;
|
||||
u64 client_socket = 0;
|
||||
u64 server_socket = 0;
|
||||
std::string input_buffer;
|
||||
std::string output_buffer;
|
||||
bool close_responsibility;
|
||||
bool close_socket;
|
||||
bool close_responsibility = false;
|
||||
bool close_socket = false;
|
||||
char type = 'F'; // F = FastCGI, H = HttpServer
|
||||
};
|
||||
|
||||
typedef StringMap Pairs;
|
||||
|
||||
std::vector<int> listen_sockets;
|
||||
std::vector<int> server_sockets;
|
||||
std::vector<std::string> listen_unlink;
|
||||
|
||||
std::map<int, Connection*> read_sockets;
|
||||
std::map<int, char> server_socket_types;
|
||||
std::map<int, Connection*> client_sockets;
|
||||
|
||||
void process_connection_read(Connection&);
|
||||
static void process_write_request(Connection&, RequestID, RequestInfo&);
|
||||
static void process_connection_write(Connection&);
|
||||
static Pairs parse_pairs(const char*, std::string::size_type);
|
||||
static void write_pair(std::string& buffer,
|
||||
void read_fgci(Connection&);
|
||||
static void request_write_fgci(Connection&, RequestID, FastCGIRequest&);
|
||||
static void write_fgci(Connection&);
|
||||
static Pairs parse_pairs_fcgi(const char*, std::string::size_type);
|
||||
static void write_pair_fcgi(std::string& buffer,
|
||||
const std::string& key, const std::string&);
|
||||
static void write_data(std::string& buffer, RequestID id,
|
||||
static void write_data_fcgi(std::string& buffer, RequestID id,
|
||||
const std::string& input, unsigned char type);
|
||||
static void assemble_output_buffer(FastCGIRequest& request, Connection* connection = 0);
|
||||
int send_output_buffer(Connection& con);
|
||||
void process_http_request(FastCGIRequest& request, String& data);
|
||||
|
||||
};
|
||||
|
||||
|
||||
+23
-36
@@ -1,7 +1,7 @@
|
||||
#include "compiler.h"
|
||||
#include <sys/file.h>
|
||||
|
||||
String process_html_literal(Request* context, SharedUnit* su, String content)
|
||||
String process_text_literal(Request* context, SharedUnit* su, String content)
|
||||
{
|
||||
String pc;
|
||||
String HT_START = "print(R\"(";
|
||||
@@ -89,7 +89,12 @@ String process_html_literal(Request* context, SharedUnit* su, String content)
|
||||
|
||||
String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String content)
|
||||
{
|
||||
String pc = "#include \"" + su->src_file_name + ".setup.h" + "\"\n#line 1\n";
|
||||
String pc =
|
||||
("#include \"")+context->server->config["COMPILER_SYS_PATH"] +"/src/lib/uce_lib.h\" \n"+
|
||||
file_get_contents(
|
||||
context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"]
|
||||
)+
|
||||
"#line 1\n";
|
||||
String token = "";
|
||||
String html_buffer = "";
|
||||
u8 mode = 0;
|
||||
@@ -100,29 +105,13 @@ String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String
|
||||
{
|
||||
char c = content[i];
|
||||
current_line.append(1, c);
|
||||
if(mode == 2)
|
||||
if(mode == 1)
|
||||
{
|
||||
auto end_pos = content.find(String("</")+token+">", i);
|
||||
if(end_pos != String::npos)
|
||||
if(c == '<' && (content[i+1] == '/') && (content[i+2] == '>'))
|
||||
{
|
||||
u32 len = token.length() + 3 + end_pos - i;
|
||||
html_buffer.append(content.substr(i, len - (token.length() == 0 ? 3 : 0)));
|
||||
i += len - 1;
|
||||
pc.append(process_html_literal(context, su, html_buffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("(!) unterminated HTML literal <%s> in %s", token.c_str(), su->file_name.c_str());
|
||||
}
|
||||
mode = 0;
|
||||
}
|
||||
else if(mode == 1)
|
||||
{
|
||||
if(isspace(c) || c == '>')
|
||||
{
|
||||
mode = 2;
|
||||
if(token.length() > 0)
|
||||
html_buffer.append(1, c);
|
||||
i += 2;
|
||||
pc.append(process_text_literal(context, su, html_buffer));
|
||||
mode = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -130,13 +119,12 @@ String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String
|
||||
html_buffer.append(1, c);
|
||||
}
|
||||
}
|
||||
else if(!inside_quote && c == '<' && (content[i+1] == '>'/* || isalpha(content[i+1])*/))
|
||||
else if(!inside_quote && c == '<' && (content[i+1] == '>'))
|
||||
{
|
||||
mode = 1;
|
||||
token = "";
|
||||
html_buffer = "";
|
||||
if(content[i+1] != '>')
|
||||
html_buffer.append(1, c);
|
||||
i += 1;
|
||||
}
|
||||
else if(c == '\"')
|
||||
{
|
||||
@@ -206,7 +194,7 @@ void setup_unit_paths(Request* context, SharedUnit* su, String file_name)
|
||||
|
||||
su->so_name = su->bin_path + "/" + su->bin_file_name;
|
||||
su->api_file_name = su->bin_path + "/" + su->src_file_name + ".exports.txt";
|
||||
su->setup_file_name = su->bin_path + "/" + su->src_file_name + ".setup.h";
|
||||
//su->setup_file_name = su->bin_path + "/" + su->src_file_name + ".setup.h";
|
||||
}
|
||||
|
||||
void load_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
@@ -245,7 +233,7 @@ void load_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
}
|
||||
}
|
||||
|
||||
String compile_setup_file(Request* context, SharedUnit* su)
|
||||
/*String compile_setup_file(Request* context, SharedUnit* su)
|
||||
{
|
||||
String result =
|
||||
String("#ifndef UCE_LIB_INCLUDED\n") +
|
||||
@@ -254,17 +242,13 @@ String compile_setup_file(Request* context, SharedUnit* su)
|
||||
file_get_contents(
|
||||
context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"]) +
|
||||
"#endif \n";
|
||||
StringList declarations;
|
||||
StringList load_units;
|
||||
|
||||
result = replace(result, "/*load_declarations*/", join(declarations, "\n"));
|
||||
result = replace(result, "/*load_units*/", join(load_units, "\n"));
|
||||
return(result);
|
||||
}
|
||||
}*/
|
||||
|
||||
void compile_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
{
|
||||
//setup_unit_paths(context, su, file_name);
|
||||
f64 comp_start = microtime();
|
||||
|
||||
if(!file_exists(su->file_name))
|
||||
{
|
||||
@@ -274,7 +258,7 @@ void compile_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
|
||||
shell_exec("mkdir -p " + shell_escape(su->pre_path));
|
||||
file_put_contents(su->pre_path + "/" + su->pre_file_name, preprocess_shared_unit(context, su));
|
||||
file_put_contents(su->setup_file_name, compile_setup_file(context, su));
|
||||
//file_put_contents(su->setup_file_name, compile_setup_file(context, su));
|
||||
file_put_contents(su->api_file_name, join(su->api_declarations, "\n"));
|
||||
|
||||
if(!su->opt_so_optional)
|
||||
@@ -293,7 +277,9 @@ void compile_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
else
|
||||
{
|
||||
load_shared_unit(context, su, file_name);
|
||||
//printf("(i) compiled unit %s\n", file_name.c_str());
|
||||
printf("(i) compiled unit %s in %f s\n",
|
||||
(su->pre_path + "/" + su->pre_file_name).c_str(),
|
||||
microtime() - comp_start);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,6 +376,7 @@ SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String
|
||||
|
||||
void compiler_invoke(Request* context, String file_name, DTree& call_param)
|
||||
{
|
||||
printf("(i) compiler_invoke file %s\n", file_name.c_str());
|
||||
auto su = compiler_load_shared_unit(context, file_name, "", false);
|
||||
if(su)
|
||||
{
|
||||
|
||||
+69
-17
@@ -6,7 +6,7 @@ String var_dump(StringMap map, String prefix, String postfix)
|
||||
|
||||
for (auto it = map.begin(); it != map.end(); ++it)
|
||||
{
|
||||
result.append(prefix + it->first + ": " + it->second + postfix);
|
||||
result.append(prefix + to_upper(it->first) + ": " + it->second + postfix);
|
||||
}
|
||||
|
||||
return(result);
|
||||
@@ -42,25 +42,15 @@ u8 hex_to_u8(String src)
|
||||
|
||||
String to_lower(String s)
|
||||
{
|
||||
String result = "";
|
||||
for(auto c : s)
|
||||
{
|
||||
if(c >= 'A' && c <= 'Z')
|
||||
c = tolower(c);
|
||||
result.append(1, c);
|
||||
}
|
||||
String result = s;
|
||||
std::transform(result.begin(), result.end(),result.begin(), ::tolower);
|
||||
return(result);
|
||||
}
|
||||
|
||||
String to_upper(String s)
|
||||
{
|
||||
String result = "";
|
||||
for(auto c : s)
|
||||
{
|
||||
if(c >= 'A' && c <= 'Z')
|
||||
c = toupper(c);
|
||||
result.append(1, c);
|
||||
}
|
||||
String result = s;
|
||||
std::transform(result.begin(), result.end(),result.begin(), ::toupper);
|
||||
return(result);
|
||||
}
|
||||
|
||||
@@ -140,7 +130,7 @@ StringList split(String str, String delim)
|
||||
return(result);
|
||||
}
|
||||
|
||||
StringMap split_kv(String s, char separator, bool trim_whitespace)
|
||||
StringMap split_kv(String s, char separator, bool trim_whitespace, bool uppercase_keys)
|
||||
{
|
||||
StringMap result;
|
||||
String k;
|
||||
@@ -157,7 +147,7 @@ StringMap split_kv(String s, char separator, bool trim_whitespace)
|
||||
if(c == separator)
|
||||
mode = 1;
|
||||
else
|
||||
k.append(1, c);
|
||||
k.append(1, uppercase_keys ? toupper(c) : c);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -175,6 +165,68 @@ StringMap split_kv(String s, char separator, bool trim_whitespace)
|
||||
return(result);
|
||||
}
|
||||
|
||||
StringMap split_http_headers(String s)
|
||||
{
|
||||
StringMap result;
|
||||
String k;
|
||||
String v;
|
||||
String query_string;
|
||||
String base_uri;
|
||||
for(auto s : split(s, "\n"))
|
||||
{
|
||||
u8 mode = 0;
|
||||
k = "";
|
||||
v = "";
|
||||
for(auto c : s)
|
||||
{
|
||||
if(mode == 0)
|
||||
{
|
||||
if(c == ':')
|
||||
mode = 1;
|
||||
else
|
||||
k.append(1, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
v.append(1, c);
|
||||
}
|
||||
}
|
||||
if(k != "")
|
||||
{
|
||||
k = trim(k);
|
||||
v = trim(v);
|
||||
if(v == "")
|
||||
{
|
||||
if(result["REQUEST_METHOD"] == "")
|
||||
{
|
||||
result["REQUEST_METHOD"] = nibble(k, " ");
|
||||
result["REQUEST_URI"] = nibble(k, " ");
|
||||
String query_string = result["REQUEST_URI"];
|
||||
String base_uri = nibble(query_string, "?");
|
||||
result["SERVER_PROTOCOL"] = k;
|
||||
result["SCRIPT_NAME"] = base_uri;
|
||||
result["DOCUMENT_URI"] = base_uri;
|
||||
result["QUERY_STRING"] = query_string;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(k != "")
|
||||
result["_"] = k;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
String header_key = to_upper(k);
|
||||
std::replace(header_key.begin(), header_key.end(), '-', '_');
|
||||
result["HTTP_"+header_key] = v;
|
||||
if(header_key == "CONTENT_TYPE" || header_key == "CONTENT_LENGTH")
|
||||
result[header_key] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
String join(StringList l, String delim)
|
||||
{
|
||||
String result;
|
||||
|
||||
@@ -11,7 +11,8 @@ String trim(String raw);
|
||||
StringList split_space(String str);
|
||||
StringList split(String str, String delim);
|
||||
StringList split_utf8(String s, bool compound_characters = false);
|
||||
StringMap split_kv(String s, char separator = '=', bool trim_whitespace = true);
|
||||
StringMap split_kv(String s, char separator = '=', bool trim_whitespace = true, bool uppercase_keys = false);
|
||||
StringMap split_http_headers(String s);
|
||||
String join(StringList l, String delim = "\n");
|
||||
String nibble(String& haystack, String delim);
|
||||
void json_consume_space(String s, u32& i);
|
||||
@@ -84,9 +85,10 @@ String var_dump(StringMap map, String prefix = "", String postfix = "\n");
|
||||
String var_dump(StringList slist, String prefix = "", String postfix = "\n");
|
||||
|
||||
void ob_start();
|
||||
void ob_clear();
|
||||
String ob_get_clear();
|
||||
void ob_close();
|
||||
String ob_get();
|
||||
String ob_get_close();
|
||||
|
||||
String safe_name(String raw);
|
||||
|
||||
#define is_bit_set(var,pos) ((var) & (1<<(pos)))
|
||||
|
||||
+31
-7
@@ -135,14 +135,28 @@ String file_get_contents(String file_name)
|
||||
|
||||
bool file_put_contents(String file_name, String content)
|
||||
{
|
||||
s32 fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC);
|
||||
s32 fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if(fd == -1)
|
||||
{
|
||||
printf("(!) Could not write %s\n", file_name.c_str());
|
||||
return(false);
|
||||
}
|
||||
flock(fd, LOCK_EX);
|
||||
write(fd, content.data(), content.length());
|
||||
const char* data = content.data();
|
||||
size_t remaining = content.length();
|
||||
while(remaining > 0)
|
||||
{
|
||||
auto bytes_written = write(fd, data, remaining);
|
||||
if(bytes_written < 0)
|
||||
{
|
||||
flock(fd, LOCK_UN);
|
||||
close(fd);
|
||||
printf("(!) Could not fully write %s\n", file_name.c_str());
|
||||
return(false);
|
||||
}
|
||||
data += bytes_written;
|
||||
remaining -= bytes_written;
|
||||
}
|
||||
flock(fd, LOCK_UN);
|
||||
close(fd);
|
||||
return(true);
|
||||
@@ -242,7 +256,7 @@ String gmdate(String format, u64 timestamp)
|
||||
|
||||
u64 parse_time(String time_String)
|
||||
{
|
||||
return(int_val(trim(shell_exec("date -u -d '"+time_String+"' +'%s'"))));
|
||||
return(int_val(trim(shell_exec("date -u -d "+shell_escape(time_String)+" +'%s'"))));
|
||||
}
|
||||
|
||||
u64 socket_connect(String host, short port)
|
||||
@@ -475,8 +489,8 @@ pid_t task(String key, std::function<void()> exec_after_spawn, u64 timeout)
|
||||
my_pid = getpid();
|
||||
file_put_contents(status_file_name, std::to_string(my_pid));
|
||||
|
||||
close(context->resources.fcgi_socket);
|
||||
context->resources.fcgi_socket = 0;
|
||||
close(context->resources.client_socket);
|
||||
context->resources.client_socket = 0;
|
||||
//printf("(C) child procress started, PID:%i\n", my_pid);
|
||||
//prctl(PR_SET_PDEATHSIG, SIGHUP);
|
||||
exec_after_spawn();
|
||||
@@ -534,13 +548,13 @@ StringMap make_server_settings()
|
||||
cfg["SETUP_TEMPLATE"] = "scripts/setup.h.template";
|
||||
cfg["LIT_ESC"] = "3d5b5_1";
|
||||
cfg["CONTENT_TYPE"] = "text/html; charset=utf-8";
|
||||
cfg["SOCKET_PATH"] = "/run/uce.sock";
|
||||
cfg["FCGI_SOCKET_PATH"] = "/run/uce.sock";
|
||||
cfg["TMP_UPLOAD_PATH"] = "/tmp/uce/uploads";
|
||||
cfg["SESSION_PATH"] = "/tmp/uce/sessions";
|
||||
cfg["COMPILER_SYS_PATH"] = ".";
|
||||
cfg["PRECOMPILE_FILES_IN"] = ".";
|
||||
|
||||
cfg["LISTEN_PORT"] = std::to_string(9993);
|
||||
cfg["HTTP_PORT"] = std::to_string(8080);
|
||||
cfg["SESSION_TIME"] = std::to_string(60*60*24*30);
|
||||
cfg["WORKER_COUNT"] = std::to_string(4);
|
||||
cfg["MAX_MEMORY"] = std::to_string(1024*1024*16);
|
||||
@@ -550,5 +564,15 @@ StringMap make_server_settings()
|
||||
cfg[it.first] = it.second;
|
||||
}
|
||||
|
||||
if(cfg["FCGI_SOCKET_PATH"] == "" && cfg["SOCKET_PATH"] != "")
|
||||
cfg["FCGI_SOCKET_PATH"] = cfg["SOCKET_PATH"];
|
||||
if(cfg["SOCKET_PATH"] == "" && cfg["FCGI_SOCKET_PATH"] != "")
|
||||
cfg["SOCKET_PATH"] = cfg["FCGI_SOCKET_PATH"];
|
||||
|
||||
if(cfg["FCGI_PORT"] == "" && cfg["LISTEN_PORT"] != "")
|
||||
cfg["FCGI_PORT"] = cfg["LISTEN_PORT"];
|
||||
if(cfg["LISTEN_PORT"] == "" && cfg["FCGI_PORT"] != "")
|
||||
cfg["LISTEN_PORT"] = cfg["FCGI_PORT"];
|
||||
|
||||
return(cfg);
|
||||
}
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ String nibble(String div, String& haystack)
|
||||
|
||||
void Request::ob_start()
|
||||
{
|
||||
ob_stack.push_back(new std::ostringstream());
|
||||
ob_stack.push_back(new ByteStream());
|
||||
ob = ob_stack.back();
|
||||
}
|
||||
|
||||
|
||||
+23
-11
@@ -47,6 +47,7 @@ String operator+(String lhs, f32 rhs) {
|
||||
|
||||
typedef std::map<String, String> StringMap;
|
||||
typedef std::vector<String> StringList;
|
||||
typedef std::ostringstream ByteStream;
|
||||
|
||||
struct Request;
|
||||
struct DTree;
|
||||
@@ -129,22 +130,27 @@ struct Request {
|
||||
String session_name = "";
|
||||
std::vector<UploadedFile> uploaded_files;
|
||||
|
||||
String response_code = "HTTP/1.1 200 OK";
|
||||
StringMap header;
|
||||
StringList set_cookies;
|
||||
|
||||
u64 random_seed;
|
||||
u64 random_index;
|
||||
|
||||
std::vector<ByteStream*> ob_stack;
|
||||
ByteStream* ob;
|
||||
|
||||
String in;
|
||||
std::vector<std::ostringstream*> ob_stack;
|
||||
std::ostringstream* ob;
|
||||
String out;
|
||||
String err;
|
||||
|
||||
bool is_finished = false;
|
||||
|
||||
struct Flags {
|
||||
bool log_request = true;
|
||||
bool is_finished = false;
|
||||
int status;
|
||||
bool output_closed = false;
|
||||
bool params_closed = false;
|
||||
bool input_closed = false;
|
||||
} flags;
|
||||
|
||||
struct Stats {
|
||||
@@ -152,20 +158,20 @@ struct Request {
|
||||
f64 time_init;
|
||||
f64 time_start;
|
||||
f64 time_end;
|
||||
u64 mem_high;
|
||||
u64 mem_alloc;
|
||||
u64 mem_high = 0;
|
||||
u64 mem_alloc = 0;
|
||||
u32 invoke_count = 0;
|
||||
} stats;
|
||||
|
||||
// OS-related resources
|
||||
struct Resources {
|
||||
std::vector<u64> sockets;
|
||||
std::vector<void*> mysql_connections;
|
||||
u64 fcgi_socket = 0;
|
||||
u64 client_socket = 0;
|
||||
u64 server_socket = 0;
|
||||
std::string params_buffer;
|
||||
} resources;
|
||||
|
||||
//void invoke(String file_name);
|
||||
//void invoke(String file_name, DTree& call_param);
|
||||
|
||||
void ob_start();
|
||||
|
||||
~Request();
|
||||
@@ -184,10 +190,16 @@ void print(Ts... args)
|
||||
((*context->ob << args), ...);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void out(Ts... args)
|
||||
{
|
||||
((*context->ob << args), ...);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
String concat(Ts... args)
|
||||
{
|
||||
std::stringstream out;
|
||||
ByteStream out;
|
||||
((out << args), ...);
|
||||
return(out.str());
|
||||
}
|
||||
|
||||
+135
-27
@@ -101,42 +101,117 @@ String encode_query(StringMap map)
|
||||
return(result);
|
||||
}
|
||||
|
||||
String trim_wrapping_quotes(String raw)
|
||||
{
|
||||
if(raw.length() >= 2 && raw[0] == '"' && raw[raw.length()-1] == '"')
|
||||
return(raw.substr(1, raw.length()-2));
|
||||
return(raw);
|
||||
}
|
||||
|
||||
void parse_multipart_content_disposition(
|
||||
String raw,
|
||||
String& disposition_type,
|
||||
String& field_name,
|
||||
String& file_name)
|
||||
{
|
||||
auto parts = split(raw, ";");
|
||||
if(parts.size() == 0)
|
||||
return;
|
||||
|
||||
disposition_type = to_lower(trim(parts[0]));
|
||||
for(u32 i = 1; i < parts.size(); i++)
|
||||
{
|
||||
String part = trim(parts[i]);
|
||||
String key = to_lower(trim(nibble(part, "=")));
|
||||
String value = trim_wrapping_quotes(trim(part));
|
||||
if(key == "name")
|
||||
field_name = value;
|
||||
else if(key == "filename")
|
||||
file_name = value;
|
||||
}
|
||||
}
|
||||
|
||||
String make_upload_tmp_name()
|
||||
{
|
||||
String upload_path = context->server->config["TMP_UPLOAD_PATH"];
|
||||
if(upload_path.length() > 0 && upload_path[upload_path.length()-1] != '/')
|
||||
upload_path.append(1, '/');
|
||||
return(upload_path + make_session_id());
|
||||
}
|
||||
|
||||
StringMap parse_multipart(String q, String boundary, std::vector<UploadedFile>& uploaded_files)
|
||||
{
|
||||
StringMap result;
|
||||
|
||||
auto i = boundary.length();
|
||||
if(boundary == "")
|
||||
return(result);
|
||||
|
||||
u64 i = 0;
|
||||
while(i < q.length())
|
||||
{
|
||||
auto end_pos = q.find(boundary, i);
|
||||
if(end_pos != String::npos)
|
||||
auto start_pos = q.find(boundary, i);
|
||||
if(start_pos == String::npos)
|
||||
break;
|
||||
|
||||
start_pos += boundary.length();
|
||||
if(q.substr(start_pos, 2) == "--")
|
||||
break;
|
||||
if(q.substr(start_pos, 2) == "\r\n")
|
||||
start_pos += 2;
|
||||
|
||||
auto header_end = q.find("\r\n\r\n", start_pos);
|
||||
if(header_end == String::npos)
|
||||
break;
|
||||
|
||||
String header_block = q.substr(start_pos, header_end - start_pos);
|
||||
auto end_pos = q.find(boundary, header_end + 4);
|
||||
if(end_pos == String::npos)
|
||||
break;
|
||||
|
||||
String body = q.substr(header_end + 4, end_pos - (header_end + 4));
|
||||
if(body.length() >= 2 && body.substr(body.length()-2) == "\r\n")
|
||||
body.resize(body.length()-2);
|
||||
|
||||
String disposition_type;
|
||||
String field_name;
|
||||
String file_name;
|
||||
for(auto header_line : split(header_block, "\r\n"))
|
||||
{
|
||||
String field = q.substr(i, end_pos - i);
|
||||
nibble(":", field);
|
||||
String ftype = trim(nibble(";", field));
|
||||
if(ftype == "form-data")
|
||||
String header_name = to_lower(trim(nibble(header_line, ":")));
|
||||
String header_value = trim(header_line);
|
||||
if(header_name == "content-disposition")
|
||||
{
|
||||
nibble("=\"", field);
|
||||
String field_name = nibble("\"", field);
|
||||
result[field_name] = field.substr(4, field.length()-6);
|
||||
parse_multipart_content_disposition(
|
||||
header_value,
|
||||
disposition_type,
|
||||
field_name,
|
||||
file_name
|
||||
);
|
||||
}
|
||||
else if(ftype == "attachment")
|
||||
}
|
||||
|
||||
if(field_name != "")
|
||||
{
|
||||
bool is_uploaded_file =
|
||||
(disposition_type == "form-data" || disposition_type == "attachment") &&
|
||||
file_name != "";
|
||||
|
||||
if(is_uploaded_file)
|
||||
{
|
||||
nibble("=\"", field);
|
||||
UploadedFile f;
|
||||
f.tmp_name = context->server->config["TMP_UPLOAD_PATH"] + std::to_string(rand());
|
||||
f.file_name = nibble("\"", field);
|
||||
String bin = field.substr(4, field.length()-6);
|
||||
f.size = bin.length();
|
||||
file_put_contents(f.tmp_name, bin);
|
||||
f.tmp_name = make_upload_tmp_name();
|
||||
f.file_name = file_name;
|
||||
f.size = body.length();
|
||||
file_put_contents(f.tmp_name, body);
|
||||
uploaded_files.push_back(f);
|
||||
result[field_name] = file_name;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[field_name] = body;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we're done
|
||||
end_pos = q.length();
|
||||
}
|
||||
|
||||
i = end_pos + boundary.length();
|
||||
}
|
||||
|
||||
@@ -287,23 +362,56 @@ String make_session_id()
|
||||
return(to_hex(rand())+to_hex(rand())+to_hex(rand())+to_hex(rand()));
|
||||
}
|
||||
|
||||
bool is_valid_session_id(String session_id)
|
||||
{
|
||||
if(session_id.length() < 16 || session_id.length() > 128)
|
||||
return(false);
|
||||
for(auto c : session_id)
|
||||
{
|
||||
if(!isxdigit(c))
|
||||
return(false);
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
String session_file_path(String session_id)
|
||||
{
|
||||
if(!is_valid_session_id(session_id))
|
||||
return("");
|
||||
return(context->server->config["SESSION_PATH"] + "/" + session_id);
|
||||
}
|
||||
|
||||
StringMap load_session_data(String session_id)
|
||||
{
|
||||
return(parse_query(file_get_contents(context->server->config["SESSION_PATH"] + "/" + session_id)));
|
||||
String session_path = session_file_path(session_id);
|
||||
if(session_path == "")
|
||||
return(StringMap());
|
||||
return(parse_query(file_get_contents(session_path)));
|
||||
}
|
||||
|
||||
void save_session_data(String session_id, StringMap data)
|
||||
{
|
||||
file_put_contents(context->server->config["SESSION_PATH"] + "/" + session_id, encode_query(data));
|
||||
String session_path = session_file_path(session_id);
|
||||
if(session_path == "")
|
||||
{
|
||||
printf("(!) Refusing to save invalid session id\n");
|
||||
return;
|
||||
}
|
||||
file_put_contents(session_path, encode_query(data));
|
||||
}
|
||||
|
||||
String session_start(String session_name)
|
||||
{
|
||||
if(context->cookies[session_name].length() == 0)
|
||||
String session_id = context->cookies[session_name];
|
||||
if(!is_valid_session_id(session_id))
|
||||
session_id = "";
|
||||
|
||||
if(session_id.length() == 0)
|
||||
{
|
||||
set_cookie(session_name, make_session_id(), time() + int_val(context->server->config["SESSION_TIME"]));
|
||||
session_id = make_session_id();
|
||||
set_cookie(session_name, session_id, time() + int_val(context->server->config["SESSION_TIME"]));
|
||||
}
|
||||
context->session_id = context->cookies[session_name];
|
||||
context->session_id = session_id;
|
||||
context->session_name = session_name;
|
||||
context->session = load_session_data(context->session_id);
|
||||
return(context->session_id);
|
||||
|
||||
@@ -17,3 +17,69 @@ StringMap load_session_data(String session_id);
|
||||
void save_session_data(String session_id, StringMap data);
|
||||
String session_start(String session_name = "uce-session");
|
||||
void session_destroy(String session_name = "uce-session");
|
||||
|
||||
struct WSFrame {
|
||||
|
||||
u8 opcode;
|
||||
u8 is_final_fragment;
|
||||
u8 mask_bit;
|
||||
u64 payload_length;
|
||||
u64 offset;
|
||||
u8* payload;
|
||||
|
||||
WSFrame(String& bufstr)
|
||||
{
|
||||
const char* buffer = bufstr.c_str();
|
||||
u64 buffer_size = bufstr.size();
|
||||
opcode = buffer[0] & 0x0F;
|
||||
is_final_fragment = buffer[0] & 0x80;
|
||||
mask_bit = buffer[1] & 0x80;
|
||||
payload_length = buffer[1] & 0x7F;
|
||||
|
||||
offset = 2;
|
||||
if (payload_length == 126) {
|
||||
payload_length = ((u64) buffer[2] << 8) | buffer[3];
|
||||
offset = 4;
|
||||
} else if (payload_length == 127) {
|
||||
payload_length = ((u64) buffer[2] << 56) |
|
||||
((u64) buffer[3] << 48) |
|
||||
((u64) buffer[4] << 40) |
|
||||
((u64) buffer[5] << 32) |
|
||||
((u64) buffer[6] << 24) |
|
||||
((u64) buffer[7] << 16) |
|
||||
((u64) buffer[8] << 8) |
|
||||
buffer[9];
|
||||
offset = 10;
|
||||
}
|
||||
|
||||
uint8_t* maskingKey = nullptr;
|
||||
if (mask_bit) {
|
||||
maskingKey = reinterpret_cast<uint8_t*>(const_cast<char*>(buffer + offset));
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
// Parse the frame payload
|
||||
payload = reinterpret_cast<uint8_t*>(const_cast<char*>(buffer + offset));
|
||||
if (mask_bit) {
|
||||
for (int i = 0; i < payload_length; i++) {
|
||||
payload[i] = payload[i] ^ maskingKey[i % 4];
|
||||
}
|
||||
}
|
||||
|
||||
// Print the parsed frame
|
||||
std::cout << "Parsed WebSocket frame:" << std::endl;
|
||||
std::cout << " Opcode: " << (int) opcode << std::endl;
|
||||
std::cout << " Is final fragment: " << (is_final_fragment ? "true" : "false") << std::endl;
|
||||
std::cout << " Payload length: " << payload_length << std::endl;
|
||||
if (mask_bit) {
|
||||
std::cout << " Masking key: ";
|
||||
for (int i = 0; i < 4; i++) {
|
||||
std::cout << std::hex << (int) maskingKey[i] << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << " Payload: " << std::endl << std::string((const char*) payload, payload_length) << std::endl;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
+27
-10
@@ -70,8 +70,8 @@ int handle_complete(FastCGIRequest& request) {
|
||||
}
|
||||
}
|
||||
|
||||
// printf("(i) request ready\n");
|
||||
render_file(request.params["SCRIPT_FILENAME"]);
|
||||
DTree call_param;
|
||||
compiler_invoke(&request, request.params["SCRIPT_FILENAME"], call_param);
|
||||
|
||||
for( auto &f : request.uploaded_files)
|
||||
{
|
||||
@@ -86,6 +86,20 @@ int handle_complete(FastCGIRequest& request) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
volatile bool termination_signal_received = false;
|
||||
|
||||
void on_terminate(int sig)
|
||||
{
|
||||
if(termination_signal_received)
|
||||
return;
|
||||
termination_signal_received = true;
|
||||
if(getpid() != parent_pid)
|
||||
exit(1);
|
||||
printf("Terminating... PID %i:%i\n", getpid(), parent_pid);
|
||||
server.shutdown();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void listen_for_connections()
|
||||
{
|
||||
if(precompile_jobs.size() > 0)
|
||||
@@ -119,25 +133,27 @@ void init_base_process()
|
||||
|
||||
server_state.config["COMPILE_SCRIPT"] =
|
||||
server_state.config["COMPILER_SYS_PATH"] + "/" + server_state.config["COMPILE_SCRIPT"];
|
||||
if(server_state.config["LISTEN_PORT"] != "")
|
||||
server.listen(int_val(server_state.config["LISTEN_PORT"]));
|
||||
|
||||
if(server_state.config["FCGI_PORT"] != "")
|
||||
server.listen(int_val(server_state.config["FCGI_PORT"]));
|
||||
|
||||
printf("%s\n", var_dump(server_state.config).c_str());
|
||||
|
||||
if(server_state.config["SOCKET_PATH"] != "")
|
||||
if(server_state.config["FCGI_SOCKET_PATH"] != "")
|
||||
{
|
||||
server.listen(server_state.config["SOCKET_PATH"]);
|
||||
chmod(server_state.config["SOCKET_PATH"].c_str(), S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
||||
server.listen(server_state.config["FCGI_SOCKET_PATH"]);
|
||||
chmod(server_state.config["FCGI_SOCKET_PATH"].c_str(), S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
||||
}
|
||||
|
||||
//dirname(server_state.config.COMPILER_SYS_PATH);
|
||||
//basename(server_state.config.COMPILER_SYS_PATH);
|
||||
if(server_state.config["HTTP_PORT"] != "")
|
||||
server.listen_http(int_val(server_state.config["HTTP_PORT"]));
|
||||
|
||||
mkdir(server_state.config["BIN_DIRECTORY"]);
|
||||
mkdir(server_state.config["TMP_UPLOAD_PATH"]);
|
||||
mkdir(server_state.config["SESSION_PATH"]);
|
||||
|
||||
signal(SIGCHLD, on_child_exit);
|
||||
signal(SIGINT, on_terminate);
|
||||
srand(time());
|
||||
}
|
||||
|
||||
@@ -187,7 +203,8 @@ int main(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
}
|
||||
spawn_subprocess(listen_for_connections);
|
||||
if(!termination_signal_received)
|
||||
spawn_subprocess(listen_for_connections);
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user