cURL
cURL is a command-line tool and C library for client access to several network protocols, depending on the selected build-time options. Among them:
- HTTP, HTTPS,
- IMAP, POP3, SMTP,
- FTP, FTPS, SFTP, TFTP,
- Gopher, LDAP, MQTT, Telnet.
On FreeBSD, install the package ftp/curl
with:
# pkg install ftp/curl
The package contains the cURL command-line tool as well as the library and the development headers. On Linux, the required development header may have to be installed separately.
We can invoke cURL either by executing the command-line tool from Fortran, or by calling libcurl routines through Fortran interface bindings:
Command-Line
In Fortran, we can simply execute cURL from command-line in order to retrieve arbitrary data from a web server. In this particular example, a RESTful web service is accessed that returns the current time in London. The response is temporarily stored in a text file that will be read from Fortran afterwards:
! curl.f90
program main
implicit none
character(len=*), parameter :: URL = 'http://worldtimeapi.org/api/timezone/Europe/London.txt'
character(len=*), parameter :: PARENT = '/tmp/'
character(len=512) :: buf
character(len=:), allocatable :: cmd, tmp_file
integer :: file_unit, stat
call random_seed()
! Set output of cURL to random file, for example `/tmp/aUqCmPev.tmp`.
call random_file(file_path=tmp_file, parent=PARENT, n=12, ext='.tmp')
cmd = 'curl -s -o "' // tmp_file // '" "' // URL // '"'
! Run cURL from command-line.
call execute_command_line(cmd, exitstat=stat)
if (stat /= 0) then
print '("Error: HTTP request failed: ", i0)', stat
stop
end if
! Open the temporary file.
open (action='readwrite', file=tmp_file, iostat=stat, newunit=file_unit, status='old')
if (stat /= 0) then
print '("Error: reading file ", a, " failed: ", i0)', tmp_file, stat
stop
end if
! Read and output contents of file.
do
read (file_unit, '(a)', iostat=stat) buf
if (stat /= 0) exit
print '(a)', trim(buf)
end do
! Close and delete file.
close (file_unit, status='delete')
contains
subroutine random_file(file_path, parent, n, ext)
!! Returns a random file path in string `file_path`, with parent
!! directory 'parent`, for instance: `/tmp/aUqCmPev.tmp`.
!!
!! The returned file name contains `n` random characters in range
!! [A-Za-z], plus the given extension.
character(len=:), allocatable, intent(inout) :: file_path
character(len=*), intent(in) :: parent
integer, intent(in) :: n
character(len=*), intent(in) :: ext
character(len=n) :: tmp
integer :: i, l
real :: r(n)
file_path = parent
l = len(parent)
if (parent(l:l) /= '/') file_path = file_path // '/'
call random_number(r)
do i = 1, n
if (r(i) < 0.5) then
tmp(i:i) = achar(65 + int(25 * r(i)))
else
tmp(i:i) = achar(97 + int(25 * r(i)))
end if
end do
file_path = file_path // tmp // ext
end subroutine random_file
end program main
The example outputs the timezone information of London:
$ gfortran13 -o curl curl.f90
$ ./curl
abbreviation: BST
client_ip: XXX.XXX.XXX.XXX
datetime: 2020-05-24T00:09:30.924031+01:00
day_of_week: 0
day_of_year: 144
dst: true
dst_from: 2020-03-29T01:00:00+00:00
dst_offset: 3600
dst_until: 2020-10-25T01:00:00+00:00
raw_offset: 0
timezone: Europe/London
unixtime: 1590275370
utc_datetime: 2020-05-23T23:09:30.924031+00:00
utc_offset: +01:00
week_number: 21
libcurl
Instead of running the cURL command-line tool, we can call the
libcurl library routines through the Fortran 2008 interface bindings
fortran-curl. First, we need to build the interface library
libfortran-curl.a
:
$ git clone https://github.com/interkosmos/fortran-curl
$ cd fortran-curl/
$ make
In Fortran, we then just have to import the module curl
. A cURL
instance is created with curl_easy_init()
, and options set through
routine curl_easy_setopt()
. Then, run the request with
curl_easy_perform()
. Finally, the instance must be deleted by
calling curl_easy_cleanup()
. The option
CURLOPT_WRITEFUNCTION
is used to set a callback function. We may
pass arbitrary data as a C pointer through option
CURLOPT_WRITEDATA
to the dummy argument
client_data
.
The example program opens a scratch file to temporarily store the HTTP response. To retrieve the response, we simply rewind the file, and read all lines sequentially.
! http.f90
module callback
use, intrinsic :: iso_c_binding
use :: curl, only: c_f_str_ptr
implicit none
private
public :: write_callback
contains
! static size_t callback(void *ptr, size_t size, size_t nmemb, void *client_data)
function write_callback(ptr, sze, nmemb, client_data) bind(c)
!! Callback function for `CURLOPT_WRITEFUNCTION` that simply outputs
!! the HTTP response chunk-wise.
type(c_ptr), intent(in), value :: ptr !! C pointer to response chunk.
integer(kind=c_size_t), intent(in), value :: sze !! Always 1.
integer(kind=c_size_t), intent(in), value :: nmemb !! Size of response chunk.
type(c_ptr), intent(in), value :: client_data !! C pointer to argument passed by caller.
integer(kind=c_size_t) :: write_callback ! Bytes written.
character(len=:), allocatable :: chunk ! Data to be written.
integer, pointer :: file_unit ! Passed file unit.
write_callback = int(0, kind=c_size_t)
if (.not. c_associated(ptr)) return
if (.not. c_associated(client_data)) return
call c_f_pointer(client_data, file_unit) ! Get unit of scratch file.
call c_f_str_ptr(ptr, chunk, nmemb) ! Convert C char pointer to Fortran character.
write (file_unit, '(a)', advance='no') chunk ! Write response chunk to scratch file.
write_callback = nmemb ! Return number of bytes written.
end function write_callback
end module callback
program main
use, intrinsic :: iso_c_binding
use :: curl
use :: callback
implicit none
character(len=*), parameter :: PROTOCOL = 'http'
character(len=*), parameter :: URL = 'http://worldtimeapi.org/api/timezone/Europe/London.txt'
character(len=4096) :: line
type(c_ptr) :: curl_ptr
integer, target :: file_unit
integer :: rc, stat
! Create new scratch file.
open (action='readwrite', iostat=stat, status='scratch', newunit=file_unit)
if (stat /= 0) stop 'Error: failed to open scratch file'
! Initialise curl.
rc = curl_global_init(CURL_GLOBAL_DEFAULT)
curl_ptr = curl_easy_init()
if (.not. c_associated(curl_ptr)) stop 'Error: curl_easy_init() failed'
! Set curl options.
rc = curl_easy_setopt(curl_ptr, CURLOPT_DEFAULT_PROTOCOL, PROTOCOL)
rc = curl_easy_setopt(curl_ptr, CURLOPT_URL, URL)
rc = curl_easy_setopt(curl_ptr, CURLOPT_WRITEFUNCTION, c_funloc(write_callback))
rc = curl_easy_setopt(curl_ptr, CURLOPT_WRITEDATA, c_loc(file_unit))
! Send request and clean-up.
rc = curl_easy_perform(curl_ptr)
call curl_easy_cleanup(curl_ptr)
call curl_global_cleanup()
if (rc /= CURLE_OK) stop 'Error: curl_easy_perform() failed'
! Output contents of scratch file.
rewind (file_unit)
do
read (file_unit, '(a)', iostat=stat) line
if (stat /= 0) exit
print '(a)', trim(line)
end do
close (file_unit)
end program main
The callback routine must have the bind(c)
attribute. By
default, arguments are passed by reference unless the particular dummy argument
has the value
attribute. The Fortran program has to be linked
against the static interface library libfortran-curl.a
and
-lcurl
:
$ gfortran13 -I/usr/local/include -L/usr/local/lib -o http http.f90 libfortran-curl.a -lcurl
$ ./http
abbreviation: BST
client_ip: XXX.XXX.XXX.XXX
datetime: 2020-06-09T22:28:16.386066+01:00
day_of_week: 2
day_of_year: 161
dst: true
dst_from: 2020-03-29T01:00:00+00:00
dst_offset: 3600
dst_until: 2020-10-25T01:00:00+00:00
raw_offset: 0
timezone: Europe/London
unixtime: 1591738096
utc_datetime: 2020-06-09T21:28:16.386066+00:00
utc_offset: +01:00
week_number: 24
See the fortran-curl repository for more examples.
Fortran Libraries
- fortran-curl: Fortran 2008 interface bindings to libcurl
References
- cURL: Official website
< POSIX Message Queues | [Index] | HTTP Client > |