http_client.f90 Source File

This file defines the client_type derived type, which handles the process of making HTTP requests. The actual HTTP requests are executed using the Fortran-curl package as the underlying mechanism.



Contents

Source Code


Source Code

!!> This file defines the **`client_type`** derived type, which handles the 
!!> process of making **HTTP requests**. The actual HTTP requests are executed 
!!> using the [Fortran-curl](https://github.com/interkosmos/fortran-curl) 
!!> package as the underlying mechanism.

module http_client

    !!> This module defines the **`client_type`** derived type, which handles the 
    !!> process of making **HTTP requests**. The actual HTTP requests are executed 
    !!> using the [Fortran-curl](https://github.com/interkosmos/fortran-curl) 
    !!> package as the underlying mechanism.

    use iso_fortran_env, only: int64
    use iso_c_binding, only: c_associated, c_f_pointer, c_funloc, c_loc, &
        c_null_ptr, c_ptr, c_size_t, c_null_char
    use curl, only: c_f_str_ptr, curl_easy_cleanup, curl_easy_getinfo, &
        curl_easy_init, curl_easy_perform, curl_easy_setopt, &
        curl_easy_strerror, curl_slist_append, CURLE_OK, &
        CURLINFO_RESPONSE_CODE, CURLOPT_CUSTOMREQUEST, CURLOPT_HEADERDATA, &
        CURLOPT_HEADERFUNCTION, CURLOPT_HTTPHEADER, CURLOPT_URL, &
        CURLOPT_WRITEDATA, CURLOPT_WRITEFUNCTION, &
        CURLOPT_POSTFIELDS, CURLOPT_POSTFIELDSIZE_LARGE, curl_easy_escape, &
        curl_mime_init, curl_mime_addpart, curl_mime_filedata,curl_mime_name, &
        CURLOPT_MIMEPOST,curl_mime_data, CURL_ZERO_TERMINATED, &
        CURLOPT_TIMEOUT, CURLOPT_CONNECTTIMEOUT, &
        CURLOPT_HTTPAUTH, CURLAUTH_BASIC, CURLOPT_USERNAME, CURLOPT_PASSWORD
    use stdlib_optval, only: optval
    use http_request, only: request_type
    use http_response, only: response_type
    use http_pair, only: append_pair, pair_has_name, pair_type
    use http_version, only: VERSION_STRING
    
    implicit none

    private
    public :: request

    ! http_client Type
    type :: client_type
        !!> A derived type, responsible for making **actual HTTP `request`** using
        !!> fortran-curl at backend.
        type(request_type) :: request
    contains
        procedure :: client_get_response
    end type client_type

    interface client_type
        !!> Interface for `new_client` function.
        module procedure new_client
    end interface client_type

    interface request
        !!> Interface for `new_request` function.
        module procedure new_request
    end interface request
    
contains

    function new_request(url, method, header, data, form, file, timeout, auth) result(response)
    
        !!> This function create a `request_type` object and populates it.
        !!> The function returns the `response_type` object containing the
        !!> **server's response**.
        !!____
        !!> #### Note :
        !!> If the `header` argument is not provided, **default `user-agent`
        !!> header is set to `http-client/0.1`**.
    
        integer, intent(in), optional :: method
            !! Specifies the HTTP `method` to use for the request. 
            !! The **default value is 1**, which corresponds to the **`HTTP_GET`** method.
        character(len=*), intent(in) :: url
            !! Specifies the **`URL`** of the server.
        character(len=*), intent(in), optional :: data
            !! Specifies the **`data`** that needs to be sent to the server.
        type(pair_type), intent(in), optional :: header(:)
            !! Specifies the **request `headers`** that need to be sent to the server.
        type(pair_type), intent(in), optional :: form(:)
            !! Specifies the **`form data`** that needs to be sent to the server.
        type(pair_type), intent(in), optional :: file
            !! Specifies the **`file`** that needs to be sent to the server.
        integer, intent(in), optional :: timeout
            !! **`Timeout`** value for the request in **seconds**.
        type(pair_type), intent(in), optional :: auth
            !! stores the `username` and `password` for **`authentication`** purposes.
        type(response_type) :: response
            !! Stores the server's **`response`**.

        type(request_type) :: request
        type(client_type) :: client
        integer :: i

        ! setting request url
        request%url = url

        ! Set default HTTP method.
        request%method = optval(method, 1)
        
        ! Set request header
        if (present(header)) then
            request%header = header
            ! Set default request headers.
            if (.not. pair_has_name(header, 'user-agent')) then
              call append_pair(request%header, 'user-agent', 'http-client/'//VERSION_STRING)
            end if
        else
            ! Set default request headers.
            request%header = [pair_type('user-agent', 'http-client/'//VERSION_STRING)]
        end if

        ! setting the request data to be send
        if(present(data)) then
            request%data = data
        end if
        
        ! setting request form
        if(present(form)) then
            request%form = form
        end if

        ! setting request file
        if(present(file)) then
            request%file = file
        end if

        ! Set request timeout.
        request%timeout = optval(timeout, -1)
                
        ! setting username and password for Authentication
        if(present(auth)) then
            request%auth = auth
        end if

        ! Populates the response 
        client = client_type(request=request)
        response = client%client_get_response()
    end function new_request

    function new_client(request) result(client)

        !!> This is the constructor for the `client_type` derived type.
        
        type(request_type), intent(in) :: request
            !! Specifies the **HTTP `request`** to send.
        type(client_type) :: client
            !! A `client_type` object containing the `request` field set to the input `request` object.

        client%request = request
    end function new_client

    function client_get_response(this) result(response)
        
        !!> This function sends an HTTP `request` to a server using the 
        !!> [fortran-curl](https://github.com/interkosmos/fortran-curl) package 
        !!> and stores the server's response in a `response_type` 
        !!> object.

        class(client_type), intent(inout) :: this
            !! Contains the HTTP `request` to send.
        type(response_type), target :: response
            !! Contains the **server's response**.

        type(c_ptr) :: curl_ptr, header_list_ptr
        integer :: rc, i
        
        curl_ptr = c_null_ptr
        header_list_ptr = c_null_ptr
        
        response%url = this%request%url
        
        curl_ptr = curl_easy_init()
      
        if (.not. c_associated(curl_ptr)) then
            response%ok = .false.
            response%err_msg = "The initialization of a new easy handle using the 'curl_easy_init()'&
            & function failed. This can occur due to insufficient memory available in the system. &
            & Additionally, if libcurl is not installed or configured properly on the system"
            return
        end if

        ! setting request URL
        rc = curl_easy_setopt(curl_ptr, CURLOPT_URL, this%request%url)

        ! setting request method
        rc = set_method(curl_ptr, this%request%method, response)

        ! setting request timeout
        rc = set_timeout(curl_ptr, this%request%timeout)

        ! setting request body
        rc = set_body(curl_ptr, this%request)

        ! setting request authentication
        rc = set_auth(curl_ptr, this%request)

        ! prepare headers for curl
        call prepare_request_header_ptr(header_list_ptr, this%request%header)

        ! setting request header
        rc = curl_easy_setopt(curl_ptr, CURLOPT_HTTPHEADER, header_list_ptr);

        ! setting callback for writing received data
        rc = curl_easy_setopt(curl_ptr, CURLOPT_WRITEFUNCTION, c_funloc(client_response_callback))

        ! setting response content pointer to write callback
        rc = curl_easy_setopt(curl_ptr, CURLOPT_WRITEDATA, c_loc(response))

        ! setting callback for writing received headers
        rc = curl_easy_setopt(curl_ptr, CURLOPT_HEADERFUNCTION, c_funloc(client_header_callback))

        ! setting response header pointer to write callback
        rc = curl_easy_setopt(curl_ptr, CURLOPT_HEADERDATA, c_loc(response))

        ! Send request.
        rc = curl_easy_perform(curl_ptr)
        
        if (rc /= CURLE_OK) then
            response%ok = .false.
            response%err_msg = curl_easy_strerror(rc)
        end if
        
        ! setting response status_code
        rc = curl_easy_getinfo(curl_ptr, CURLINFO_RESPONSE_CODE, response%status_code)  
        
        call curl_easy_cleanup(curl_ptr)
      
    end function client_get_response
    
    function prepare_form_encoded_str(curl_ptr, request) result(form_encoded_str)
        
        !!> This subroutine converts the `request%form` into a **URL-encoded name-value 
        !!> string** and returns it.

        ! This subroutine takes a request object containing a list of name-value pairs 
        ! representing the form data. It iterates over the list and URL-encodes each 
        ! name and value using the curl_easy_escape function, which replaces special 
        ! characters with their corresponding escape sequences.
        ! The encoded name-value pairs are concatenated into a single string, separated 
        ! by '&' characters. The resulting string is returned

        type(c_ptr), intent(out) :: curl_ptr
            !! Pointer to the `curl` handler.
        type(request_type), intent(inout) :: request
            !! The HTTP `request` to send, which includes the `form` data to be encoded.
        character(:), allocatable :: form_encoded_str
            !! Stores the **URL Encoded string**.
        
        integer :: i
        if(allocated(request%form)) then
            do i=1, size(request%form)
                if(.not. allocated(form_encoded_str)) then
                    form_encoded_str = curl_easy_escape(curl_ptr, request%form(i)%name, &
                    len(request%form(i)%name)) // '=' // curl_easy_escape(curl_ptr, &
                    request%form(i)%value, len(request%form(i)%value))
                else
                    form_encoded_str = form_encoded_str // '&' // &
                    curl_easy_escape(curl_ptr, request%form(i)%name, len(request%form(i)%name))&
                    // '=' // curl_easy_escape(curl_ptr, request%form(i)%value, len(request%form(i)%value))
                end if
            end do
        end if
    end function prepare_form_encoded_str

    subroutine prepare_request_header_ptr(header_list_ptr, req_headers)
        
        !!> This subroutine prepares `headers` in required format(Linked list) for an HTTP request.
        !!____
        !!> This subroutine prepares a **linked list** of `headers` for an HTTP request using the 
        !!> [fortran-curl](https://github.com/interkosmos/fortran-curl) package. 
        !!> The function takes an array of `pair_type` objects(i.e. `req_headers`) that contain the  
        !!> **key-value** pairs of the headers to include in the request.
        !!> It iterates over the array and constructs a string for each header in the format **`key:value`**.
        !!> The subroutine then appends each string to the linked list using the `curl_slist_append` function.
        !!> The resulting linked list is returned via the `header_list_ptr` argument.
        
        type(c_ptr), intent(out) :: header_list_ptr
            !! A `Pointer` that is allocated and points to a linked list of headers.
        type(pair_type), allocatable, intent(in) :: req_headers(:)
            !! The `headers` to be included in the request.
        character(:), allocatable :: h_name, h_val, final_header_string
        integer :: i

        do i = 1, size(req_headers)
            h_name = req_headers(i)%name
            h_val = req_headers(i)%value
            final_header_string = h_name // ':' // h_val 
            header_list_ptr = curl_slist_append(header_list_ptr, final_header_string)
        end do
    end subroutine prepare_request_header_ptr

    function set_method(curl_ptr, method, response) result(status)
        !!> This function sets the **HTTP `method`** for the request.
        !!____
        !!#### **The `method` argument can take one of the following values:**
        !!> `HTTP_GET`, `HTTP_HEAD`, `HTTP_POST`, `HTTP_PUT`, `HTTP_DELETE`, 
        !!> `HTTP_PATCH`. If any other value is provided, an **error will be thrown**.

        type(c_ptr), intent(out) :: curl_ptr
            !! Pointer to the `curl` handler.
        integer, intent(in) :: method
            !! Specifies the HTTP `method` to use.
        type(response_type), intent(out) :: response
            !! The HTTP `response` from the server.
        integer :: status
            !! The `status` of setting HTTP method.

        select case(method)
        case(1)
            status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'GET' )
            response%method = 'GET'
        case(2)
            status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'HEAD' )
            response%method = 'HEAD'
        case(3)
            status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'POST' )
            response%method = 'POST'
        case(4)
            status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'PUT' )
            response%method = 'PUT'
        case(5)
            status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'DELETE' )
            response%method = 'DELETE'
        case(6)
            status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'PATCH' )
            response%method = 'PATCH'
        case default
            error stop 'Method argument can be either HTTP_GET, HTTP_HEAD, HTTP_POST, HTTP_PUT, HTTP_DELETE, HTTP_PATCH'
        end select
    end function set_method

    function set_timeout(curl_ptr, timeout) result(status)
        !!> This function sets the `timeout` value **(in seconds)**. 
        !!>
        !!> If the `timeout` value is **less than zero**, it is ignored and a success status is returned. 
        type(c_ptr), intent(out) :: curl_ptr
            !! Pointer to the `curl` handle.
        integer(kind=int64), intent(in) :: timeout
            !! `Timeout` seconds for request.
        integer :: status
            !! `Status code` indicating whether the operation was successful.
        if(timeout < 0) then
            status = 0
        else
            ! setting the maximum time allowed for the connection to established.(in seconds)
            status = curl_easy_setopt(curl_ptr, CURLOPT_CONNECTTIMEOUT, timeout)
            ! setting maximum time allowed for transfer operation.(in seconds)
            status = curl_easy_setopt(curl_ptr, CURLOPT_TIMEOUT, timeout)
        end if
    end function set_timeout

    ! This function determines the type of data to include in the `request body` 
    ! based on the inputs provided. 
    
    ! If `data` member is provided, it is sent as the body of the 
    ! request. If along with `data` member `file` or `form` or both `file` and `form` members 
    ! are provided then both `form` and `file` member will be ignored and only `data` member will be
    ! sent in request body. `data` argument takes the highest priority.

    ! If only `file` member is provided then `file` is sent as the body of the request. and 
    ! a default `Content-type` header with value `multipart/form-data` will be set, if no `Content-type`
    ! header is provided. If both `form` and `file` members are provided, then `file` 
    ! and the `form` is sent as part of the body. having default `Content-type` header with value `multipart/form-data`
    ! if no `Content-type` header is provided.
    ! If data, form, and file are all provided, only data is sent and the form and file 
    ! inputs are ignored.
    !
    ! If only `form` member is provided then `form` data is URL encoded and sent 
    ! as the body of the request. and a default `Content-type` header with value 
    ! `application/x-www-form-urlencoded` will be set, if no `Content-type` header is
    ! provided 
    ! 
    ! 
    ! data -> data
    ! form -> form
    ! file -> file
    ! data + form + file -> data
    ! form + file -> form + file (in multipart/form-data)
    ! 
    ! Note : At a time only one file can be send
    function set_body(curl_ptr, request) result(status)
        !!> The function sets the request `body`.
        !!____

        !!> This function determines and set the type of data to include in the `request body` 
        !!> based on the inputs provided to the `request()` procedure.

        !!> The function handles different combinations of `data`, `file`, and `form` members 
        !!> to decide the content and the default header for the request body.

        !!> - If `data` member is provided, it takes the highest priority and is sent as the 
        !!> body of the request. Any other provided `file` or `form` members will be ignored, 
        !!> and only the `data` member will be included in the request body.

        !!> - If only the `file` member is provided, the `file` is sent as the body of the request. 
        !!> If no `Content-type` header is provided, a default `Content-type` header with value 
        !!> `multipart/form-data` will be set.

        !!> - If only the `form` member is provided, the `form` data is URL encoded and sent as 
        !!> the body of the request. If no `Content-type` header is provided, a default `Content-type` 
        !!> header with value `application/x-www-form-urlencoded` will be set.

        !!> - If both `form` and `file` members are provided, both `form` and `file` data are included 
        !!> as part of the request body. A default `Content-type` header with value `multipart/form-data` 
        !!> will be set if no `Content-type` header is provided.

        !!> - If `data`, `form`, and `file` are all provided, only `data` is sent, and the `form` and `file`
        !!> inputs are ignored.

        !!> ### **Combination Behavior Table**


        !!> | Passed Arguments   | Request Body                    | Default Header                | Behavior                                                |
        !! |--------------------|---------------------------------|-------------------------------|---------------------------------------------------------|
        !! | data               | data                            | None                          | The `data` is sent as the body of the request.         |
        !! | file               | file                            | multipart/form-data           | The `file` is sent as the body of the request with the default header.  |
        !! | form               | Form data URL encoded           | application/x-www-form-urlencoded | The `form` data is sent as the body of the request with the default header. |
        !! | data + file        | data (file ignored)             | None                          | The `file` member is ignored, and the `data` is sent as the body of the request. |
        !! | data + form        | data (form ignored)             | None                          | The `form` member is ignored, and the `data` is sent as the body of the request. |
        !! | file + form        | both file and form              | multipart/form-data           | Both `form` and `file` are sent as part of the request. |
        !! | data + file + form | data (form and file ignored)    | None                          | Both `form` and `file` members are ignored, and only the `data` is sent as the body of the request. |
        
        !!> Note: If custom headers are provided in the `headers` parameter, they will be used. Otherwise, default headers will be applied as mentioned in the table.

        type(c_ptr), intent(out) :: curl_ptr
            !! Pointer to the `curl` handle.
        type(request_type), intent(inout) :: request
            !! The HTTP request
        integer :: status
            !! An integer value representing the status of the function call.
        
        integer :: i
        type(c_ptr) :: mime_ptr, part_ptr

        ! if only data is passed
        if (allocated(request%data)) then
            status = set_postfields(curl_ptr, request%data)
        
        ! if file is passsed
        else if (allocated(request%file)) then
            mime_ptr = curl_mime_init(curl_ptr)
            part_ptr = curl_mime_addpart(mime_ptr)
            status = curl_mime_filedata(part_ptr, request%file%value)
            status = curl_mime_name(part_ptr, request%file%name)
            
            ! if both file and form are passed
            if(allocated(request%form)) then 
                do i=1, size(request%form)
                    part_ptr = curl_mime_addpart(mime_ptr)
                    status = curl_mime_data(part_ptr, request%form(i)%value, CURL_ZERO_TERMINATED)
                    status = curl_mime_name(part_ptr, request%form(i)%name)
                end do
            end if
            status = curl_easy_setopt(curl_ptr, CURLOPT_MIMEPOST, mime_ptr)
            
            ! setting the Content-Type header to multipart/form-data, used for sending  binary data
            if (.not. pair_has_name(request%header, 'Content-Type')) then
                call append_pair(request%header, 'Content-Type', 'multipart/form-data')
            end if
        
        ! if only form is passed
        else if (allocated(request%form)) then
            request%form_encoded_str = prepare_form_encoded_str(curl_ptr, request)
            status = set_postfields(curl_ptr, request%form_encoded_str)
           
            ! setting the Content-Type header to application/x-www-form-urlencoded, used for sending form data
            if (.not. pair_has_name(request%header, 'Content-Type')) then
                call append_pair(request%header, 'Content-Type', 'application/x-www-form-urlencoded')
            end if
        else
            ! No curl function was called so set status to zero.
            status = 0
        end if
        
    end function set_body

    function set_postfields(curl_ptr, data) result(status)
        !!> Set the data to be sent in the HTTP POST request body.
        !!____
        !!> Use as helper function by `set_body` procedure to set request body
        type(c_ptr), intent(inout) :: curl_ptr
            !! Pointer to the CURL handle.
        character(*), intent(in), target :: data
            !! The data to be sent in the request body.
        integer :: status
            !! An integer indicating whether the operation was successful (0) or not (non-zero).

        status = curl_easy_setopt(curl_ptr, CURLOPT_POSTFIELDS, c_loc(data))
        status = curl_easy_setopt(curl_ptr, CURLOPT_POSTFIELDSIZE_LARGE, len(data, kind=int64))

    end function set_postfields

    function set_auth(curl_ptr, request) result(status)
        !!> Set the user name and password for Authentication. 
        !!_____
        !!> It sends the user name and password over the network in plain text, easily captured by others.
        type(c_ptr), intent(out) :: curl_ptr
            !! Pointer to the CURL handle.
        type(request_type), intent(inout) :: request
            !! The HTTP request
        integer :: status
            !! An integer indicating whether the operation was successful (0) or not (non-zero).

        if(allocated(request%auth)) then
            status = curl_easy_setopt(curl_ptr, CURLOPT_HTTPAUTH, CURLAUTH_BASIC)
            status = curl_easy_setopt(curl_ptr, CURLOPT_USERNAME, request%auth%name)
            status = curl_easy_setopt(curl_ptr, CURLOPT_PASSWORD, request%auth%value)
        else 
            ! No curl function was called so set status to zero.
            status = 0
        end if
    end function set_auth

    function client_response_callback(ptr, size, nmemb, client_data) bind(c)
        !!> This function is a `callback` function used by the fortran-curl package to handle HTTP responses. 
        !!> It is called for each `chunk` of data received from the server and appends the data to a 
        !!> `response_type` object.
        !!>_____
        !!> It is called for each `chunk` of data received from the server and appends the data to a `response_type` 
        !!> object passed(i.e `client_data`). The function takes four input arguments: `ptr`, `size`, `nmemb`,  
        !!> and `client_data`. `ptr` is a pointer to the received data buffer, `size` specifies the size of each 
        !!> data element, `nmemb` specifies the number of data elements received, and `client_data` is a pointer to  
        !!> a `response_type` object. The function uses `c_f_pointer` to convert the C pointer to a Fortran pointer and 
        !!> appends the received data to the `content` field of the `response_type` object. The function returns an integer
        !!> value representing the **number of bytes received.**
        
        type(c_ptr), intent(in), value :: ptr 
            !! Pointer to the CURL handle.
        integer(kind=c_size_t), intent(in), value :: size 
            !! Specifies the size of each data element.
        integer(kind=c_size_t), intent(in), value :: nmemb
            !! Specifies the number of data elements received.
        type(c_ptr), intent(in), value :: client_data
            !!  Points to a response_type object.
        integer(kind=c_size_t) :: client_response_callback 
            !! The number of bytes received.
        type(response_type), pointer :: response 
        character(len=:), allocatable :: buf
      
        client_response_callback = int(0, kind=c_size_t)
      
        ! Are the passed C pointers associated?
        if (.not. c_associated(ptr)) return
        if (.not. c_associated(client_data)) return
      
        ! Convert C pointer to Fortran pointer.
        call c_f_pointer(client_data, response)
        if (.not. allocated(response%content)) response%content = ''
      
        ! Convert C pointer to Fortran allocatable character.
        call c_f_str_ptr(ptr, buf, nmemb)
        if (.not. allocated(buf)) return
        response%content = response%content // buf
        deallocate (buf)
        response%content_length = response%content_length + nmemb
        
        ! Return number of received bytes.
        client_response_callback = nmemb

    end function client_response_callback

    function client_header_callback(ptr, size, nmemb, client_data) bind(c)
        !!> This function is a `callback` function used by the `fortran-curl` package to handle HTTP headers. 
        !!>_____
        !!> It is called for each header received from the server and stores the header in an `header` member 
        !!> of `response_type` object.
        type(c_ptr), intent(in), value :: ptr 
            !! Pointer to the CURL handle. that points to the received header buffer.
        integer(kind=c_size_t), intent(in), value :: size 
            !!  Specifies the size of each header element.
        integer(kind=c_size_t), intent(in), value :: nmemb
            !! Specifies the number of header elements received.
        type(c_ptr), intent(in), value :: client_data
            !! Pointer to a `response_type` object.
        integer(kind=c_size_t) :: client_header_callback 
            !! The number of bytes received.
        type(response_type), pointer :: response 
        character(len=:), allocatable :: buf, h_name, h_value
        integer :: i
      
        client_header_callback = int(0, kind=c_size_t)
      
        ! Are the passed C pointers associated?
        if (.not. c_associated(ptr)) return
        if (.not. c_associated(client_data)) return
      
        ! Convert C pointer to Fortran pointer.
        call c_f_pointer(client_data, response)
      
        ! Convert C pointer to Fortran allocatable character.
        call c_f_str_ptr(ptr, buf, nmemb)
        if (.not. allocated(buf)) return
        
        ! Parsing Header, and storing in array of pair_type object
        i = index(buf, ':')
        if(i /= 0 .and. len(buf) > 2) then
            h_name = trim(buf(:i-1))
            h_value = buf(i+2 : )
            h_value = h_value( : len(h_value)-2)
            if(len(h_value) > 0 .and. len(h_name) > 0) then
                call append_pair(response%header, h_name, h_value)
                ! response%header = [response%header, pair_type(h_name, h_value)]
            end if
        end if
        deallocate(buf)
        
        ! Return number of received bytes.
        client_header_callback = nmemb

    end function client_header_callback

end module http_client