Fortran + FastCGI + nginx : Possibly the fastest combination for certain web applications?

By Ricolindo dot Carino at gmail dot com

Below is an example of a simple FastCGI Fortran application that runs with the nginx webserver. Instructions for GNU/Linux and MS Windows XP are provided.

Section I. GNU/Linux

The following instructions were tested to work in the following environment:

Part A. Download, install and configure nginx

  1. Download nginx (v.1.2.5) sources from http://nginx.org/en/download.html
  2. Unzip to /home/heeds
  3. Change working directory to nginx sources
      $ cd /home/heeds/ngingx-1.2.5
    
  4. Install nginx
      $ ./configure --without-http_rewrite_module --without-http_gzip_module
      $ make
      $ sudo make install
    
  5. Run nginx
      $ sudo /usr/local/nginx/sbin/nginx
    
    If the error "nginx: [emerg] bind() to 0.0.0.0.80 failed ...", it means another process is using port 80 - most probably Apache. Stop Apache, then start nginx as follows:
      $ sudo service apache2 stop
      $ sudo /usr/local/nginx/sbin/nginx
    
  6. Open browser, go to http://localhost. Browser should display 'Welcome to nginx!'
  7. Edit the nginx configuration file
      $ sudo gedit /usr/local/nginx/conf/nginx.conf
    
    Change section "location"
    From:
            location / {
                root   html;
                index  index.html index.htm;
            }
    
    To:
            location / {
                root   /home/heeds/HEEDS/web;
                index  index.html;
            }
    
    Save and exit editor.
  8. Create /home/heeds/HEEDS/web/index.html as follows:
    	<html>
    	<body>
    	<h1> Welcome to HEEDS via nginx!</h1>
    	</body>
    	</html>
    	</preb>
    
  9. Reload nginx
      $ sudo /usr/local/nginx/sbin/nginx -s reload
    
  10. Reload browser. Browser should display 'Welcome to HEEDS via nginx!'

Part B. Download and install FastCGI

  1. Download the FastCGI development kit from http://www.fastcgi.com/dist/
  2. Unzip to /home/heeds/
  3. Change working directory to FastCGI sources
      $ cd /home/heeds/fcgi-2.4.1
    
  4. Install FastCGI
      $ ./configure
      $ make
      $ make install
    
  5. Run FastCGI example/echo
      $ cd /home/heeds/fcgi-2.4.1/example
      $ spawn-fcgi -a 127.0.0.1 -p 9000 ./echo
    
  6. Re-edit the nginx configuration file
      $ sudo gedit /usr/local/nginx/conf/nginx.conf
    
    Change section "location"
    From:
            location / {
                root   /home/heeds/HEEDS/web;
                index  index.html;
            }
    
    To:
            location / {
                root   /home/heeds/HEEDS/web;
                fastcgi_pass   127.0.0.1:9000;
                fastcgi_index  index.html;
                include        fastcgi_params;
            }
    
    Save and exit editor.
  7. Reload nginx
      $ sudo /usr/local/nginx/sbin/nginx -s reload
    
  8. Reload browser. Browser should display 'FastCGI echo', etc. Successive reload will increment the 'Request number'
  9. Kill the 'FastCGI echo' process
      $ kill -9 9066 (the actual Process ID appears after the Request number)
    
  10. Reload browser. Browser should display 'An error ocurred.'

Part C. A simple FastCGI Fortran application

  1. Save Fortran code in Section III below as /home/heeds/fcgi-2.4.1/example/fortran_fcgi.F90
  2. Compile as:
      $ gfortran -o fortran_fcgi fortran_fcgi.F90 -lfcgi -Wl,--rpath -Wl,/usr/local/lib
    
  3. Execute as:
      $ spawn-fcgi -a 127.0.0.1 -p 9000 ./fortran_fcgi
    
  4. Reload browser. Browser should display 'fortran FastCGI', etc. Enter some text in the Username & Password fields, then click Login. Also click on the available links.
  5. Clicking 'Stop' will terminate the fortran_fcgi and the spawn_fcgi processes.

Section II. Microsoft Windows

The following instructions were tested to work in the following environment: Download the following installers and files (links verified to be available on 2012-12-20):
  1. MinGW installer - http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/mingw-get-inst-20120426/mingw-get-inst-20120426.exe/download
  2. FastCGI DevPak - http://prdownloads.sourceforge.net/devpaks/libfcgi-2.4.0-1cm.DevPak?download
  3. spawn-fcgi-win32.c - https://github.com/mkottman/lighttpd-1.5-mingw/blob/master/spawn-fcgi-win32.c
  4. Nginx installer - http://nginx.org/download/nginx-1.3.9.zip

Part A. Install MinGW, for compiling spawn-fcgi-win32.c (see Part B) and FastCGI applications.

  1. Download the MinGW installer - http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/mingw-get-inst-20120426/mingw-get-inst-20120426.exe/download
  2. Run mingw-get-inst-20120426.exe
    1. When prompted for "Repository Catalogues" select "Download latest repository catalogues".
    2. Accept the "License Agreement".
    3. Accept the default "Destination Location" (C:\MinGW)
    4. Accept the default "Start Folder" (MinGW).
    5. For "Components" select "C compiler", "Fortran compiler", and the "MSYS Basic System"
    Be patient. Downloading and installing the files will take several minutes, depending on the speed of your Internet connection and hard disk.
  3. Add C:\MinGW\bin and C:\MinGW\msys\1.0\bin to the PATH environment variable.

Part B. Download and compile spawn-fcgi-win32.c. The spawn-fcgi executable will be used to spawn FastCGI processes.

  1. Download spawn-fcgi-win32.c from https://github.com/mkottman/lighttpd-1.5-mingw/blob/master/spawn-fcgi-win32.c . Get the C-program, not the HTML page. Use cut-and-paste technique if necessary: select the C code from the web page, then paste to text file C:\temp\spawn-fcgi-win32.c
  2. Open a command-line window. Change directory to C:\temp. Compile spawn-fcgi-win32.c as:
      C:\temp>gcc -o spawn-fcgi spawn-fcgi-win32.c -lws2_32
    
  3. Test spawn-fcgi as follows:
      C:\temp>spawn-fcgi.exe
    
    Usage info should be displayed.

Part C. Download and install the pre-built FastCGI library for MinGW. The library is required by FastCGI applications.

  1. Download the FastCGI library DevPak - http://prdownloads.sourceforge.net/devpaks/libfcgi-2.4.0-1cm.DevPak?download
  2. Rename libfcgi-2.4.0-1cm.DevPak to libfcgi-2.4.0-1cm.tar.bz2. Extract the files in archive to a temporary directory. Copy all the extracted files to C:\MinGW (allow overwriting of the bin, doc, and include directories).
  3. Copy fcgi_config_x86.h in C:\MinGW\include\ to fcgi_config.h in the same directory.

Part D. Download and install nginx ("Engine X"), a high-performance web server.

  1. Download the nginx installer - http://nginx.org/download/nginx-1.3.9.zip. Unzip to C:\
  2. Open a command-line window. Change directory to C:\nginx-1.3.9. Run nginx by typing the command
      C:\nginx-1.3.9>start nginx.exe
    
  3. Open browser, go to http://localhost. Browser should display 'Welcome to nginx!'
  4. On the command-line window, stop nginx by typing the command
      C:\nginx-1.3.9>nginx.exe -s stop
    
  5. Edit the nginx configuration file to communicate with FastCGI processes. On the command-line window:
      C:\nginx-1.3.9>notepad.exe conf\nginx.conf
    
    Change section "location"
    From:
            location / {
                root   html;
                index  index.html index.htm;
            }
    
    To:
            location / {
                root   html;
                fastcgi_pass   127.0.0.1:9000;
                fastcgi_index  index.html;
                include        fastcgi_params;
            }
    
    Save and exit editor.
  6. Restart nginx with the new configuration:
      C:\nginx-1.3.9>start nginx.exe
    

Part E. A simple FastCGI Fortran application

  1. Save Fortran code in Section III below to C:\temp\fortran_fcgi.F90
  2. On the command-line window at C:\temp, compile fortran_fcgi.F90 as
      C:\temp>gfortran -o fortran_fcgi fortran_fcgi.F90 -lfcgi
    
  3. Execute fortran_fcgi as:
      C:\temp>spawn-fcgi.exe -f fortran_fcgi.exe -a 127.0.0.1 -p 9000
    
  4. Restart the NGINX server, so that it can connect to the spawned process:
      C:\temp>start nginx -s stop
      C:\temp>start nginx
    
  5. Check running process on the command-line window at C:\nginx-1.3.9
      C:\nginx-1.3.9>tasklist
    
    There should be two nginx.exe processes, the spawn-fcgi process, and the fortran_fcgi process.
  6. Reload http://localhost in browser. Browser should display 'fortran FastCGI', etc. Enter some text in the Username & Password fields, then click Login. Also click on the available links.
  7. Clicking 'Stop' will terminate the fortran_fcgi and the spawn_fcgi processes.
  8. On the command-line window at C:\nginx-1.3.9, stop nginx by typing the command
      C:\nginx-1.3.9>nginx.exe -s stop
    

Section III. fortran_fcgi.F90 - A simple FastCGI application in Fortran

!
! Demonstration Fortran FastCGI program, to run with the nginx webserver
! By Ricolindo.Carino@gmail.com and arjen.markus895@gmail.com
!
! Requires:
!    - the FLIBS modules cgi_protocol and fcgi_protocol
!    - the FastCGI library
! See 'readme' for setup instructions of the compiler, nignx, and FastCGI library
!
! See 'makefile' for compile and execute commands. In summary,
!   To compile test_fcgi.f90      : make
!   To execute as FastCGI process : spawn-fcgi -a 127.0.0.1 -p 9000 ./test_fcgi
!      The "-a 127.0.0.1" and "-p 9000" options to spawn-fcgi must match the
!          "fastcgi_pass   127.0.0.1:9000;" in nginx.conf
!
! Notes:
!    1. Example 2 is from FLIBS test_cgi.f90
!    2. Customize routine respond() for your own application
!

program test_fcgi

    use fcgi_protocol

    implicit none

    type(DICT_STRUCT), pointer  :: dict => null() ! Initialisation is important!
    logical                     :: stopped = .false. ! set to true in respond() to terminate program
    integer                     :: unitNo ! unit number  for a scratch file

    ! open scratch file
    open(newunit=unitNo, status='scratch')
    ! comment previous line AND uncomment next line for debugging;
    !open(newunit=unitNo, file='fcgiout', status='unknown') ! file 'fcgiout' will show %REMARKS%

    ! wait for environment variables from webserver
    do while (fcgip_accept_environment_variables() >= 0)

        ! build dictionary from GET or POST data, environment variables
        call fcgip_make_dictionary( dict, unitNo )

        ! give dictionary to the user supplied routine
        ! routine writes the response to unitNo
        ! routine sets stopped to true to terminate program
        call respond(dict, unitNo, stopped)

        ! copy file unitNo to the webserver
        call fcgip_put_file( unitNo, 'text/html' )

        ! terminate?
        if (stopped) exit

    end do !  while (fcgip_accept_environment_variables() >= 0)

    ! before termination, it is good practice to close files that are open
    close(unitNo)

    ! webserver will return an error since this process will now terminate
    unitNo = fcgip_accept_environment_variables()


contains


    subroutine respond ( dict, unitNo, stopped )

        type(DICT_STRUCT), pointer        :: dict
        integer, intent(in)               :: unitNo
        logical, intent(out)              :: stopped

        ! the following are defined in fcgi_protocol
        !character(len=3), parameter :: AFORMAT = '(a)'
        !character(len=2), parameter :: CRLF = achar(13)//achar(10)
        !character(len=1), parameter :: NUL = achar(0)

        ! the script name
        character(len=80)  :: scriptName

        ! variables for Example 1
        character(len=80)  :: actionButton, username, password

        ! variables for Example 2 (from test_cgi.f90 of FLIBS)
        integer                           :: steps
        real                              :: xmin
        real                              :: xmax
        character(len=20)                 :: fnName
        character(len=20)                 :: output

        real, dimension(:), allocatable   :: x
        real, dimension(:), allocatable   :: y

        integer                           :: i
        logical                           :: okInputs

        ! start of response
        ! lines starting with %REMARK% are for debugging & will not be copied to webserver
        write(unitNo, AFORMAT) &
            '%REMARK% respond() started ...', &
            '<html>', &
            '<head><title>Fortran FastCGI</title></head>', &
            '<body>', &
            '<h1>Fortran FastCGI</h1>'

        ! retrieve script name (key=DOCUMENT_URI) from dictionary
        call cgi_get( dict, "DOCUMENT_URI", scriptName )

        if ( trim(scriptName) /= '/' ) & ! a script was requested
            write(unitNo,AFORMAT) 'Script is : '//trim(scriptName)

        select case (trim(scriptName))

            case ('/login') ! See form in Example 1 below
                ! keys are: action, username, password

                call cgi_get( dict, "action", actionButton)
                write(unitNo,AFORMAT) '<br>Action is : '//trim(actionButton)

                call cgi_get( dict, "username", username)
                write(unitNo,AFORMAT) '<br>Username is : '//trim(username)

                call cgi_get( dict, "password", password)
                write(unitNo,AFORMAT) '<br>Password is : '//trim(password)


            case ('/calculate') ! See form in Example 2 below
                ! keys are: function, minimum, maximum, steps, output

                fnName  = '?'
                xmin     = 0.0
                xmax     = 1.0
                steps    = 10
                call cgi_get( dict, "function", fnName  )
                call cgi_get( dict, "minimum",  xmin     )
                call cgi_get( dict, "maximum",  xmax     )
                call cgi_get( dict, "steps",    steps    )
                call cgi_get( dict, "output",   output   )

                write(unitNo, AFORMAT) '%REMARK% function='//trim(fnName )
                write(unitNo, '(a,f8.3)') '%REMARK% minimum=', xmin
                write(unitNo, '(a,f8.3)') '%REMARK% maximum=', xmax
                write(unitNo, '(a,i4)') '%REMARK% steps=', steps
                write(unitNo, AFORMAT) '%REMARK% output='//trim(output)

                okInputs = .true.
                if ( trim(fnName ) == '?' ) then
                    write(unitNo,AFORMAT) '<br>No function selected'
                    okInputs = .false.
                endif
                if ( abs(xmin) > 100.0 .or. abs(xmax) > 100.0 ) then
                    write(unitNo,AFORMAT) '<br>Minimum and maximum should be in the range -100 to 100'
                    okInputs = .false.
                endif
                if ( trim(fnName ) == 'J0' ) then
                    write(unitNo,AFORMAT) '<br>Sorry, the Bessel function is not yet implemented'
                    okInputs = .false.
                endif

                if (okInputs) then
                    !
                    ! Actual processing
                    !
                    allocate( x(0:steps), y(0:steps) )

                    x = (/ (xmin + i*(xmax-xmin)/steps, i=0,steps) /)
                    if ( trim(fnName ) == 'sin' ) then
                        y = sin(x)
                    endif
                    if ( trim(fnName ) == 'cos' ) then
                        y = cos(x)
                    endif

                    !
                    ! Write the HTML output or the CSV file
                    !
                    if ( trim(output) == 'html' ) then
                        write( unitNo,AFORMAT ) &
                            '<table>', &
                            '<tr><td>X</td><td>'//trim(fnName)//'(X)</td></tr>'
                        do i = 0,steps
                            write( unitNo, '(a,f12.6,a,f12.6,a)' ) &
                                '<tr><td>', x(i), '</td><td>', y(i), '</td></tr>'
                        enddo
                        write( unitNo,AFORMAT ) &
                            '</table>'
                    else
                        write( unitNo,AFORMAT ) &
                            '<pre>', '      X     ,      '//trim(fnName)//'(X)'
                        do i = 0,steps
                            write( unitNo, '(f12.6,a,f12.6)' ) x(i), ',', y(i)
                        enddo
                        write( unitNo,AFORMAT ) &
                            '</pre>'
                    endif

                end if


            case ('/shutdown') ! to terminate program
                write(unitNo,AFORMAT) '<br>Program has terminated.<br><a href="/">Verify</a>'
                stopped = .true.

        end select

        ! generate page for next action
        if (.not. stopped) then

            write(unitNo,AFORMAT) &
                "<hr>", &
                "<b>Example 1: POST method</b>", &
                "<table>", &
                "<form action='login' method='post'>", &
                "<tr><td>Username:</td><td>Password:</td><td>&nbsp;Action:</td></tr>", &
                "<tr><td><input type='text' name='username' value=''></td>", &
                "    <td><input type='password' name='password' value=''></td>", &
                "    <td>&nbsp;<input type='submit' name='action' value='Login'>", &
                "        or <input type='submit' name='action' value='Register'></td></tr>", &
                "</form>", &
                "</table>"

            write(unitNo,AFORMAT) &
                "<hr>", &
                "<b>Example 2: GET method</b>", &
                "<form action='calculate' method='get'>", &
                "<p>", &
                "Function: f(x) =", &
                "<select name='function'>", &
                "    <option value='sin' selected>sin(x)</option>", &
                "    <option value='cos'>cos(x)</option>", &
                "    <option value='J0'>J0(x)</option>", &
                "</select>", &
                "<br>", &
                "Domain:", &
                "<table>", &
                "<tr>", &
                "<td>Minimum = </td><td><input type='text' name='minimum' value='0.0'></td>", &
                "</tr>", &
                "<tr>", &
                "<td>Maximum = </td><td><input type='text' name='maximum' value='1.0'></td>", &
                "</tr>", &
                "<tr>", &
                "<td>Steps = </td><td><input type='text' name='steps' value='10'></td>", &
                "</tr>", &
                "</table>", &
                "<p>", &
                "Type of output:", &
                "<input type='radio' name='output' value='html', checked>HTML</input>", &
                "<input type='radio' name='output' value='csv'>CSV</input>", &
                "<p>", &
                "<input type='submit' value='Calculate'>", &
                "</form>"

            write(unitNo,AFORMAT) &
                "<hr>", &
                "<b>Example 3: Hyperlink</b><br>", &
                "&nbsp;<a href='shutdown'><b>Stop</b></a> the Fortran FastCGI program."

        end if

        ! end of response
        write(unitNo,AFORMAT) '</body>', '</html>', &
            '%REMARK% respond() completed ...'

        return

    end subroutine respond

end program test_fcgi