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.

242 lines
5.4 KiB

2 years ago
/* 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;
}