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.
The following instructions were tested to work in the following environment:
- Lenovo ThinkPad X200
- GNU/Linux, Ubuntu 12.04
- gfortran and spawn-fcgi installed using Ubuntu Software Center
- User is 'heeds', with 'sudo' privilege.
Part A. Download, install and configure nginx
- Download nginx (v.1.2.5) sources from http://nginx.org/en/download.html
- Unzip to /home/heeds
- Change working directory to nginx sources
$ cd /home/heeds/ngingx-1.2.5
- Install nginx
$ ./configure --without-http_rewrite_module --without-http_gzip_module
$ make
$ sudo make install
- 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
- Open browser, go to http://localhost. Browser should display 'Welcome to nginx!'
- 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.
- Create /home/heeds/HEEDS/web/index.html as follows:
<html>
<body>
<h1> Welcome to HEEDS via nginx!</h1>
</body>
</html>
</preb>
- Reload nginx
$ sudo /usr/local/nginx/sbin/nginx -s reload
- Reload browser. Browser should display 'Welcome to HEEDS via nginx!'
Part B. Download and install FastCGI
- Download the FastCGI development kit from http://www.fastcgi.com/dist/
- Unzip to /home/heeds/
- Change working directory to FastCGI sources
$ cd /home/heeds/fcgi-2.4.1
- Install FastCGI
$ ./configure
$ make
$ make install
- Run FastCGI example/echo
$ cd /home/heeds/fcgi-2.4.1/example
$ spawn-fcgi -a 127.0.0.1 -p 9000 ./echo
- 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.
- Reload nginx
$ sudo /usr/local/nginx/sbin/nginx -s reload
- Reload browser. Browser should display 'FastCGI echo', etc. Successive reload
will increment the 'Request number'
- Kill the 'FastCGI echo' process
$ kill -9 9066 (the actual Process ID appears after the Request number)
- Reload browser. Browser should display 'An error ocurred.'
Part C. A simple FastCGI Fortran application
- Save Fortran code in Section III below as /home/heeds/fcgi-2.4.1/example/fortran_fcgi.F90
- Compile as:
$ gfortran -o fortran_fcgi fortran_fcgi.F90 -lfcgi -Wl,--rpath -Wl,/usr/local/lib
- Execute as:
$ spawn-fcgi -a 127.0.0.1 -p 9000 ./fortran_fcgi
- 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.
- Clicking 'Stop' will terminate the fortran_fcgi and the spawn_fcgi processes.
The following instructions were tested to work in the following environment:
- Lenovo ThinkPad X200
- Windows XP Professional
Download the following installers and files (links verified to be available on 2012-12-20):
- MinGW installer -
http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/mingw-get-inst-20120426/mingw-get-inst-20120426.exe/download
- FastCGI DevPak -
http://prdownloads.sourceforge.net/devpaks/libfcgi-2.4.0-1cm.DevPak?download
- spawn-fcgi-win32.c -
https://github.com/mkottman/lighttpd-1.5-mingw/blob/master/spawn-fcgi-win32.c
- 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.
- Download the MinGW installer -
http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/mingw-get-inst-20120426/mingw-get-inst-20120426.exe/download
- Run mingw-get-inst-20120426.exe
- When prompted for "Repository Catalogues" select "Download latest repository catalogues".
- Accept the "License Agreement".
- Accept the default "Destination Location" (C:\MinGW)
- Accept the default "Start Folder" (MinGW).
- 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.
- 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.
- 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
- 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
- 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.
- Download the FastCGI library DevPak -
http://prdownloads.sourceforge.net/devpaks/libfcgi-2.4.0-1cm.DevPak?download
- 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).
- 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.
- Download the nginx installer -
http://nginx.org/download/nginx-1.3.9.zip.
Unzip to C:\
- 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
- Open browser, go to http://localhost. Browser should display 'Welcome to nginx!'
- On the command-line window, stop nginx by typing the command
C:\nginx-1.3.9>nginx.exe -s stop
- 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.
- Restart nginx with the new configuration:
C:\nginx-1.3.9>start nginx.exe
Part E. A simple FastCGI Fortran application
- Save Fortran code in Section III below to C:\temp\fortran_fcgi.F90
- On the command-line window at C:\temp, compile fortran_fcgi.F90 as
C:\temp>gfortran -o fortran_fcgi fortran_fcgi.F90 -lfcgi
- Execute fortran_fcgi as:
C:\temp>spawn-fcgi.exe -f fortran_fcgi.exe -a 127.0.0.1 -p 9000
- Restart the NGINX server, so that it can connect to the spawned process:
C:\temp>start nginx -s stop
C:\temp>start nginx
- 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.
- 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.
- Clicking 'Stop' will terminate the fortran_fcgi and the spawn_fcgi processes.
- 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> Action:</td></tr>", &
"<tr><td><input type='text' name='username' value=''></td>", &
" <td><input type='password' name='password' value=''></td>", &
" <td> <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>", &
" <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