Skip to content

fcgi

Some ancient but very performant technique. Here is a quick guide to get it working with apache2 and libfcgi.

introduction

The principle is to have a server running at a given port, possibly even with a unix socket connection. The server is started up separately using the spawn-fcgi program. Apache2 is configured to talk to the server.

documentation

visit visit visit

installation

Mostly we need apache2 running, and libfcgi and spawn-fcgi for the server.

install
apt-get install libapache2-mod-proxy-fcgi spawn-fcgi libfcgi-dev

the apache config than has to be setup to redirect certain requests directly to the fcgi program loop. A rather minimal config would be :

apache
1
2
3
4
<VirtualHost *:80>
   ServerName 192.168.1.151
   ProxyPass "/ws" "fcgi://localhost:8000/" enablereuse=off
</VirtualHost>

Note that the servername seems to be needed. And when you can't use localhost anymore. But now it is reachable from all machines in the network.

Do NOT use enablereuse=on because this does result in crashing behavior.

Really use localhost NOT 192.168.1.151, or setup the proxy to use that ip address.

For the planner app, this means changing the line in src/environments/environment.ts to :

  apiUrl: "http://localhost", /* localhost */

This just can be an extra line added to an existing site, very easy. Note that it is an extension of mod-proxy that we are using !!, apache relays the requests to port 8000 as a normal proxy.

Note also that a multi-server solution would be something like :

multiple fcgi services
1
2
3
4
5
6
Balanced gateway to multiple application instances
ProxyPass "/myapp/" "balancer://myappcluster/"
<Proxy "balancer://myappcluster/">
   BalancerMember "fcgi://hosta:4000"
   BalancerMember "fcgi://hostb:4000"
</Proxy>

The example at apache use localhost with two different ports, so this may have to be tested first.

Now everything for /ws get's sent to localhost port 8000. At this moment nothing is listening there so :

wget tryout
1
2
3
4
5
6
7
wget localhost/ws/

--2019-08-01 14:28:55--  http://localhost/ws
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:80... connected.
HTTP request sent, awaiting response... 503 Service Unavailable
2019-08-01 14:28:55 ERROR 503: Service Unavailable.

the server

We need a program that listens for FCGI requests in a loop and handle them. Here is a simple echo implementation. :

server script
#include "fcgi_config.h"

#include <stdio.h>
#include <stdlib.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
extern char **environ;

#include "fcgiapp.h"

static void PrintEnv(FCGX_Stream *out, char *label, char **envp)
{
   FCGX_FPrintF(out, "%s:<br>n<pre>n", label);
   for( ; *envp != NULL; envp++) {
      FCGX_FPrintF(out, "%sn", *envp);
   }
   FCGX_FPrintF(out, "</pre><p>n");
}


int main ()
{
    FCGX_Stream *in, *out, *err;
    FCGX_ParamArray envp;
    int count = 0;
    int ret=0;

    while ((ret = FCGX_Accept(&in, &out, &err, &envp)) >= 0) {
        char *contentLength = FCGX_GetParam("CONTENT_LENGTH", envp);
        printf("Length is %sn", contentLength);
        int len = 0;

        FCGX_FPrintF(out,
           "Content-type: text/htmlrn"
           "rn"
           "<title>FastCGI echo (fcgiapp version)</title>"
           "<h1>FastCGI echo (fcgiapp version)</h1>n"
           "Request number %d,  Process ID: %d<p>n", ++count, getpid());

        if (contentLength != NULL)
            len = strtol(contentLength, NULL, 10);

        if (len <= 0) {
            FCGX_FPrintF(out, "No data from standard input.<p>n");
        }
        else {
            int i, ch;

            FCGX_FPrintF(out, "Standard input:<br>n<pre>n");
            for (i = 0; i < len; i++) {
                if ((ch = FCGX_GetChar(in)) < 0) {
                    FCGX_FPrintF(out,
                        "Error: Not enough bytes received on standard input<p>n");
                    break;
                }
                FCGX_PutChar(ch, out);
            }
            FCGX_FPrintF(out, "n</pre><p>n");
        }

        PrintEnv(out, "Request environment", envp);
        PrintEnv(out, "Initial environment", environ);
    } /* while */

    printf("Dat was %dn", ret);

    return 0;
}

When you compile this and run it nothing much happens:

compile and run
gcc test.c -lfcgi 
./a.out 

But spawn it with this command:

spawn
spawn-fcgi -p 8000 ./a.out
spawn-fcgi: child spawned successfully: PID: 8806

Now you can browse to visit

You will see a page dumping the whole environment, if you look at REQUEST_URI you will see

uri
REQUEST_URI=/ws/withanything/attached

So it can be used to build a REST interface quit easy. Keep this echo implementation around for debugging purposes.

stopping the server

Practical guide :

stopping
1
2
3
4
5
netstat -ltnp | grep 8000

tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      4502/./a.out

kill -9 4502

building the server

Now to get more out of this, more will follow. First lets tryout a multi-thread version because we do want the server to remain reactive. Most request will take more than seconds so we don't want to wait for the results but must be able to accept the next request.

The main outline is :

building
#include "fcgi_config.h"

#include <pthread.h>
#include <sys/types.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "fcgiapp.h"

#define THREAD_COUNT 20

static int counts[THREAD_COUNT];

static void *doit(void *a)
{
    int64_t rc, i, thread_id = (int64_t)a;
    pid_t pid = getpid();
    FCGX_Request request;
    char *server_name;

    FCGX_InitRequest(&request, 0, 0);

    for (;;)
    {
        static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER;
        static pthread_mutex_t counts_mutex = PTHREAD_MUTEX_INITIALIZER;

        /* Some platforms require accept() serialization, some don't.. */
        pthread_mutex_lock(&accept_mutex);
        rc = FCGX_Accept_r(&request);
        pthread_mutex_unlock(&accept_mutex);

        if (rc < 0)
            break;

        server_name = FCGX_GetParam("SERVER_NAME", request.envp);

        FCGX_FPrintF(request.out,
            "Content-type: text/htmlrn"
            "rn"
            "<title>FastCGI Hello! (multi-threaded C, fcgiapp library)</title>"
            "<h1>FastCGI Hello! (multi-threaded C, fcgiapp library)</h1>"
            "Thread %d, Process %ld<p>"
            "Request counts for %d threads running on host <i>%s</i><p><code>",
            thread_id, pid, THREAD_COUNT, server_name ? server_name : "?");

        sleep(2);

        pthread_mutex_lock(&counts_mutex);
        ++counts[thread_id];
        for (i = 0; i < THREAD_COUNT; i++)
            FCGX_FPrintF(request.out, "%5d " , counts[i]);
        pthread_mutex_unlock(&counts_mutex);

        FCGX_Finish_r(&request);
    }

    return NULL;
}

int main(void)
{
    int64_t i;
    pthread_t id[THREAD_COUNT];
    FCGX_Init();

    for (i = 1; i < THREAD_COUNT; i++)
        pthread_create(&id[i], NULL, doit, (void*)i);

    doit(0);
    return 0;
}

debugging fast CGI

The standalone method is more powerful since you can also test valgrind etc. but the gdb attach method is still very useful too.

standalone method

This discussion tries to open the socket as a unix socket :

visit

If this link dies: this is the solution :

debugging
#if STANDALONE
   int sockfd = FCGX_OpenSocket("/tmp/fcgiTest.socket", 1024);
   char command[] = "chmod ag+rwx /tmp/fcgiTest.socket";
   system( command );
#endif
   FCGX_Request request;
   FCGX_Init();

#if STANDALONE
   FCGX_InitRequest(&request, sockfd, 0);
#else
   FCGX_InitRequest(&request, 0, 0);
#endif

This does startup the server locally, but how to reach the socket ? Then this discussion mentions it can be on a port as well :

visit

In short, open the socket like this :

open socket
FCGX_OpenSocket(":8000", 1024);

See fcgi.c in the backend server for a working version of this. Now you can valgrind the server !!

profile
valgrind debug/backend

gdb attach

The best way I see is attaching gdb to the process id of the fcgi server and setting a breakpoint.

gdb
1
2
3
4
sudo apt-get install net-tools # if not installed
ps -elf | grep klopt_rte
# or netstat -lntp | grep 8000
gdb --pid=xxxx
of course today we would use killall
killall -9 backend # testing
killall -9 klopt_rte # production

Now you start in a halted gdb session so you can move up and down the stack to decide where to put a breakpoint. I would suggest just after the FCGX_Accept_r() call. Then continue the server and post a request on the apache site.

Also as a debug aid you can print anything onto the webpage but a header is needed because it is to the browser, so :

header
1
2
3
4
FCGX_FPrintF(request.out,
    "Content-type: text/htmlrn"
    "rn"
    "FastCGI Hello! %d", 22);

Of course you don't need to use the browser, the old test scripts worked on the url endpoint '/ws/' so after changing the loop into a fcgi loop and configuring apache to proxy '/ws/' to the 'new' klopt_rte server. The out put of the above program delivers this :

test ping
1
2
3
./ping.py 
{"jsonrpc":"2.0","method":"ping","id":33}
<title>FastCGI Hello! (multi-threaded C, fcgiapp library)</title><h1>FastCGI Hello! (multi-threaded C, fcgiapp library)</h1>Thread 0, Process 22012<p>Request counts for 20 threads running on host <i>localhost</i><p><code>

Now we like to detect if this is a POST or GET, but which parameter to get. A handy function to dump the complete environment was printed earlier : PrintEnv(), and it is very helpful to have that present for debugging. Also i keep forgetting to print the header, and you get no output without it so :

don't forget the header
static void PrintHdr(FCGX_Stream *out) {
FCGX_FPrintF(out,
    "Content-type: text/htmlrn"
    "rn");
}

static void PrintEnv(FCGX_Stream *out, char **envp)
{
   for( ; *envp != NULL; envp++) {
      FCGX_FPrintF(out, "%sn", *envp);
   }
   FCGX_FPrintF(out, "</pre><p>n");
}


...

     PrintHdr(request.out);
     PrintEnv(request.out, request.envp);

...

In the list printed you can clearly see : REQUEST_METHOD=POST and CONTENT_LENGTH=41. (this was for a ping example) Strangely just reading from either stdin or FCGX_stdin does not give anything, and so does reading with FCGX_fread().

The only way to get this working is the example from libfcgi itself, the echo-cpp.cpp program : https://github.com/jocelyn-old/libfcgi/blob/master/examples/echo-cpp.cpp

The main part we used is this function :

echo-cpp.cpp
https://github.com/jocelyn-old/libfcgi/blob/master/examples/echo-cpp.cpp

It uses a separate function for reading the POST data, and also it replaces the cin,cout and cerr handles for the duration of the loop and replaces them with the ones from the 'request'. That does not seem actually necessary so try to make a shorter version (todo)

cors problems

First of all uninstall the CORS plugin that was advised by the angular tutorials to get rid of this error :

Error

'Access-Control-Allow-Origin' header contains multiple values 'http://evil.com/, *', but only one is allowed. Origin 'http://my-app-name.rhcloud.com' is therefore not allowed access.

The extension writer thought to be clever in adding http://evil.com as a reminder that CORS is useful. However this leads to another error because it now specifies two options. Disable the extension and do it in apache2 ! : In the VirtualHost section where the ProxyPass line is add These Header lines :

allow-headers
1
2
3
4
Header set Access-Control-Allow-Headers "*"
Header set Access-Control-Allow-Origin "*"
...
ProxyPass "/ws" "fcgi://localhost:8000/" enablereuse=off

We only get the CORS message because or web service is on a different port but still localhost. Reload apache and if you get an error don't forget to :

reload
a2enmod headers
systemctl reload apache2 

2023 findings

I first got much trouble starting the backend program but it probably is because the command line is not a feasible way to start the server.

If you compile it standalone it will start, but it never listens to connections.

endless wait
./debug/backend 
sok: 3
Loop ... 0
Init ... 

telnet connect will detect that something is listening on port 8000, but it won't enter the code after accept.

WEB_VERSION

In the makefile you can set this value to create a version that is started by apache itself.

Disabling WEB_VERSION does not make debug/backend standalone !!

It still only work through fcgi + apache !! You just start it from the command line.

The non web version is very handy for debugging, because you CAN start it under gdb !

Just don't try to connect to it's port (6666)!!

It will not work !. If you would want that, you have to use a plain listen/accept.

You can get the data by browsing to http://192.168.1.151/ws/rest/orders. Or with wget.

startup with fcgi-spawn