FastCGI

The FastCGI protocol allows the implementation of persistent server-side web applications. In contrast to the older Common Gateway Interface standard, FastCGI provides improved scalability, as processes handle multiple requests over their lifetime. Most modern web servers feature FastCGI passthrough, such as nginx, lighttpd, or Apache HTTP Server.

The request is passed to the FastCGI application context using environment variables, but interface bindings are required to accept incoming request and to return any response. In order to run FastCGI applications written in Fortran, we will need:

On FreeBSD, install the packages nginx, FastCGI, and spawn-fcgi by running:

# pkg install www/nginx www/fcgi www/spawn-fcgi

The packages should be available on most Linux distributions as well. The nginx web server is configured through file /usr/local/etc/nginx/nginx.conf (FreeBSD) or /etc/nginx/nginx.conf (Linux). The FastCGI spawner spawn-fcgi just uses an rc.d(8) command script for set-up.

FastCGI and Fortran

In Fortran, interface bindings to a few functions of the FastCGI API in shared library libfcgi.so are required to be able to interact with the web server:

result = fcgi_accept()
Blocks until the next HTTP request, then creates a new execution environment. The request is passed through environment variables. Returns < 0 on error.
result = fcgi_getchar()
Returns a single character from content sent by POST method.
result = fcgi_puts(str)
Appends the given character string to the HTTP response. The character argument str must be null-terminated.

The FLIBS library includes Fortran 2003 interfaces to the FastCGI functions above, along additional interfaces, utility routines, and data structures for simplified request handling. For the following example, we just declare iso_c_binding interfaces to both FastCGI functions in a single Fortran module fcgi:

! fcgi.f90
module fcgi
    use, intrinsic :: iso_c_binding, only: c_char, c_int
    implicit none
    private

    public :: fcgi_accept
    public :: fcgi_getchar
    public :: fcgi_puts

    interface
        ! int FCGI_Accept(void)
        function fcgi_accept() bind(c, name='FCGI_Accept')
            import :: c_int
            integer(kind=c_int) :: fcgi_accept
        end function fcgi_accept

        ! int FCGI_getchar(void)
        function fcgi_getchar() bind(c, name='FCGI_getchar')
            import :: c_char
            character(kind=c_char) :: fcgi_getchar
        end function fcgi_getchar

        ! int FCGI_puts(const char *str)
        function fcgi_puts(str) bind(c, name='FCGI_puts')
            import :: c_char, c_int
            character(kind=c_char), intent(in) :: str(*)
            integer(kind=c_int)                :: fcgi_puts
        end function fcgi_puts
    end interface
end module fcgi

Compile and archive the Fortran module to static library libfortran-fcgi.a:

$ gfortran10 -c src/fcgi.f90
$ ar crs libfortran-fcgi.a fcgi.o 

Any FastCGI application written in Fortran then has to be linked against libfortran-fcgi.a -lfcgi to access this subset of the FastCGI API. However, it is not really necessary to pack the Fortran module fcgi into a static library. We can instead link our web application just with object file fcgi.o.

Web Application

The following FastCGI application in Fortran returns an HTML document that contains the values of the CGI environment variables REQUEST_URI, SERVER_SOFTWARE, and GATEWAY_INTERFACE (passed by the web server) as an HTTP response. The HTML5 template is hard-coded into the executable, but could possibly be loaded from file instead.

Fortran FastCGI application
Fig. 1: The output of the FastCGI application

Once the web application is initialised (not necessary in this example), we call the blocking FastCGI function fcgi_accept() to signal that we are ready to accept a new request from the HTTP server and to create a CGI-compatible execution environment for the request. If the function returns −1, the web applications will terminate, which causes the FastCGI spawner to shutdown the associated process. In this case, the web server returns HTTP status code 503 (Service Unavailable). The nginx log file /var/log/nginx/error.log may give further details.

The FastCGI parameters of the request are passed by the web server as environment variables to the FastCGI application context. In Fortran, we just have to call the intrinsic routine environment_variable() to retrieve parameters such as the request URI, the IP address of the client, or the root path.

Each HTTP response starts with the specific type of the served content, followed by two carriage return/line-feed characters (CR_LF), to mark the beginning of the payload. Instead of a HyperText document (text/html), we could return any other MIME type, like text/plain for plain text, image/gif for GIF, or application/octet-stream for a byte stream download. Based on the MIME type of the response, the client is able to decide how to handle the returned data stream.

All character arguments given to the C function fcgi_puts() must be properly null-terminated, using c_null_char from module iso_c_binding, or simply with char(0).

! webapp.f90
program webapp
    use, intrinsic :: iso_c_binding, only: c_null_char
    use :: fcgi
    implicit none
    character(len=*), parameter :: CR_LF         = achar(13) // achar(10)
    character(len=*), parameter :: CONTENT_TYPE = 'text/html'
    character(len=*), parameter :: HEADER       = '<!doctype html>' // &
                                                  '<html lang="en">' // &
                                                  '<head><meta charset="utf-8">' // &
                                                  '<title>Fortran + FastCGI</title></head>' // &
                                                  '<body>'
    character(len=*), parameter :: FOOTER       = '</body></html>'

    character(len=72) :: env
    integer           :: rc

    do while (fcgi_accept() >= 0)
        ! HTTP header.
        rc = fcgi_puts('Content-Type: ' // CONTENT_TYPE // CR_LF // CR_LF // c_null_char)

        ! HTML payload.
        rc = fcgi_puts(HEADER // c_null_char)
        rc = fcgi_puts('<h1>Hello, from Fortran!</h1>' // &
                       '<table border="1">' // &
                       '<tr><th>Key</th><th>Value</th></tr>' // c_null_char)

        call get_environment_variable('REQUEST_URI', env)
        rc = fcgi_puts('<tr><td>REQUEST_URI</td><td>' // trim(env) // '</td></tr>' // c_null_char)

        call get_environment_variable('SERVER_SOFTWARE', env)
        rc = fcgi_puts('<tr><td>SERVER_SOFTWARE</td><td>' // trim(env) // '</td></tr>' // c_null_char)

        call get_environment_variable('GATEWAY_INTERFACE', env)
        rc = fcgi_puts('<tr><td>GATEWAY_INTERFACE</td><td>' // trim(env) // '</td></tr>' // c_null_char)

        rc = fcgi_puts('</table>' // c_null_char)
        rc = fcgi_puts(FOOTER // c_null_char)
    end do
end program webapp

The web application has to be statically linked against our Fortran interface bindings in libfortran-fcgi.a, and dynamically against the FastCGI SDK with -lfcgi:

$ gfortran10 -I/usr/local/include/ -L/usr/local/lib/ -o webapp webapp.f90 libfortran-fcgi.a -lfcgi

Copy the FastCGI program webapp to, for example, /var/www/cgi-bin/, and configure the FastCGI spawner accordingly.

Server Configuration

In the next step, we have to configure the nginx web server to redirect all incoming requests to the FastCGI process. In the nginx configuration file, we add a FastCGI passthrough to 127.0.0.1 on port 9000 for an arbitrary location, is this particular case, the root path /:

user www;
worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include      mime.types;
    default_type application/octet-stream;

    server {
        listen      80;
        server_name localhost;

        location / {
            fastcgi_pass  127.0.0.1:9000;
            fastcgi_index index.html;
            include       fastcgi_params;
        }

        location /cgi-bin {
            deny all;
        }
    }
}

By default, the FastCGI spawner spawn-fcgi binds to port 9000. We have to add web server and spawner to /etc/rc.conf to be loaded as system services on FreeBSD:

nginx_enable="YES"
spawn_fcgi_enable="YES"
spawn_fcgi_app="/var/www/cgi-bin/webapp"

The path to the binary webapp is set through variable spawn_fcgi_app. You may want to change the value to the actual location. On FreeBSD, start both services with:

# service spawn-fcgi start
# service nginx start

Open a web browser and access http://127.0.0.1/ to see the response of the FastCGI application (fig. 1). The binary /var/www/cgi-bin/webapp will be locked by spawn-fcgi. Therefore, we first have to stop the service to replace the web application:

# service spawn-fcgi stop
# cp webapp /var/www/cgi-bin/
# service spawn-fcgi start

References