Add basic FastCGI support to AntHTTP (responder only)

This commit is contained in:
DanyLE 2023-01-12 01:05:48 +01:00
parent 1d3db5ea5f
commit 14e6f28767
6 changed files with 1249 additions and 41 deletions

109
.gitignore vendored
View File

@ -1,49 +1,76 @@
# http://www.gnu.org/software/automake # Prerequisites
plugins
build
*._*
*.d
*.deb
.vscode
# Object files
*.o
*.ko
*.obj
*.elf
Makefile.in # Linker output
/ar-lib *.ilk
/mdate-sh *.map
/py-compile *.exp
/test-driver
/ylwrap
# http://www.gnu.org/software/autoconf # Precompiled Headers
*.gch
*.pch
autom4te.cache # Libraries
/autoscan.log *.lib
/autoscan-*.log *.a
/aclocal.m4 *.la
/compile *.lo
/config.guess
/config.h.in
/config.log
/config.status
/config.sub
/configure
/configure.scan
/depcomp
/install-sh
/missing
/stamp-h1
# https://www.gnu.org/software/libtool/ # Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
/ltmain.sh # Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# http://www.gnu.org/software/texinfo # Debug files
*.dSYM/
*.su
*.idb
*.pdb
/texinfo.tex # Kernel Module Compile Results
*.mod*
# http://www.gnu.org/software/m4/ *.cmd
modules.order
m4/libtool.m4 Module.symvers
m4/ltoptions.m4 Makefile.old
m4/ltsugar.m4 dkms.con
m4/ltversion.m4 .DS_Store
m4/lt~obsolete.m4 .*
*.cache
# Generated Makefile
# (meta build system like autotools,
# can automatically generate from config.status script
# (which is called by configure script))
Makefile Makefile
antd
compile
config.guess
depcomp
install-sh
missing
libtool
config.log
config.status
config.sub
configure
aclocal.m4
ltmain.sh
Makefile.in
# others
3rd/lua-5.3.4/lua
3rd/lua-5.3.4/luac

19
Makefile.am Normal file
View File

@ -0,0 +1,19 @@
AUTOMAKE_OPTIONS = foreign
# check for system
if LINUX
AM_CPPFLAGS = -Wl,--no-as-needed
else
AM_CPPFLAGS = -Wl,-undefined,dynamic_lookup
endif
AM_CPPFLAGS += -W -Wall -g -std=c99 -fPIC
lib_LTLIBRARIES = fcgi.la
fcgi_la_LDFLAGS = -module -avoid-version -shared
fcgi_la_SOURCES = fcgi.c proto.c
EXTRA_DIST = README.md

69
configure.ac Normal file
View File

@ -0,0 +1,69 @@
# initialise autoconf and set up some basic information about the program were packaging
AC_INIT([fcgi], [0.1.0a], [xsang.le@gmail.com])
# Were going to use automake for this project
# [subdir-objects] if needed
AM_INIT_AUTOMAKE()
# dependencies
# C compiler
AC_PROG_CC
# libtool for linking
AC_PROG_LIBTOOL
# check for lib antd
AC_CHECK_HEADER([antd/plugin.h],[
has_antd=yes
# check if the library exists
],[
AC_MSG_ERROR([Unable to find antd, please install it first])
])
AC_CHECK_LIB([antd],[antd_send],[],[
if test "$has_antd" = "yes"; then
AC_MSG_ERROR([Unable to find antd shared library, please install it first])
fi
])
# check for pthread
AC_CHECK_LIB([pthread], [pthread_create], [], [
AC_MSG_ERROR([libpthread is not found])])
AC_DEFINE([_GNU_SOURCE], [1],[Use GNU source])
# AC_CANONICAL_HOST is needed to access the 'host_os' variable
AC_CANONICAL_HOST
build_linux=no
build_windows=no
build_mac=no
# Detect the target system
case "${host_os}" in
linux*)
AC_DEFINE([LINUX], [1],[Linux system])
build_linux=yes
;;
darwin*)
build_mac=yes
AC_DEFINE([MACOS], [1],[MacOS system])
;;
*)
AC_MSG_ERROR(["OS $host_os is not supported"])
;;
esac
# case for window:
# cygwin*|mingw*)
# build_windows=yes
# ;;
# Pass the conditionals to automake
AM_CONDITIONAL([LINUX], [test "$build_linux" = "yes"])
AM_CONDITIONAL([WINDOWS], [test "$build_windows" = "yes"])
AM_CONDITIONAL([OSX], [test "$build_mac" = "yes"])
# find a file called Makefile.in, substitute placeholders
# like @PACKAGE_VERSION@ with values like 0.1.0a,
# and write the results to Makefile.
AC_CONFIG_FILES([Makefile])
# output the script:
AC_OUTPUT

649
fcgi.c Normal file
View File

@ -0,0 +1,649 @@
#define PLUGIN_IMPLEMENT 1
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <antd/plugin.h>
#include <antd/scheduler.h>
#include <antd/utils.h>
#include <ctype.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <time.h>
#include <poll.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <libgen.h>
#include "proto.h"
#define MAX_PATH_LEN 108
#define MAX_BACK_LOG 1024
#define PROCESS_TIMEOUT 200u
#define FCGI_CLIENT_REQUEST_SENT (0)
#define FCGI_CLIENT_WAIT_FOR_RESPONSE_HEADER (-1)
#define FCGI_CLIENT_WAIT_FOR_RESPONSE_DATA (-2)
typedef struct {
// FastCGI application path
char app_bin[MAX_PATH_LEN];
// TCP host or Unix domain socket
char address[MAX_PATH_LEN];
// only for TCP socket
int port;
// server fd
// int fd;
// pid of the application process
// pid_t pid;
} fcgi_config_t;
static fcgi_config_t g_config;
static int read_config()
{
char * tmp;
(void*) memset(g_config.app_bin, 0, MAX_PATH_LEN);
(void*) memset(g_config.address, 0, MAX_PATH_LEN);
g_config.port = -1;
//g_config.fd = -1;
//g_config.pid = -1;
regmatch_t regex_matches[3];
// read plugin configuration
if(!__plugin__.config)
{
PLUGIN_PANIC("No plugin configuration found. Please specify it it server config file");
return -1;
}
tmp = (char*) dvalue(__plugin__.config, "socket");
if(!tmp)
{
PLUGIN_PANIC("No socket configuration found (socket)");
return -1;
}
if(strncmp(tmp,"unix:", 5) == 0)
{
if(strlen(tmp + 5) > MAX_PATH_LEN - 1)
{
PLUGIN_PANIC("socket configuration is too long: %s", tmp);
return -1;
}
snprintf(g_config.address, MAX_PATH_LEN,"%s", tmp+5);
LOG("Found Unix domain socket configuration: %s", g_config.address);
}
else if(regex_match("^([a-zA-Z0-9\\-_\\.]+):([0-9]+)$", tmp,3, regex_matches))
{
if(regex_matches[1].rm_eo - regex_matches[1].rm_so > MAX_PATH_LEN - 1)
{
PLUGIN_PANIC("socket configuration is too long: %s", tmp);
return -1;
}
memcpy(g_config.address, tmp + regex_matches[2].rm_so, regex_matches[2].rm_eo - regex_matches[2].rm_so);
g_config.port = atoi(g_config.address);
(void*) memset(g_config.address, 0, MAX_PATH_LEN);
memcpy(g_config.address, tmp + regex_matches[1].rm_so, regex_matches[1].rm_eo - regex_matches[1].rm_so);
LOG("Found TCP socket configuration: %s:%d", g_config.address, g_config.port);
}
else
{
PLUGIN_PANIC("Unknown socket configuration: %s", tmp);
return -1;
}
tmp = (char*) dvalue(__plugin__.config, "bin");
if(!tmp)
{
PLUGIN_PANIC("No FastCGI application configuration found (bin)");
return -1;
}
if(strlen(tmp) > MAX_PATH_LEN - 1)
{
PLUGIN_PANIC("Bin applicqtion configuration is too long: %s", tmp);
return -1;
}
snprintf(g_config.app_bin, MAX_PATH_LEN,"%s", tmp);
LOG("Binary application configuration: %s", g_config.app_bin);
return 0;
}
static int open_un_socket()
{
struct sockaddr_un address;
address.sun_family = AF_UNIX;
// create the socket
(void)strncpy(address.sun_path, g_config.address, sizeof(address.sun_path));
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1)
{
ERROR("Unable to create Unix domain socket: %s", strerror(errno));
return -1;
}
if(connect(fd, (struct sockaddr*)(&address), sizeof(address)) == -1)
{
ERROR( "Unable to connect to socket '%s': %s", address.sun_path, strerror(errno));
close(fd);
return -1;
}
LOG("Connected to FastCGI server at %s: %d", g_config.address, fd);
return fd;
}
static int open_tcp_socket()
{
struct sockaddr_in servaddr;
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
PLUGIN_PANIC("Cannot create TCP socket %s:d: %s",g_config.address, g_config.port, strerror(errno));
return -1;
}
bzero(&servaddr, sizeof(servaddr));
// assign IP, PORT
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(g_config.address);
servaddr.sin_port = htons(g_config.port);
// connect the client socket to server socket
if (connect(fd, (struct sockaddr*)&servaddr, sizeof(servaddr))!= 0) {
ERROR( "Unable to connect to socket '%s:%d': %s", g_config.address, g_config.port, strerror(errno));
close(fd);
return -1;
}
LOG("Connected to server: %s:%d at [%d]", g_config.address, g_config.port, fd);
return fd;
}
static int open_socket()
{
if(g_config.port != -1)
{
return open_tcp_socket();
}
else
{
return open_un_socket();
}
}
void init()
{
use_raw_body();
if(read_config() != 0)
return;
// create the socket
//if(create_socket() != 0)
// return;
LOG("FastCGI init successful");
}
void destroy()
{
/*if(g_config.fd)
{
LOG("Close socket: %d", g_config.fd);
close(g_config.fd);
}*/
}
char* read_line(char** buff, int* size)
{
int i = 0;
while(i <= *size-1 && (*buff)[i] != '\n') i++;
if(i > 0 && i <= *size - 1)
(*buff)[i] = '\0';
char* line = *buff;
*size = *size - i - 1;
*buff = *buff + i+1;
return line;
}
static int read_header(antd_client_t* cl, antd_request_t* rq)
{
FCGI_Header header;
antd_response_header_t rhd;
rhd.header = dict();
rhd.cookie = list_init();
rhd.status = 200;
char *k;
char *v;
int len, ret;
regmatch_t matches[3];
uint8_t * payload;
char* line;
char* ptr;
while(cl->state == FCGI_CLIENT_WAIT_FOR_RESPONSE_HEADER)
{
ret = fcgi_read_header(cl,&header);
if(ret < 0)
{
(void)fcgi_abort_request(cl, cl->sock);
LOG("fcgi_read_header() on %d: %s", cl->sock, strerror(errno));
return -1;
}
payload = fcgi_read_payload(cl, &header, &ret);
switch(header.type)
{
case FCGI_STDOUT:
// write data to the other side
if(payload && ret > 0)
{
ptr = (char*)payload;
while(ret > 0)
{
line = read_line(&ptr, &ret);
trim(line, '\r');
if(strlen(line) == 0)
{
cl->state = FCGI_CLIENT_WAIT_FOR_RESPONSE_DATA;
// write out header and the rest of the data
antd_send_header(rq->client, &rhd);
if(ret > 0)
{
if(antd_send(rq->client,ptr, ret) != ret)
{
(void)fcgi_abort_request(cl, cl->sock);
ERROR("Error atnd_send(): %s", strerror(errno));
free(payload);
return -1;
}
}
break;
}
if(ret < 0) break;
if (regex_match("\\s*Status\\s*:\\s+([0-9]{3})\\s+([a-zA-Z0-9 ]*)", line, 3, matches))
{
len = matches[1].rm_eo - matches[1].rm_so;
k = (char *)malloc(len + 1);
memset(k, 0, len + 1);
memcpy(k, line + matches[1].rm_so, len);
rhd.status = atoi(k);
LOG("Response status %d", rhd.status);
free(k);
}
else if (regex_match("^([a-zA-Z0-9\\-]+)\\s*:\\s*(.*)$", line, 3, matches))
{
len = matches[1].rm_eo - matches[1].rm_so;
k = (char *)malloc(len + 1);
memset(k, 0, len + 1);
memcpy(k, line + matches[1].rm_so, len);
verify_header(k);
len = matches[2].rm_eo - matches[2].rm_so;
v = (char *)malloc(len + 1);
memset(v, 0, len + 1);
memcpy(v, line + matches[2].rm_so, len);
LOG("Header [%s] -> [%s]", k, v);
if (strcmp(k, "Set-Cookie") == 0)
{
list_put_ptr(&rhd.cookie, v);
}
else
{
dput(rhd.header, k, v);
}
free(k);
}
else
{
LOG("Ignore invalid header: %s", line);
}
}
}
break;
case FCGI_STDERR:
if(payload && ret > 0)
{
ERROR("%s", (char*) payload);
}
break;
case FCGI_END_REQUEST:
LOG("End request received, this should not happen %d", cl->sock);
// FCGI_EndRequestBody* body = (FCGI_EndRequestBody*) payload;
if(payload) free(payload);
return -1;
default:
LOG("Unsupported record type: 0x%02x", header.type);
break;
}
if(payload) free(payload);
}
return 0;
}
static int read_data(antd_client_t* cl, antd_request_t* rq)
{
FCGI_Header header;
int ret = fcgi_read_header(cl,&header);
if(ret < 0)
{
(void)fcgi_abort_request(cl, cl->sock);
LOG("fcgi_read_header() on %d: %s", cl->sock, strerror(errno));
return -1;
}
uint8_t * payload = fcgi_read_payload(cl, &header, &ret);
switch(header.type)
{
case FCGI_STDOUT:
// write data to the other side
if(payload && ret > 0)
{
if(antd_send(rq->client,payload, ret) != ret)
{
(void)fcgi_abort_request(cl, cl->sock);
ERROR("Error atnd_send(): %s", strerror(errno));
free(payload);
return -1;
}
}
break;
case FCGI_STDERR:
if(payload && ret > 0)
{
ERROR("%s", (char*) payload);
}
break;
case FCGI_END_REQUEST:
LOG("End request received, close connection %d", cl->sock);
if(payload) free(payload);
return -1;
default:
LOG("Unsupported record type: 0x%02x", header.type);
break;
}
if(payload) free(payload);
return 0;
}
static void *process(void *data)
{
antd_request_t *rq = (antd_request_t *)data;
antd_client_t* cl = (antd_client_t* ) dvalue(rq->request, "FCGI_CL_DATA");
struct pollfd pfds[2];
pfds[0].fd = rq->client->sock;
pfds[0].events = POLLIN;
if(rq->client->ssl)
{
pfds[0].events = POLLIN | POLLOUT;
}
pfds[1].fd = cl->sock;
pfds[1].events = POLLIN;
if(cl->state == FCGI_CLIENT_REQUEST_SENT)
{
(void)fcgi_send_stdin(cl, cl->sock, NULL, 0, 0);
if (ws_enable(rq->request))
{
cl->state = FCGI_CLIENT_WAIT_FOR_RESPONSE_DATA;
}
else
{
cl->state = FCGI_CLIENT_WAIT_FOR_RESPONSE_HEADER;
}
}
int rc = poll(pfds, 2, PROCESS_TIMEOUT);
antd_task_t* task;
uint8_t buff[BUFFLEN];
int ret;
switch (rc)
{
case -1:
ERROR("Error on poll(): %s", strerror(errno));
(void)fcgi_abort_request(cl, cl->sock);
antd_close(cl);
dput(rq->request, "FCGI_CL_DATA", NULL);
return antd_create_task(NULL, data, NULL, rq->client->last_io);
case 0:
// time out
task = antd_create_task(process, (void *)rq, NULL, time(NULL));
antd_task_bind_event(task, rq->client->sock, 0, TASK_EVT_ON_WRITABLE | TASK_EVT_ON_READABLE);
antd_task_bind_event(task, cl->sock, 0, TASK_EVT_ON_READABLE);
return task;
// we have data
default:
// If data is on webserver
if ((pfds[0].revents & POLLIN) || (rq->client->ssl && (pfds[0].revents & POLLOUT)) )
{
while((ret = antd_recv_upto(rq->client,buff, BUFFLEN)) > 0)
{
// write data to the application stdin
if(fcgi_send_stdin(cl, cl->sock,buff, ret, (ret % 8 == 0)? 0 : 8 - (ret % 8) ) != 0)
{
ERROR("Error on fcgi_send_stdin(): %s", strerror(errno));
(void)fcgi_abort_request(cl, cl->sock);
antd_close(cl);
dput(rq->request, "FCGI_CL_DATA", NULL);
return antd_create_task(NULL, data, NULL, rq->client->last_io);
}
if(cl->state > 0)
cl->state -= ret;
LOG("sending %s: %d", buff, cl->state);
}
if(ret < 0)
{
LOG("antd_recv_upto() on %d: %s",rq->client->sock, strerror(errno));
(void)fcgi_abort_request(cl, cl->sock);
antd_close(cl);
dput(rq->request, "FCGI_CL_DATA", NULL);
return antd_create_task(NULL, data, NULL, rq->client->last_io);
}
}
else if(pfds[0].revents &(POLLERR | POLLHUP))
{
ERROR("POLLERR or POLLHUP received on %d", rq->client->sock);
(void)fcgi_abort_request(cl, cl->sock);
antd_close(cl);
dput(rq->request, "FCGI_CL_DATA", NULL);
return antd_create_task(NULL, data, NULL, rq->client->last_io);
}
if(pfds[1].revents & POLLIN)
{
if(cl->state == FCGI_CLIENT_WAIT_FOR_RESPONSE_HEADER)
{
if(read_header(cl, rq) != 0)
{
antd_close(cl);
dput(rq->request, "FCGI_CL_DATA", NULL);
return antd_create_task(NULL, data, NULL, rq->client->last_io);
}
}
else
{
if(read_data(cl,rq) != 0)
{
antd_close(cl);
dput(rq->request, "FCGI_CL_DATA", NULL);
return antd_create_task(NULL, data, NULL, rq->client->last_io);
}
}
}
else if(pfds[1].revents &(POLLERR | POLLHUP))
{
ERROR("POLLERR or POLLHUP received on %d", cl->sock);
//(void)fcgi_abort_request(cl, cl->sock);
antd_close(cl);
dput(rq->request, "FCGI_CL_DATA", NULL);
return antd_create_task(NULL, data, NULL, rq->client->last_io);
}
task = antd_create_task(process, (void *)rq, NULL, time(NULL));
antd_task_bind_event(task, rq->client->sock, 0, TASK_EVT_ON_WRITABLE | TASK_EVT_ON_READABLE);
antd_task_bind_event(task, cl->sock, 0, TASK_EVT_ON_READABLE);
return task;
}
}
static int send_request(antd_client_t *cl, antd_request_t* rq)
{
int ret = 0;
char *tmp = NULL;
char *sub = NULL;
char *root;
dictionary_t request = (dictionary_t)rq->request;
dictionary_t header = (dictionary_t)dvalue(rq->request, "REQUEST_HEADER");
ret += fcgi_begin_request(cl, cl->sock, FCGI_RESPONDER, 0);
//ret += fcgi_send_param(cl, cl->sock, "", "");
ret += fcgi_send_param(cl, cl->sock, "GATEWAY_INTERFACE", "CGI/1.1");
ret += fcgi_send_param(cl, cl->sock, "SERVER_SOFTWARE", SERVER_NAME);
root = (char *)dvalue(request, "SERVER_WWW_ROOT");
tmp = (char *)dvalue(request, "REQUEST_QUERY");
if (!tmp)
{
ret += fcgi_send_param(cl, cl->sock, "QUERY_STRING", "");
}
else
{
ret += fcgi_send_param(cl, cl->sock, "REQUEST_URI", tmp);
sub = strchr(tmp, '?');
if (sub)
{
sub++;
ret += fcgi_send_param(cl, cl->sock, "QUERY_STRING", sub);
}
else
{
ret += fcgi_send_param(cl, cl->sock, "QUERY_STRING", "");
}
}
tmp = (char *)dvalue(request, "METHOD");
if (tmp)
{
ret += fcgi_send_param(cl, cl->sock, "REQUEST_METHOD", tmp);
}
tmp = (char *)dvalue(header, "Content-Type");
if (tmp)
{
ret += fcgi_send_param(cl, cl->sock, "CONTENT_TYPE", tmp);
}
else
{
ret += fcgi_send_param(cl, cl->sock, "CONTENT_TYPE", "");
}
tmp = (char *)dvalue(header, "Content-Length");
if (tmp)
{
cl->state = atoi(tmp);
ret += fcgi_send_param(cl, cl->sock, "CONTENT_LENGTH", tmp);
}
else
{
ret += fcgi_send_param(cl, cl->sock, "CONTENT_LENGTH", "");
}
ret += fcgi_send_param(cl, cl->sock, "DOCUMENT_ROOT", root);
tmp = (char *)dvalue(request, "REQUEST_PATH");
if (tmp)
{
sub = tmp;
while (*sub == '/')
sub++;
if (sub)
{
ret += fcgi_send_param(cl, cl->sock, "PATH_INFO", sub);
}
else
{
ret += fcgi_send_param(cl, cl->sock, "PATH_INFO", "");
}
}
else
{
ret += fcgi_send_param(cl, cl->sock, "PATH_INFO", "");
}
tmp = (char *)dvalue(request, "REMOTE_ADDR");
if(tmp)
{
ret += fcgi_send_param(cl, cl->sock, "REMOTE_ADDR", tmp);
ret += fcgi_send_param(cl, cl->sock, "REMOTE_HOST", tmp);
}
ret += fcgi_send_param(cl, cl->sock, "SERVER_NAME", SERVER_NAME);
ret += fcgi_send_param(cl, cl->sock, "SERVER_PORT", (char *)dvalue(request, "SERVER_PORT"));
ret += fcgi_send_param(cl, cl->sock, "SERVER_PROTOCOL", "HTTP/1.1");
// add remaining header to the vars
chain_t it;
for_each_assoc(it, header)
{
tmp = __s("HTTP_%s", it->key);
char *s = tmp;
while (*s)
{
if (*s == '-')
*s = '_';
else if (*s != '_')
*s = toupper((char)*s);
s++;
}
ret += fcgi_send_param(cl, cl->sock, tmp, (char *)it->value);
free(tmp);
}
tmp = (char *)dvalue(request, "RESOURCE_PATH");
if (tmp)
{
ret += fcgi_send_param(cl, cl->sock, "SCRIPT_NAME", basename(tmp));
tmp = __s("%s/%s", root, tmp);
ret += fcgi_send_param(cl, cl->sock, "SCRIPT_FILENAME", tmp);
ret += fcgi_send_param(cl, cl->sock, "PATH_TRANSLATED", tmp);
free(tmp);
}
else
{
ret += fcgi_send_param(cl, cl->sock, "SCRIPT_FILENAME", "");
ret += fcgi_send_param(cl, cl->sock, "PATH_TRANSLATED", "");
ret += fcgi_send_param(cl, cl->sock, "SCRIPT_NAME", "");
}
// redirect status for php
ret += fcgi_send_param(cl, cl->sock, "REDIRECT_STATUS", "200");
ret += fcgi_send_param(cl, cl->sock, "", "");
return ret;
}
void* handle(void* data)
{
antd_request_t *rq = (antd_request_t *)data;
// connect to socket
int fd = open_socket();
if(fd < 0)
{
antd_error(rq->client, 503, "Service unavailable");
return antd_create_task(NULL, data, NULL, rq->client->last_io);
}
set_nonblock(fd);
// write all header to fastCGI server via params
antd_client_t* cl = (antd_client_t*) malloc(sizeof(antd_client_t));
(void)memset(cl, 0, sizeof(antd_client_t));
cl->sock = fd;
time(&cl->last_io);
cl->ssl = NULL;
// state is used to store content lenth of the current request
cl->state = FCGI_CLIENT_REQUEST_SENT;
cl->z_status = 0;
cl->z_level = ANTD_CNONE;
cl->zstream = NULL;
rq->client->z_level = ANTD_CNONE;
// start the request
if(send_request(cl,rq) != 0)
{
ERROR("Unable to send request to application: %d", fd);
antd_error(rq->client, 500, "Internal server error");
(void)fcgi_abort_request(cl, cl->sock);
antd_close(cl);
return antd_create_task(NULL, data, NULL, rq->client->last_io);
}
dput(rq->request, "FCGI_CL_DATA", cl);
antd_task_t* task = antd_create_task(process, (void *)rq, NULL, time(NULL));
//antd_task_bind_event(task, rq->client->sock, 0, TASK_EVT_ON_WRITABLE | TASK_EVT_ON_READABLE);
//antd_task_bind_event(task, fd, 0, TASK_EVT_ON_READABLE);
return task;
}

279
proto.c Normal file
View File

@ -0,0 +1,279 @@
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "proto.h"
#define PARAMS_LENGTH_11 ((0<<0) | (0<<1))
#define PARAMS_LENGTH_14 ((0<<0) | (1<<1))
#define PARAMS_LENGTH_41 ((1<<0) | (0<<1))
#define PARAMS_LENGTH_44 ((1<<0) | (1<<1))
typedef struct {
unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */
unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
char value[0];
} FCGI_NameValuePair11;
typedef struct {
unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */
unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
unsigned char valueLengthB2;
unsigned char valueLengthB1;
unsigned char valueLengthB0;
char value[0];
} FCGI_NameValuePair14;
typedef struct {
unsigned char nameLengthB3; /* nameLengthB3 >> 7 == 1 */
unsigned char nameLengthB2;
unsigned char nameLengthB1;
unsigned char nameLengthB0;
unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
char value[0];
} FCGI_NameValuePair41;
typedef struct {
unsigned char nameLengthB3; /* nameLengthB3 >> 7 == 1 */
unsigned char nameLengthB2;
unsigned char nameLengthB1;
unsigned char nameLengthB0;
unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
unsigned char valueLengthB2;
unsigned char valueLengthB1;
unsigned char valueLengthB0;
char value[0];
} FCGI_NameValuePair44;
typedef union {
FCGI_NameValuePair11 d11;
FCGI_NameValuePair14 d14;
FCGI_NameValuePair41 d41;
FCGI_NameValuePair44 d44;
} FCGI_Params_Body;
static int fcgi_send_record(antd_client_t* cl, FCGI_Header* header, uint8_t* buff, size_t len)
{
if(!header)
{
ERROR("Record header should not empty");
return -1;
}
// send the header
int ret = antd_send(cl, (uint8_t*)header, sizeof(FCGI_Header));
if(ret != sizeof(FCGI_Header))
{
ERROR("fcgi_send_record: Unable to send record header, only %d of %d bytes sent: %s", ret, sizeof(FCGI_Header), strerror(errno));
return -1;
}
if(!buff)
{
return 0;
}
// send the data
ret = antd_send(cl, (uint8_t*)buff, len);
if(ret != (int)len)
{
ERROR("fcgi_send_record: Unable to send record data, only %d of %d bytes sent", ret, len);
return -1;
}
return 0;
}
int fcgi_begin_request(antd_client_t* cl, uint16_t id, uint16_t role, uint8_t flags)
{
FCGI_BeginRequestRecord record;
record.header.version = FCGI_VERSION_1;
record.header.type = FCGI_BEGIN_REQUEST;
record.header.requestIdB1 = id >> 8;
record.header.requestIdB0 = id & 0xFF;
record.header.contentLengthB1 = 0;
record.header.contentLengthB0 = 8;
record.header.paddingLength = 0;
record.body.roleB1 = role >> 8;
record.body.roleB0 = role & 0xFF;
record.body.flags = flags;
int ret = antd_send(cl, (uint8_t*)&record, sizeof(record));
if(ret != sizeof(record))
{
ERROR("fcgi_begin_request: Unable to send record data, only %d of %d bytes sent", ret, sizeof(record));
return -1;
}
return 0;
}
int fcgi_abort_request(antd_client_t* cl, uint16_t id)
{
FCGI_Header header;
header.version = FCGI_VERSION_1;
header.type = FCGI_BEGIN_REQUEST;
header.requestIdB1 = id >> 8;
header.requestIdB0 = id & 0xFF;
header.contentLengthB1 = 0;
header.contentLengthB0 = 0;
header.paddingLength = 0;
int ret = antd_send(cl, (uint8_t*)&header, sizeof(header));
if(ret != sizeof(header))
{
ERROR("fcgi_abort_request: Unable to send record data, only %d of %d bytes sent", ret, sizeof(header));
return -1;
}
return 0;
}
int fcgi_send_param(antd_client_t* cl, int id, const char* key, const char* value)
{
size_t k_length = strlen(key);
size_t v_length = strlen(value);
//LOG("sending [%s] -> [%s]", key, value);
FCGI_Params_Body* body = NULL;
uint8_t* buff = NULL;
size_t clen = k_length + v_length;
if(clen > 0)
{
size_t max_buff_len = sizeof(FCGI_Params_Body) + k_length + v_length + 8;
buff = (uint8_t*)malloc(max_buff_len);
if(!buff)
{
ERROR("Unable to allocate PARAMS record buffer memory: %s", strerror(errno));
return -1;
}
}
body = (FCGI_Params_Body*) buff;
FCGI_Header header;
header.version = FCGI_VERSION_1;
header.type = FCGI_PARAMS;
header.requestIdB1 = id >> 8;
header.requestIdB0 = id & 0xFF;
if(clen > 0)
{
uint8_t encoding_type = (((k_length & 0xFF) >> 7) << 0) | (((v_length & 0xFF)>>7) << 1);
switch(encoding_type)
{
case PARAMS_LENGTH_11:
body->d11.nameLengthB0 = k_length;
body->d11.valueLengthB0 = v_length;
memcpy(body->d11.value, key, k_length);
memcpy(body->d11.value+k_length, value, v_length);
clen += 2;
break;
case PARAMS_LENGTH_14:
body->d14.nameLengthB0 = k_length;
body->d14.valueLengthB3 = (v_length >> 24) | 0x80;
body->d14.valueLengthB2 = (v_length >> 16) & 0xFF;
body->d14.valueLengthB1 = (v_length >> 8) & 0xFF;
body->d14.valueLengthB0 = v_length & 0xFF;
memcpy(body->d14.value, key, k_length);
memcpy(body->d14.value+k_length, value, v_length);
clen += 5;
break;
case PARAMS_LENGTH_41:
body->d41.valueLengthB0 = v_length;
body->d41.nameLengthB3 = (k_length >> 24) | 0x80;
body->d41.nameLengthB2 = (k_length >> 16) & 0xFF;
body->d41.nameLengthB1 = (k_length >> 8) & 0xFF;
body->d41.nameLengthB0 = k_length & 0xFF;
memcpy(body->d41.value, key, k_length);
memcpy(body->d41.value+k_length, value, v_length);
clen += 5;
break;
case PARAMS_LENGTH_44:
body->d44.nameLengthB3 = (k_length >> 24) | 0x80;
body->d44.nameLengthB2 = (k_length >> 16) & 0xFF;
body->d44.nameLengthB1 = (k_length >> 8) & 0xFF;
body->d44.nameLengthB0 = k_length & 0xFF;
body->d44.valueLengthB3 = (v_length >> 24) | 0x80;
body->d44.valueLengthB2 = (v_length >> 16) & 0xFF;
body->d44.valueLengthB1 = (v_length >> 8) & 0xFF;
body->d44.valueLengthB0 = v_length & 0xFF;
memcpy(body->d44.value, key, k_length);
memcpy(body->d44.value+k_length, value, v_length);
clen += 8;
break;
default:
// this should never happends
free(buff);
return -1;
}
}
header.contentLengthB1 = clen >> 8;
header.contentLengthB0 = clen & 0xFF;
header.paddingLength = (clen % 8 == 0)? 0 : 8 - (clen % 8);
// send the record
int ret = fcgi_send_record(cl, &header, buff, clen + header.paddingLength);
if(buff)
free(buff);
return ret;
}
int fcgi_send_stdin(antd_client_t* cl, int id, uint8_t* padded_data, size_t len, uint8_t paddlen)
{
FCGI_Header header;
header.version = FCGI_VERSION_1;
header.type = FCGI_STDIN;
header.requestIdB1 = id >> 8;
header.requestIdB0 = id & 0xFF;
header.contentLengthB1 = len >> 8;
header.contentLengthB0 = len & 0xFF;
header.paddingLength = paddlen;
// send the record
return fcgi_send_record(cl, &header, padded_data, len + paddlen);
}
int fcgi_read_header(antd_client_t* cl, FCGI_Header* header)
{
uint8_t* buff = (uint8_t*) header;
int ret = antd_recv(cl, buff, sizeof(FCGI_Header));
if(ret != sizeof(FCGI_Header))
{
ERROR("Unable to read header: received %d bytes out of %d bytes", ret, sizeof(FCGI_Header));
return -1;
}
return 0;
}
int fcgi_read_data(antd_client_t* cl, FCGI_Header* header, uint8_t* buffer)
{
int len = ((header->contentLengthB1 << 8) | header->contentLengthB0) + header->paddingLength;
int ret = antd_recv(cl, buffer, len);
if(ret != len)
{
ERROR("Unable to read record body: received %d bytes out of %d bytes", ret, len);
return -1;
}
return 0;
}
uint8_t* fcgi_read_payload(antd_client_t* cl, FCGI_Header* header, int* size)
{
int len = ((header->contentLengthB1 << 8) | header->contentLengthB0) + header->paddingLength;
uint8_t* buff = (uint8_t*) malloc(len + 1);
if(!buff)
{
ERROR("Unable to allocate buffer of size %d", len);
return NULL;
}
int ret = antd_recv(cl, buff, len);
if(ret != len)
{
ERROR("Unable to read record body: received %d bytes out of %d bytes", ret, len);
free(buff);
return NULL;
}
*size = len - header->paddingLength;
buff[*size] = '\0';
return buff;
}

165
proto.h Normal file
View File

@ -0,0 +1,165 @@
#ifndef PROTO_H
#define PROTO_H
#include <unistd.h>
#include <antd/plugin.h>
/*
* Listening socket file number
*/
#define FCGI_LISTENSOCK_FILENO 0
typedef struct {
unsigned char version;
unsigned char type;
unsigned char requestIdB1;
unsigned char requestIdB0;
unsigned char contentLengthB1;
unsigned char contentLengthB0;
unsigned char paddingLength;
unsigned char reserved;
} FCGI_Header;
/*
* Number of bytes in a FCGI_Header. Future versions of the protocol
* will not reduce this number.
*/
#define FCGI_HEADER_LEN 8
/*
* Value for version component of FCGI_Header
*/
#define FCGI_VERSION_1 1
/*
* Values for type component of FCGI_Header
*/
#define FCGI_BEGIN_REQUEST 1
#define FCGI_ABORT_REQUEST 2
#define FCGI_END_REQUEST 3
#define FCGI_PARAMS 4
#define FCGI_STDIN 5
#define FCGI_STDOUT 6
#define FCGI_STDERR 7
#define FCGI_DATA 8
#define FCGI_GET_VALUES 9
#define FCGI_GET_VALUES_RESULT 10
#define FCGI_UNKNOWN_TYPE 11
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
/*
* Value for requestId component of FCGI_Header
*/
#define FCGI_NULL_REQUEST_ID 0
typedef struct {
unsigned char roleB1;
unsigned char roleB0;
unsigned char flags;
unsigned char reserved[5];
} FCGI_BeginRequestBody;
typedef struct {
FCGI_Header header;
FCGI_BeginRequestBody body;
} FCGI_BeginRequestRecord;
/*
* Mask for flags component of FCGI_BeginRequestBody
*/
#define FCGI_KEEP_CONN 1
/*
* Values for role component of FCGI_BeginRequestBody
*/
#define FCGI_RESPONDER 1
#define FCGI_AUTHORIZER 2
#define FCGI_FILTER 3
typedef struct {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus;
unsigned char reserved[3];
} FCGI_EndRequestBody;
typedef struct {
FCGI_Header header;
FCGI_EndRequestBody body;
} FCGI_EndRequestRecord;
/*
* Values for protocolStatus component of FCGI_EndRequestBody
*/
#define FCGI_REQUEST_COMPLETE 0
#define FCGI_CANT_MPX_CONN 1
#define FCGI_OVERLOADED 2
#define FCGI_UNKNOWN_ROLE 3
/*
* Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
*/
#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
#define FCGI_MAX_REQS "FCGI_MAX_REQS"
#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
typedef struct {
unsigned char type;
unsigned char reserved[7];
} FCGI_UnknownTypeBody;
typedef struct {
FCGI_Header header;
FCGI_UnknownTypeBody body;
} FCGI_UnknownTypeRecord;
/**
* The role component sets the role the Web server expects the application to play. The currently-defined roles are:
* FCGI_RESPONDER
* FCGI_AUTHORIZER
* FCGI_FILTER
* The flags component contains a bit that controls connection shutdown:
* flags & FCGI_KEEP_CONN: If zero, the application closes the connection
* after responding to this request. If not zero, the application does not
* close the connection after responding to this request; the Web server
* retains responsibility for the connection
*/
int fcgi_begin_request(antd_client_t* cl, uint16_t id, uint16_t role, uint8_t flags);
/**
* The Web server sends a FCGI_ABORT_REQUEST record to abort a request. After receiving {FCGI_ABORT_REQUEST, R},
* the application responds as soon as possible with {FCGI_END_REQUEST, R, {FCGI_REQUEST_COMPLETE, appStatus}}.
* This is truly a response from the application, not a low-level acknowledgement from the FastCGI library.
* A Web server aborts a FastCGI request when an HTTP client closes its transport connection while the FastCGI request is running on behalf of that client.
*/
int fcgi_abort_request(antd_client_t* cl, uint16_t id);
/**
* FCGI_PARAMS is a stream record type used in sending name-value pairs from the Web server to the application. The name-value pairs are sent down the stream one after the other, in no specified order.
*/
int fcgi_send_param(antd_client_t* cl, int id, const char* key, const char* value);
/**
* FCGI_STDIN is a stream record type used in sending arbitrary data from the Web server to the application.
* FCGI_DATA is a second stream record type used to send additional data to the application.
*
* The param data should be padded buffer with size len + paddlen
*/
int fcgi_send_stdin(antd_client_t* cl, int id, uint8_t* padded_data, size_t len, uint8_t paddlen);
/**
* Read record header from application
*/
int fcgi_read_header(antd_client_t* cl, FCGI_Header* header);
/**
* Read error message from stderr
*
* It is the responisbility of the caller to make
* sure that the buffer lenth is big enough for the
* Content data + padded len
*/
int fcgi_read_data(antd_client_t* cl, FCGI_Header* header, uint8_t* buffer);
uint8_t* fcgi_read_payload(antd_client_t* cl, FCGI_Header* header, int* size);
#endif