nginx

nginx is an open-soure web server, reverse proxy, load balancer, and web application platform that can be extended through 3rd party modules. OpenResty is web application platform based on nginx and LuaJIT that includes several modules by default.

The nginx-link-function module provides an API to dynamically link nginx against a web application within the server context and to forward requests to functions in such an application. The Fortran 2003 interface library fortran-nginx includes bindings to the C API.

The Fortran application has to be compiled to a shared libary. Once a route is defined in the nginx configuration, the desired function is called on each request, and the response returned to the client.

Installation

On FreeBSD, we can build nginx from the ports collection. Select the build option LINK to enable the nginx-link-function module:

# cd /usr/local/ports/www/nginx/
# make config
# make
# make install

Or, if you prefer packages over ports, and the chosen package repository provides an nginx package that has been built with LINK, run instead:

# pkg install www/nginx
# pkg info www/nginx | grep LINK

On all other platforms, we either compile nginx from source and include the nginx-link-function module at compile time, or build the module independendly as a shared library to be loaded through the nginx configuration file at run time (load_module). For a static library, we first have to download and unpack the source code of the module:

# cd /tmp/
# fetch "https://github.com/Taymindis/nginx-link-function/archive/master.zip"
# unzip master.zip

Then, download the source code of the latest version of nginx (≥ v1.21.0), add the nginx-link-function module as an argument to the configure script, compile, and finally install nginx. For example:

# fetch "https://nginx.org/download/nginx-<VERSION>.tar.gz"
# tar xzvf nginx-<VERSION>.tar.gz
# cd nginx-<VERSION>/
# ./configure --add-module=/tmp/nginx-link-function-master/
# make -j 2
# make install
# install -m 644 /tmp/nginx-link-function-master/src/ngx_link_func_module.h /usr/local/include/

In the install step, you may have to change the path /usr/local/include/ to your system’s include directory, such as /usr/include/ on Linux. On FreeBSD, the nginx daemon is started with:

# service nginx start

fortran-nginx

In order to interact with the nginx-link-function module from Fortran, we need to link the fortran-nginx library that provides the requires ISO C binding interfaces. Clone the repository and execute the Makefile:

$ git clone https://github.com/interkosmos/fortran-nginx
$ cd fortran-nginx/
$ make

The Makefile will create a static library libfortran-nginx.a, we link our web application against statically.

Example

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

The example web application library just returns a static HTML document. The implementation of the routines ngx_link_func_init_cycle() and ngx_link_func_exit_cycle() is mandatory. All routines callable from outside must have the bind(c) attribute.

! webapp.f90
module webapp
    use, intrinsic :: iso_c_binding
    use :: ngx_link_func
    implicit none

    character(len=*), parameter :: HEADER = '<!DOCTYPE html>' // &
                                            '<html lang="en">' // &
                                            '<head>' // &
                                            '<meta charset="utf-8">' // &
                                            '<title>Fortran Web App</title>' // &
                                            '</head>' // &
                                            '<body bgcolor="#f5f5dc">'
    character(len=*), parameter :: FOOTER = '</body></html>'

    logical, save :: is_service_on = .false.

    public :: ngx_hello
    public :: ngx_link_func_exit_cycle
    public :: ngx_link_func_init_cycle
contains
     subroutine ngx_hello(ctx) bind(c)
        !! This routine fill be called by nginx, passing in the current request context.
        use, intrinsic :: iso_fortran_env, only: compiler_options, compiler_version

        type(ngx_link_func_ctx_t), intent(in) :: ctx

        character(len=:), allocatable :: response
        character(len=19)             :: date
        integer                       :: dt(8)

        call date_and_time(values=dt)
        write (date, '(i4, 5(a, i2.2))') dt(1), '/', dt(2), '/', dt(3), ' ', &
                                         dt(5), ':', dt(6), ':', dt(7)

        response = HEADER // &
                   '<h1>Hello, from Fortran!</h1>' // &
                   '<p>' // date // '<br>' // &
                   'Compiler: ' // compiler_version() // '<br>' // &
                   'Options: ' // compiler_options() // '</p>' // &
                   FOOTER

        call ngx_link_func_log_info(ctx, 'Sending response ...' // c_null_char)
        call ngx_link_func_write_resp(ctx, &
                                      200_c_intptr_t, &
                                      '200 OK' // c_null_char, &
                                      NGX_LINK_FUNC_CONTENT_TYPE_HTML, &
                                      response, &
                                      len(response, kind=c_size_t))
    end subroutine ngx_hello

    ! void ngx_link_func_init_cycle(ngx_link_func_cycle_t* cyc)
    subroutine ngx_link_func_init_cycle(cyc) bind(c)
        type(ngx_link_func_cycle_t), intent(in) :: cyc

        call ngx_link_func_cyc_log_info(cyc, 'Starting the web app ...' // c_null_char)
        is_service_on = .true.
    end subroutine ngx_link_func_init_cycle

    ! void ngx_link_func_exit_cycle(ngx_link_func_cycle_t* cyc)
    subroutine ngx_link_func_exit_cycle(cyc) bind(c)
        type(ngx_link_func_cycle_t), intent(in) :: cyc

        call ngx_link_func_cyc_log_info(cyc, 'Shutting down the web app ...' // c_null_char)
        is_service_on = .false.
    end subroutine ngx_link_func_exit_cycle
end module webapp

Compile the shared library webapp.so with:

$ gfortran13 -shared -fPIC -o webapp.so webapp.f90 libfortran-nginx.a

The library has to be copied to a directory nginx has access to, for instance, /usr/local/etc/nginx/, and added to the nginx configuration file.

Server Configuration

In the nginx configuration file /usr/local/etc/nginx/nginx.conf (FreeBSD), we set ngx_link_func_lib to the path of our web application, and add a route that invokes ngx_link_func_call to call the routine ngx_hello inside the shared library webapp.so:

# Load shared library if module is not linked statically:
load_module "/usr/local/libexec/nginx/ngx_http_link_func_module.so";

http {
    server {
        listen            80;
        server_name       localhost;
        ngx_link_func_lib "/usr/local/etc/nginx/webapp.so";

        location = / {
            ngx_link_func_call "ngx_hello";
        }
    }
}

We are now ready to start the nginx daemon. On FreeBSD, run:

# service nginx restart

Open the URL http://127.0.0.1/ inside your web browser to access the web application (fig. 1).

Fortran Libraries

References