C and C++ web framework.
http://rapida.vilor.one/docs
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
5.4 KiB
241 lines
5.4 KiB
/* SPDX-License-Identifier: GPL-3.0-or-later */ |
|
/* Copyright 2022 Ivan Polyakov */ |
|
|
|
#include "../include/servers/fcgi.h" |
|
#include "../c/utils.h" |
|
#include <ctype.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
|
|
#ifdef MT_ENABLED |
|
#include <pthread.h> |
|
#endif /* MT_ENABLED */ |
|
|
|
typedef struct { |
|
rpd_app *app; |
|
int sock_id; |
|
} thread_data; |
|
|
|
/*! |
|
* \brief Run FastCGI requests listening in loop. |
|
* \param data \see thread_data. |
|
* \return NULL; |
|
*/ |
|
static void *listen_requests(void *data); |
|
|
|
/*! |
|
* \brief Handle Request. |
|
* |
|
* This function converts FastCGI request into the Rapida request |
|
* and calls Rapida request handler. |
|
* |
|
* \param app Application instance. |
|
* \param fcgx_req FastCGI Request. |
|
*/ |
|
static void handle_request(rpd_app *app, FCGX_Request *fcgx_req); |
|
|
|
/*! |
|
* \brief Convert FastCGI request into the Rapida request. |
|
* |
|
* \param dest Rapida request. |
|
* \param req FastCGI request. |
|
* |
|
* \return Status code. 0 is success. |
|
*/ |
|
static int fcgx_to_rpd_req(rpd_req *dest, FCGX_Request *req); |
|
|
|
/*! |
|
* Read FastCGI request body. |
|
* \param dest Destination buffer. |
|
* \param req FastCGI request. |
|
* \return Status code. 0 is success. |
|
*/ |
|
static int read_fcgx_req_body(char **dest, FCGX_Request *req); |
|
|
|
/*! |
|
* \brief Sends response to client. |
|
* \param res Response instance. |
|
* \param out Output stream. |
|
*/ |
|
static void send_response(rpd_res *res, FCGX_Stream *out); |
|
|
|
static int env_to_req_header(char **key, char **val, const char *envp); |
|
|
|
int rpd_fcgi_server_start(rpd_app *app, const char *sock_path) |
|
{ |
|
FCGX_Init(); |
|
int sock_id = 0; |
|
if ((sock_id = FCGX_OpenSocket(sock_path, 10)) < 0) { |
|
return 1; |
|
} |
|
|
|
thread_data data = { app, sock_id }; |
|
#ifdef MT_ENABLED |
|
pthread_t threads[NTHREADS]; |
|
for (int i = 0; i < NTHREADS; i++) { |
|
pthread_create(&threads[i], 0, &listen_requests, (void *) &data); |
|
pthread_join(threads[i], 0); |
|
} |
|
#else |
|
listen_requests((void *) &data); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
static void *listen_requests(void *data) |
|
{ |
|
thread_data *hdata = (thread_data *) data; |
|
FCGX_Request req; |
|
if (FCGX_InitRequest(&req, hdata->sock_id, 0)) |
|
return 0; |
|
|
|
hdata->app->running = 1; |
|
while (hdata->app->running) { |
|
#ifdef MT_ENABLED |
|
static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER; |
|
pthread_mutex_lock(&accept_mutex); |
|
#endif |
|
|
|
int rc = FCGX_Accept_r(&req); |
|
|
|
#ifdef MT_ENABLED |
|
pthread_mutex_unlock(&accept_mutex); |
|
#endif |
|
|
|
if (rc < 0) |
|
break; |
|
|
|
#ifdef MT_ENABLED |
|
static pthread_mutex_t handle_mutex = PTHREAD_MUTEX_INITIALIZER; |
|
pthread_mutex_lock(&handle_mutex); |
|
#endif |
|
|
|
handle_request(hdata->app, &req); |
|
|
|
#ifdef MT_ENABLED |
|
pthread_mutex_unlock(&handle_mutex); |
|
#endif |
|
|
|
FCGX_Finish_r(&req); |
|
} |
|
return NULL; |
|
} |
|
|
|
static void handle_request(rpd_app *app, FCGX_Request *fcgx_req) |
|
{ |
|
rpd_req *req; |
|
rpd_res *res; |
|
|
|
req = (rpd_req *) malloc(sizeof(rpd_req)); |
|
res = (rpd_res *) malloc(sizeof(rpd_res)); |
|
|
|
fcgx_to_rpd_req(req, fcgx_req); |
|
rpd_res_init(res); |
|
|
|
rpd_app_handle_request(app, req, res); |
|
send_response(res, fcgx_req->out); |
|
|
|
rpd_req_cleanup(req); |
|
rpd_res_cleanup(res); |
|
free(req); |
|
free(res); |
|
} |
|
|
|
static void send_response(rpd_res *res, FCGX_Stream *out) |
|
{ |
|
char *buff; |
|
if (rpd_res_str(&buff, res)) { |
|
FCGX_PutS("Status: 500\r\n\r\n", out); |
|
return; |
|
} |
|
|
|
FCGX_PutS(buff, out); |
|
free(buff); |
|
} |
|
|
|
static int fcgx_to_rpd_req(rpd_req *dest, FCGX_Request *req) |
|
{ |
|
rpd_query_parse(&dest->query, FCGX_GetParam("QUERY_STRING", req->envp)); |
|
|
|
dest->method = rpd_req_smethod(FCGX_GetParam("REQUEST_METHOD", req->envp)); |
|
rpd_url_parse(&dest->path, FCGX_GetParam("DOCUMENT_URI", req->envp)); |
|
// dest->auth = FCGX_GetParam("HTTP_AUTHORIZATION", req->envp); |
|
// dest->cookie = FCGX_GetParam("HTTP_COOKIE", req->envp); |
|
rpd_keyval_init(&dest->params, 0); |
|
rpd_keyval_init(&dest->headers, 0); |
|
dest->headers.unique = 0; |
|
|
|
char **env = req->envp; |
|
char *key = NULL, *val = NULL; |
|
while (*(++env)) { |
|
char *ptr = NULL; |
|
|
|
/* keep only http request fields */ |
|
ptr = strstr(*env, "HTTP"); |
|
if (ptr == NULL || ptr - *env != 0) |
|
continue; |
|
|
|
env_to_req_header(&key, &val, *env); |
|
if (!val || !key) |
|
continue; |
|
rpd_strerase(key, 5); |
|
ptr = key; |
|
while (*ptr) { |
|
*ptr = tolower(*ptr); |
|
if (ptr == key || *(ptr - 1) == '-') |
|
*ptr = toupper(*ptr); |
|
if (*ptr == '_') |
|
*ptr = '-'; |
|
ptr++; |
|
} |
|
|
|
rpd_keyval_insert(&dest->headers, key, val); |
|
free(key); |
|
free(val); |
|
} |
|
|
|
if (dest->method != GET) { |
|
if (read_fcgx_req_body(&dest->body, req)) { |
|
return 2; |
|
} |
|
} else { |
|
dest->body = NULL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int read_fcgx_req_body(char **dest, FCGX_Request *req) |
|
{ |
|
char *clen = FCGX_GetParam("CONTENT_LENGTH", req->envp); |
|
if (!clen) |
|
return 1; |
|
size_t len = atoll(clen); |
|
|
|
*dest = (char *) malloc(sizeof(char) * (len + 1)); |
|
if (!*dest) |
|
return 2; |
|
|
|
*dest[len] = '\0'; |
|
FCGX_GetStr(*dest, len, req->in); |
|
|
|
return 0; |
|
} |
|
|
|
static int env_to_req_header(char **key, char **val, const char *env) |
|
{ |
|
const char *ptr = NULL; |
|
|
|
rpd_splitbyc(key, val, env, '='); |
|
if (!*key || !*val) { |
|
if (*key) |
|
free(*key); |
|
if (*val) |
|
free(*val); |
|
return 1; |
|
} |
|
|
|
return 0; |
|
}
|
|
|