FastCGI

The Common Gateway Interface (CGI) allows applications to respond to HTTP client requests through a web server, as specified in RFC 3875. The protocol was introduced in the 1990s to connect databases and applications to the World Wide Web. The web server forwards incoming requests to the designated CGI script or application, and returns the given response to the client. The inter-communication between web server and CGI program relies on standard input/output. Consequently, CGI applications can be developed in nearly every programming language.

CGI has the drawback of starting a new process for each HTTP request, and, therefore, producing quite a lot of overhead related to process management and memory consumption, especially, if the CGI program is written in a scripting language. In the mid-1990s, the FastCGI protocol was released to address these shortcomings by creating persistent processes that handle multiple requests. The FastCGI standard is implemented by most modern web servers, such as nginx, lighttpd, or Apache HTTP Server. The details of a request are given through environment variables to the FastCGI application context.

In order to run FastCGI applications written in Fortran, we need:

On FreeBSD, you can install nginx, FastCGI, and spawn-fcgi simply with:

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

The packages should be available on the various 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 start-up.

FastCGI and Fortran

In contrast to CGI, which is not widely supported anymore, ISO C binding interfaces to at least two functions of the FastCGI API in shared library libfcgi.so are required to interact with the web server:

result = fcgi_accept()
Blocks until the next HTTP request, then creates a new execution environment. Returns < 0 on error.
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_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_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 web 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 on occured errors.

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 get, for example, 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=32) :: env
    integer           :: rc

    do while (fcgi_accept() >= 0)
        ! HTTP content type.
        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