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.

Requests are passed to the FastCGI application context using environment variables, but interface bindings are required to handle them in Fortran 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. In the following example, iso_c_binding interfaces to the FastCGI functions are declared in a single Fortran module fcgi, with additional wrapper procedures for type conversion and null-termination:

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

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

    private :: fcgi_getchar_
    private :: fcgi_puts_

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

        ! int FCGI_getchar(void)
        function fcgi_getchar_() bind(c, name='FCGI_getchar')
            import :: c_int
            implicit none
            integer(kind=c_int) :: 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
            implicit none
            character(kind=c_char), intent(in) :: str
            integer(kind=c_int)                :: fcgi_puts_
        end function fcgi_puts_
    end interface
contains
    character function fcgi_getchar() result(a)
        !! Wrapper that returns the result of the interface as character.
        a = achar(fcgi_getchar_())
    end function fcgi_getchar

    subroutine fcgi_puts(str)
        !! Null-terminates argument `str` before passing it to the interface.
        character(len=*), intent(in) :: str
        integer :: stat
        stat = fcgi_puts_(str // c_null_char)
    end subroutine fcgi_puts
end module fcgi

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

$ gfortran13 -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 passed by the web server are accessed through the Fortran 2003 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.

! webapp.f90
program webapp
    use, intrinsic :: iso_c_binding
    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

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

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

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

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

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

        call fcgi_puts('</table>')
        call fcgi_puts(FOOTER)
    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:

$ gfortran13 -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 for them 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 in variable spawn_fcgi_app. You may have to change the value to the actual location. On FreeBSD, start both services by running:

# 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

Fortran Libraries

References