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:
udo 2026-04-18 14:04:58 +00:00
parent 984453f336
commit cd8f07aaa7
26 changed files with 913 additions and 367 deletions

View File

@ -8,6 +8,24 @@ This is in the early stages of development. Don't use this for anything importan
The aim of this project is to make a PHP-like runtime that enables server-side "scripting" using C/C++. At the core is a multi-worker FastCGI server that can be talked to from Nginx or similar front-end servers. UCE has a shared-nothing isolated architecture to serve page requests. To minimize the potential for memory leaks, UCE uses a per-request memory arena. UCE also provides a PHP-like API to ease web development. Advanced features such as an integrated WebSockets broker are planned. UCE aims to use minimal dependencies (at the moment, the only dependency is the Clang compiler). UCE pages are automatically recompiled and dynamically reloaded as necessary.
## Service Setup
The repository now includes a systemd unit template and a management script for the FastCGI runtime:
- `scripts/systemd/uce.service`
- `scripts/systemd/manage-uce-service.sh`
Typical usage on the deployment host:
```bash
scripts/systemd/manage-uce-service.sh setup
scripts/systemd/manage-uce-service.sh status
scripts/systemd/manage-uce-service.sh restart
scripts/systemd/manage-uce-service.sh logs 200
```
The service runs the runtime from the repository root so `COMPILER_SYS_PATH` resolves correctly and nginx can forward `.uce` requests to the Unix socket defined in `/etc/uce/settings.cfg`.
# API
Memcache Functions

View File

@ -2,9 +2,9 @@ Output / Invocation Functions
call_file
load
ob_clear
ob_close
ob_get
ob_get_clear
ob_get_close
ob_start
print
render_file

View File

@ -1,11 +0,0 @@
:sig
void ob_clear()
:params
(none)
:desc
Discard the current output buffer.
:see
>ob

11
doc/pages/ob_close.txt Normal file
View File

@ -0,0 +1,11 @@
:sig
void ob_close()
:params
(none)
:desc
Discard the current output buffer. If are more output buffers on the stack, switch to the next one.
:see
>ob

View File

@ -1,11 +1,12 @@
:sig
String ob_get_clear()
String ob_get_close()
:params
return value : content of the current output buffer
:desc
Returns the contents of the current output buffer and then discards the buffer.
If are more output buffers on the stack, switch to the next one.
:see
>ob

View File

@ -5,7 +5,7 @@ void ob_start()
(none)
:desc
Starts a new output buffer. All subsequent output will be directed into this buffer.
Starts a new output buffer. All subsequent output will be directed into this buffer. Every call to ob_start() starts a new buffer and puts that buffer on the output buffer stack. ob_close() and ob_get_close() destroy buffers and remove them from the stack.
:see
>ob

View File

@ -4,8 +4,8 @@ TMP_UPLOAD_PATH=/tmp/uce/uploads
SESSION_PATH=/tmp/uce/sessions
# LISTEN ON SOCKETS
SOCKET_PATH=/run/uce.sock
LISTEN_PORT=9993
FCGI_SOCKET_PATH=/run/uce.sock
FCGI_PORT=9993
# PRECOMPILE .uce FILES
PRECOMPILE_FILES_IN=

View File

@ -3,7 +3,7 @@ cd "$(dirname "$0")"
cd ..
BUILDMODE=${2:-"debug"}
OPT_FLAG="O3"
OPT_FLAG="O0"
GF="uce_fastcgi"
mkdir bin > /dev/null 2>&1

View File

@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ ${EUID:-0} -ne 0 ]]; then
echo "This script must run as root." >&2
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
UNIT_NAME="uce.service"
UNIT_SOURCE="$SCRIPT_DIR/uce.service"
UNIT_DEST="/etc/systemd/system/$UNIT_NAME"
CONFIG_SOURCE="$REPO_ROOT/etc/uce/settings.cfg"
CONFIG_DEST="/etc/uce/settings.cfg"
action="${1:-setup}"
install_unit() {
install -D -m 0644 "$UNIT_SOURCE" "$UNIT_DEST"
if [[ ! -f "$CONFIG_DEST" ]]; then
install -D -m 0644 "$CONFIG_SOURCE" "$CONFIG_DEST"
fi
systemctl daemon-reload
}
case "$action" in
install)
install_unit
;;
setup)
install_unit
systemctl enable --now "$UNIT_NAME"
;;
enable)
install_unit
systemctl enable "$UNIT_NAME"
;;
start|stop|restart|status)
systemctl "$action" "$UNIT_NAME"
;;
logs)
lines="${2:-100}"
journalctl -u "$UNIT_NAME" -n "$lines" --no-pager
;;
*)
echo "Usage: $0 [install|setup|enable|start|stop|restart|status|logs [lines]]" >&2
exit 1
;;
esac

View File

@ -0,0 +1,22 @@
[Unit]
Description=UCE FastCGI Runtime
After=network-online.target mariadb.service memcached.service
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/Code/uce.openfu.com/uce
ExecStartPre=/usr/bin/mkdir -p /tmp/uce/work /tmp/uce/uploads /tmp/uce/sessions
ExecStartPre=/usr/bin/rm -f /run/uce.sock
ExecStartPre=/usr/bin/bash /Code/uce.openfu.com/uce/scripts/build_linux.sh
ExecStart=/Code/uce.openfu.com/uce/bin/uce_fastcgi.linux.bin
ExecStopPost=/usr/bin/rm -f /run/uce.sock
Restart=always
RestartSec=2
TimeoutStopSec=15
KillMode=mixed
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@ -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;

View File

@ -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);
};

View File

@ -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)
{

0
src/lib/compiler2.cpp Normal file
View File

View File

@ -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;

View File

@ -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)))

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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());
}

View File

@ -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);

View File

@ -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;
}
};

View File

@ -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);
}

25
test/header.uce Normal file
View File

@ -0,0 +1,25 @@
RENDER()
{
DTree t;
<>
<link rel="stylesheet" href='style.css'></link>
<h1>
<a href="index.uce">UCE Test</a>:
Header
</h1>
Response Headers
<pre style="white-space: pre-wrap"><?
print(var_dump(context->header));
?></pre>
Params
<pre><?= var_dump(context->params) ?></pre>
</>
}

View File

@ -33,6 +33,9 @@ RENDER()
<li><a href="task_repeat.uce">Task repeat</a></li>
<li><a href="utf8.uce">UTF-8</a></li>
<li><a href="call_file.uce">call_file()</a></li>
<li><a href="parse_time.uce">parse_time()</a></li>
<li><a href="header.uce">header</a></li>
<li><a href="websockets.ws.uce">WebSockets</a></li>
</ul>
<pre><?
print("Worker PID: ", my_pid, "\n");

30
test/parse_time.uce Normal file
View File

@ -0,0 +1,30 @@
RENDER()
{
String raw = first(context->post["raw"], "today");
<>
<link rel="stylesheet" href='style.css'></link>
<h1>
<a href="index.uce">UCE Test</a>:
parse_time
</h1>
<form action="?" method="post">
<div>
<label>Time string</label>
<input type="text" name="raw" value="<?= raw ?>"/>
</div>
<div>
<input type="submit" value="Parse"/>
</div>
</form>
<pre><?
print(date("%Y-%m-%d %H:%M", parse_time(raw)));
?></pre>
<pre><?= var_dump(context->params) ?></pre>
</>
}

42
test/websockets.ws.uce Normal file
View File

@ -0,0 +1,42 @@
RENDER()
{
String raw = first(context->post["raw"], "today");
String server_url = context->params["HTTP_HOST"]+context->params["DOCUMENT_URI"];
<>
<link rel="stylesheet" href='style.css'></link>
<h1>
<a href="index.uce">UCE Test</a>:
WebSocket Test
</h1>
<form action="?" method="post">
<div>
<label></label>
<input type="text" name="raw" value="<?= raw ?>"/>
</div>
<div>
<input type="submit" value="Parse"/>
</div>
</form>
<pre id="log"></pre>
<script>
const ws = new WebSocket('wss://<?= server_url ?>');
const log_element = document.getElementById('log');
log_element.append('Connecting to wss://<?= server_url ?>...' + "\n");
ws.addEventListener('open', (event) => {
log_element.append('Connection open' + "\n");
ws.send('Hello, server!');
});
ws.addEventListener('message', (event) => {
log_element.append('Message: ' + JSON.stringify(event.data) + "\n");
});
ws.addEventListener('close', (event) => {
log_element.append('Connection closed' + "\n");
});
ws.addEventListener('error', (event) => {
log_element.append('Error: ' + JSON.stringify(event) + "\n");
});
</script>
<pre><?= var_dump(context->params) ?></pre>
</>
}