Add custom server API and runtime limits

This commit is contained in:
udo
2026-05-21 09:36:23 +00:00
parent 02e153a6a7
commit d37517041d
18 changed files with 641 additions and 66 deletions
+75 -8
View File
@@ -58,6 +58,7 @@ struct TransportLimits {
u64 max_websocket_frame_bytes = 1024 * 1024;
u64 max_websocket_message_bytes = 1024 * 1024;
u64 max_websocket_output_bytes = 4 * 1024 * 1024;
u64 max_response_bytes = 8 * 1024 * 1024;
f64 http_request_timeout_seconds = 15.0;
f64 connection_idle_timeout_seconds = 120.0;
@@ -72,9 +73,38 @@ struct TransportLimits {
}
};
const TransportLimits& transport_limits()
u64 transport_config_u64(String key, u64 fallback)
{
static const TransportLimits limits;
if(!context || !context->server)
return(fallback);
String raw = trim(context->server->config[key]);
if(raw == "")
return(fallback);
return(int_val(raw));
}
f64 transport_config_f64(String key, f64 fallback)
{
if(!context || !context->server)
return(fallback);
String raw = trim(context->server->config[key]);
if(raw == "")
return(fallback);
return(float_val(raw));
}
TransportLimits transport_limits()
{
TransportLimits limits;
limits.max_client_connections = transport_config_u64("TRANSPORT_MAX_CLIENT_CONNECTIONS", limits.max_client_connections);
limits.max_http_header_bytes = transport_config_u64("TRANSPORT_MAX_HTTP_HEADER_BYTES", limits.max_http_header_bytes);
limits.max_http_body_bytes = transport_config_u64("TRANSPORT_MAX_HTTP_BODY_BYTES", limits.max_http_body_bytes);
limits.max_websocket_frame_bytes = transport_config_u64("TRANSPORT_MAX_WEBSOCKET_FRAME_BYTES", limits.max_websocket_frame_bytes);
limits.max_websocket_message_bytes = transport_config_u64("TRANSPORT_MAX_WEBSOCKET_MESSAGE_BYTES", limits.max_websocket_message_bytes);
limits.max_websocket_output_bytes = transport_config_u64("TRANSPORT_MAX_WEBSOCKET_OUTPUT_BYTES", limits.max_websocket_output_bytes);
limits.max_response_bytes = transport_config_u64("TRANSPORT_MAX_RESPONSE_BYTES", limits.max_response_bytes);
limits.http_request_timeout_seconds = transport_config_f64("TRANSPORT_HTTP_REQUEST_TIMEOUT_SECONDS", limits.http_request_timeout_seconds);
limits.connection_idle_timeout_seconds = transport_config_f64("TRANSPORT_CONNECTION_IDLE_TIMEOUT_SECONDS", limits.connection_idle_timeout_seconds);
return(limits);
}
@@ -185,6 +215,14 @@ FastCGIServer::listen_http(unsigned tcp_port)
return server_socket;
}
int
FastCGIServer::listen_http(unsigned tcp_port, const std::string& bind_address)
{
int server_socket = listen(tcp_port, bind_address);
server_socket_types[server_socket] = 'H';
return server_socket;
}
int
FastCGIServer::listen_cli(const std::string& local_path)
{
@@ -196,6 +234,12 @@ FastCGIServer::listen_cli(const std::string& local_path)
int
FastCGIServer::listen(unsigned tcp_port)
{
return(listen(tcp_port, "0.0.0.0"));
}
int
FastCGIServer::listen(unsigned tcp_port, const std::string& bind_address)
{
int server_socket = socket(PF_INET, SOCK_STREAM, 0);
if (server_socket == -1)
@@ -211,7 +255,10 @@ FastCGIServer::listen(unsigned tcp_port)
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_address == "" || bind_address == "0.0.0.0" || bind_address == "*")
sa.sin_addr.s_addr = htonl(INADDR_ANY);
else if(inet_pton(AF_INET, bind_address.c_str(), &sa.sin_addr) != 1)
throw std::runtime_error("invalid bind address");
if (bind(server_socket, (struct sockaddr*)&sa, sizeof(sa)) == -1)
throw std::runtime_error("bind() failed");
@@ -226,7 +273,7 @@ FastCGIServer::listen(unsigned tcp_port)
}
server_socket_types[server_socket] = 'F';
printf("(P) listening to #%i port %i\n", server_socket, tcp_port);
printf("(P) listening to #%i %s:%i\n", server_socket, bind_address.c_str(), tcp_port);
return server_socket;
}
@@ -358,7 +405,7 @@ FastCGIServer::enforce_connection_timeouts(Connection& connection)
if(connection.close_socket)
return;
const TransportLimits& limits = transport_limits();
TransportLimits limits = transport_limits();
f64 now = time_precise();
if(is_http_like_type(connection.type) && !connection.is_websocket && !connection.requests.empty())
{
@@ -603,7 +650,7 @@ bool
FastCGIServer::parse_http_message(FastCGIRequest& request, String& data)
{
Connection* connection = client_sockets[request.resources.client_socket];
const TransportLimits& limits = transport_limits();
TransportLimits limits = transport_limits();
auto header_end = data.find("\r\n\r\n");
if(header_end == String::npos)
{
@@ -772,7 +819,7 @@ FastCGIServer::process_http_request(FastCGIRequest& request, String& data)
void
FastCGIServer::process_websocket_input(Connection& connection)
{
const TransportLimits& limits = transport_limits();
TransportLimits limits = transport_limits();
if(connection.input_buffer.length() > limits.max_websocket_buffer_bytes())
{
fail_websocket_connection(connection, 1009, "websocket input buffer is too large");
@@ -1121,8 +1168,15 @@ FastCGIServer::read_fgci(Connection& connection)
FastCGIRequest& request = *it->second;
//switch_to_arena(it->second->mem);
if (!request.flags.params_closed)
if (content_length != 0)
if (content_length != 0) {
if(request.resources.params_buffer.size() + content_length > transport_limits().max_http_header_bytes)
{
request.flags.status = 1;
connection.close_socket = true;
break;
}
request.resources.params_buffer.append(content, content_length);
}
else {
request.params = parse_pairs_fcgi(
request.resources.params_buffer.data(),
@@ -1152,6 +1206,12 @@ FastCGIServer::read_fgci(Connection& connection)
//switch_to_arena(it->second->mem);
if (!request.flags.input_closed)
if (content_length != 0) {
if(request.in.size() + content_length > transport_limits().max_http_body_bytes)
{
request.flags.status = 1;
connection.close_socket = true;
break;
}
request.in.append(content, content_length);
if (request.flags.params_closed && request.flags.status == 0)
{
@@ -1203,6 +1263,13 @@ FastCGIServer::assemble_output_buffer(FastCGIRequest& request, Connection* conne
request.out += obs->str();
delete obs;
}
if(request.out.length() > transport_limits().max_response_bytes)
{
request.set_status(500, "Response Too Large");
request.header.clear();
request.header["Content-Type"] = "text/plain; charset=utf-8";
request.out = request.response_code + "\r\n" + var_dump(request.header, "", "\r\n") + "\r\nresponse exceeded configured output limit\n";
}
request.ob_stack.clear();
request.flags.output_closed = true;
request.stats.time_end = time_precise();
+2
View File
@@ -52,7 +52,9 @@ public:
std::function<int(FastCGIRequest&, const String&, u8)> on_websocket_message = 0;
int listen(unsigned tcp_port);
int listen(unsigned tcp_port, const std::string& bind_address);
int listen_http(unsigned tcp_port);
int listen_http(unsigned tcp_port, const std::string& bind_address);
int listen_cli(const std::string& local_path);
int listen(const std::string& local_path);