diff --git a/example/data/file.txt b/example/data/file.txt new file mode 100644 index 0000000..32d6490 --- /dev/null +++ b/example/data/file.txt @@ -0,0 +1 @@ +Here is the data from the file that needs to be sent to the server. \ No newline at end of file diff --git a/example/post.f90 b/example/post.f90 index 9fe2402..7ecd44d 100644 --- a/example/post.f90 +++ b/example/post.f90 @@ -1,13 +1,14 @@ program post_request ! This program demonstrates sending JSON data using POST request and printing the ! status, length of the body, method, and the body of the response. - use http, only: response_type, request, HTTP_POST, header_type + use http, only: response_type, request, HTTP_POST, pair_type implicit none type(response_type) :: response character(:), allocatable :: json_data - type(header_type), allocatable :: req_header(:) + type(pair_type), allocatable :: req_header(:) - req_header = [header_type('Content-Type', 'application/json')] + ! Storing request header in array of pair_type object + req_header = [pair_type('Content-Type', 'application/json')] ! JSON data we want to send json_data = '{"name":"Jhon","role":"developer"}' diff --git a/example/post_file.f90 b/example/post_file.f90 new file mode 100644 index 0000000..c8d4e66 --- /dev/null +++ b/example/post_file.f90 @@ -0,0 +1,23 @@ +program post_file + + ! This program demonstrates sending File using POST request. + + use http, only : request, response_type, HTTP_POST, pair_type + implicit none + type(response_type) :: response + type(pair_type) :: file_data + + ! pair_type('', '/path/to/file.txt') + file_data = pair_type('file.txt', './example/data/file.txt') + + response = request(url='https://httpbin.org/post', method=HTTP_POST, file=file_data) + + if(.not. response%ok) then + print *,'Error message : ', response%err_msg + else + print *, 'Response Code : ', response%status_code + print *, 'Response Length : ', response%content_length + print *, 'Response Method : ', response%method + print *, 'Response Content : ', response%content + end if +end program post_file \ No newline at end of file diff --git a/example/post_form_data.f90 b/example/post_form_data.f90 index 10c493b..dc9b4b2 100644 --- a/example/post_form_data.f90 +++ b/example/post_form_data.f90 @@ -1,13 +1,15 @@ program post_form_data - ! This program demonstrates sending Form data using POST request and printing the - ! status, length of the body, method, and the body of the response. - use http, only: response_type, request, HTTP_POST, header_type, form_type + ! This program demonstrates sending Form data using POST request and printing + ! the status, length of the body, method, and the body of the response. + use http, only: response_type, request, HTTP_POST, pair_type implicit none type(response_type) :: response - type(header_type), allocatable :: req_header(:) - type(form_type), allocatable :: form_data(:) + type(pair_type), allocatable :: req_header(:) + type(pair_type), allocatable :: form_data(:) - form_data = [form_type('param1', 'value1'), form_type('param2', 'value2')] + ! Storing form data in a array of pair_type object, each pair_type object + ! represent a single form field + form_data = [pair_type('param1', 'value1'), pair_type('param2', 'value2')] response = request(url='https://httpbin.org/post', method=HTTP_POST, form=form_data) diff --git a/example/response_header.f90 b/example/response_header.f90 index a5360a7..496f452 100644 --- a/example/response_header.f90 +++ b/example/response_header.f90 @@ -2,20 +2,22 @@ program response_header ! This program demonstrates sending user-provided headers in a GET request ! and iterating over the headers of the response sent back by the server. use stdlib_string_type, only: string_type, write(formatted) - use http, only: response_type, request, header_type + use http, only: response_type, request, pair_type implicit none type(response_type) :: response - type(header_type), allocatable :: header(:), req_header(:) + type(pair_type), allocatable :: header(:), req_header(:) character(:), allocatable :: val integer :: i = 0 + ! Storing request header in array of pair_type object, where each pair_type + ! object represents a single header. (in header-name,header-value format) req_header = [ & - header_type('Another-One', 'Hello'), & - header_type('Set-Cookie', 'Theme-Light'), & - header_type('Set-Cookie', 'Auth-Token: 12345'), & - header_type('User-Agent', 'my user agent') & - ] + pair_type('Another-One', 'Hello'), & + pair_type('Set-Cookie', 'Theme-Light'), & + pair_type('Set-Cookie', 'Auth-Token: 12345'), & + pair_type('User-Agent', 'my user agent') & + ] response = request(url='https://httpbin.org/get', header=req_header) @@ -28,7 +30,7 @@ program response_header header = response%header ! Iterate over response headers. do i = 1, size(header) - print *, header(i)%key, ': ', header(i)%value + print *, header(i)%name, ': ', header(i)%value end do ! getting header value by header name diff --git a/src/http.f90 b/src/http.f90 index 066a0d9..78a3e3f 100644 --- a/src/http.f90 +++ b/src/http.f90 @@ -1,8 +1,7 @@ module http use http_request, only: & - HTTP_DELETE, HTTP_GET, HTTP_HEAD, HTTP_PATCH, HTTP_POST, HTTP_POST + HTTP_DELETE, HTTP_GET, HTTP_HEAD, HTTP_PATCH, HTTP_POST, HTTP_PUT use http_response, only: response_type use http_client, only: request - use http_header, only : header_type - use http_form, only : form_type + use http_pair, only : pair_type end module http diff --git a/src/http/http_client.f90 b/src/http/http_client.f90 index 6d6488e..fd7cbe8 100644 --- a/src/http/http_client.f90 +++ b/src/http/http_client.f90 @@ -10,19 +10,20 @@ module http_client 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_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 + 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 use stdlib_optval, only: optval use http_request, only: request_type use http_response, only: response_type - use http_header, only: append_header, header_has_key, header_type - use http_form, only: form_type + use http_pair, only: append_pair, pair_has_name, pair_type implicit none @@ -52,7 +53,7 @@ module http_client ! new client_type object using the request object as a parameter and sends the request to the server ! using the client_get_response method. The function returns the response_type object containing the ! server's response. - function new_request(url, method, header, data, form) result(response) + function new_request(url, method, header, data, form, file) result(response) !! This function creates a new HTTP request object of the request_type type and sends !! the request to the server using the client_type object. The function takes the URL, !! HTTP method, request headers, request data, and form data as input arguments and returns @@ -64,10 +65,12 @@ function new_request(url, method, header, data, form) result(response) !! An character(len=*) argument that specifies the URL of the server. character(len=*), intent(in), optional :: data !! An optional character(len=*) argument that specifies the data to send in the request body. - type(header_type), intent(in), optional :: header(:) - !! An optional array of header_type objects that specifies the request headers to send to the server. - type(form_type), intent(in), optional :: form(:) - !! An optional array of form_type objects that specifies the form data to send in the request body. + type(pair_type), intent(in), optional :: header(:) + !! An optional array of pair_type objects that specifies the request headers to send to the server. + type(pair_type), intent(in), optional :: form(:) + !! An optional array of pair_type objects that specifies the form data to send in the request body. + type(pair_type), intent(in), optional :: file + !! An optional pair_type object that specifies the file data to send in the request body. type(response_type) :: response !! A response_type object containing the server's response. type(request_type) :: request @@ -84,11 +87,11 @@ function new_request(url, method, header, data, form) result(response) if (present(header)) then request%header = header ! Set default request headers. - if (.not. header_has_key(header, 'user-agent')) then - call append_header(request%header, 'user-agent', 'fortran-http/0.1.0') + if (.not. pair_has_name(header, 'user-agent')) then + call append_pair(request%header, 'user-agent', 'fortran-http/0.1.0') end if else - request%header = [header_type('user-agent', 'fortran-http/0.1.0')] + request%header = [pair_type('user-agent', 'fortran-http/0.1.0')] end if ! setting the request data to be send @@ -101,6 +104,11 @@ function new_request(url, method, header, data, form) result(response) request%form = form end if + ! setting request file + if(present(file)) then + request%file = file + end if + ! Populates the response client = client_type(request=request) response = client%client_get_response() @@ -128,7 +136,7 @@ function client_get_response(this) result(response) !! An inout argument of the client_type class that specifies the HTTP request to send. type(response_type), target :: response !! A response_type object containing the server's response. - type(c_ptr) :: curl_ptr, header_list_ptr + type(c_ptr) :: curl_ptr, header_list_ptr integer :: rc, i curl_ptr = c_null_ptr @@ -147,16 +155,13 @@ & function failed. This can occur due to insufficient memory available in the sy end if ! setting request URL - rc = curl_easy_setopt(curl_ptr, CURLOPT_URL, this%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) - ! encode the request form - call prepare_form_encoded_str(curl_ptr, this%request) - ! setting request body - rc = set_body(curl_ptr, this%request%data, this%request%form_encoded_str) + rc = set_body(curl_ptr, this%request) ! prepare headers for curl call prepare_request_header_ptr(header_list_ptr, this%request%header) @@ -198,10 +203,7 @@ end function client_get_response ! The encoded name-value pairs are concatenated into a single string, separated ! by '&' characters. The resulting string is stored in the form_encoded_str field ! of the request object. - ! Finally, the subroutine checks if the Content-Type header is already set in the - ! request object. If not, it sets the header to application/x-www-form-urlencoded, - ! indicating that the HTTP request body contains URL-encoded form data. - subroutine prepare_form_encoded_str(curl_ptr, request) + function prepare_form_encoded_str(curl_ptr, request) result(form_encoded_str) !! This subroutine converts the request%form data into URL-encoded name-value pairs !! and stores the result in the request%form_encoded_str variable. The resulting !! string is used as the HTTP request body with the application/x-www-form-urlencoded @@ -212,28 +214,25 @@ subroutine prepare_form_encoded_str(curl_ptr, request) type(request_type), intent(inout) :: request !! An inout argument of the request_type type, which contains the form data to be !! encoded and the form_encoded_str variable to store the result. + character(:), allocatable :: form_encoded_str integer :: i if(allocated(request%form)) then do i=1, size(request%form) - if(.not. allocated(request%form_encoded_str)) then - request%form_encoded_str = curl_easy_escape(curl_ptr, request%form(i)%name, & + 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 - request%form_encoded_str = request%form_encoded_str // '&' // & + 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 - ! setting the Content-Type header to application/x-www-form-urlencoded, used for sending form data - if (.not. header_has_key(request%header, 'Content-Type')) then - call append_header(request%header, 'Content-Type', 'application/x-www-form-urlencoded') - end if end if - end subroutine prepare_form_encoded_str + end function prepare_form_encoded_str ! This subroutine prepares a linked list of headers for an HTTP request using the libcurl library. - ! The function takes an array of header_type objects that contain the key-value pairs of the headers + ! The function takes an array of pair_type objects that contain the key-value pairs of the headers ! to include in the request. The subroutine 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. @@ -241,15 +240,15 @@ subroutine prepare_request_header_ptr(header_list_ptr, req_headers) !! This subroutine prepares a linked list of headers for an HTTP request using the libcurl library. type(c_ptr), intent(out) :: header_list_ptr !! An out argument of type c_ptr that is allocated and set to point to a linked list of headers. - type(header_type), allocatable, intent(in) :: req_headers(:) - !! An in argument of type header_type array that specifies the headers to include in the request. - character(:), allocatable :: h_key, h_val, final_header_string + type(pair_type), allocatable, intent(in) :: req_headers(:) + !! An in argument of type pair_type array that specifies the headers to include in the request. + character(:), allocatable :: h_name, h_val, final_header_string integer :: i do i = 1, size(req_headers) - h_key = req_headers(i)%key + h_name = req_headers(i)%name h_val = req_headers(i)%value - final_header_string = h_key // ':' // h_val + 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 @@ -290,34 +289,88 @@ function set_method(curl_ptr, method, response) result(status) end select end function set_method - ! This function sets the request body for a curl handle based on the input data and form_encoded_str - ! strings and returns the status of the curl_easy_setopt function call. The function takes two input - ! arguments, data and form_encoded_str, which represent the request body data as a raw string or in - ! URL encoded form, respectively. The function then uses an if statement to set the request body using - ! the curl_easy_setopt function call and the CURLOPT_POSTFIELDS and CURLOPT_POSTFIELDSIZE_LARGE options. - ! The function returns an integer value representing the status of the curl_easy_setopt function call. - function set_body(curl_ptr, data, form_encoded_str) result(status) - !! This function sets the request body for a curl handle based on the input data and form_encoded_str - !! strings and returns the status of the curl_easy_setopt function call. + ! The set_body function determines the type of data to include in the request body + ! based on the inputs provided. If data is provided, it is sent as the body of the + ! request. If form is provided without a file, the form data is URL encoded and sent + ! as the body of the request. If file is provided without form, the file is sent + ! using a multipart/form-data header. If both form and file are provided, the file + ! takes priority and the form data along with file is sent as part of the multipart/form-data + ! body. If data, form, and file are all provided, only data is sent and the form and file + ! inputs are ignored. + ! + ! 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 set_body function sets the request body. type(c_ptr), intent(out) :: curl_ptr !! An out argument of type c_ptr that is set to point to a new curl handle. - character(*), intent(in), target :: data - !! An in argument of type character(*) that specifies the request body data. - character(*), intent(in), target :: form_encoded_str - !! An in argument of type character(*) that specifies the request body data in URL encoded form. + type(request_type), intent(inout) :: request + !! The HTTP request integer :: status !! An integer value representing the status of the curl_easy_setopt function call. + integer :: i + character(len=:), allocatable :: form_encoded_str + type(c_ptr) :: mime_ptr, part_ptr - if(len(data) > 0) then - status = curl_easy_setopt(curl_ptr, CURLOPT_POSTFIELDS, c_loc(data)) - status = curl_easy_setopt(curl_ptr, CURLOPT_POSTFIELDSIZE_LARGE, len(data, kind=int64)) - else if(len(form_encoded_str) > 0) then - status = curl_easy_setopt(curl_ptr, CURLOPT_POSTFIELDS, c_loc(form_encoded_str)) - status = curl_easy_setopt(curl_ptr, CURLOPT_POSTFIELDSIZE_LARGE, len(form_encoded_str, kind=int64)) + ! if only data is passed + if(len(request%data) > 0) then + status = set_postfields(curl_ptr, request%data) + + ! if file is passsed + else if(len(request%file%name) > 0) 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 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. + 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 + ! This function is a callback function used by the libcurl library 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. ! The function takes four input arguments: ptr, size, nmemb, and client_data. ptr is a pointer to the @@ -368,7 +421,7 @@ 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 libcurl library to handle HTTP headers. !! It is called for each header received from the server and stores the header in an array of - !! header_type objects in a response_type object. + !! pair_type objects in a response_type object. type(c_ptr), intent(in), value :: ptr !! An in argument of type c_ptr that points to the received header buffer. integer(kind=c_size_t), intent(in), value :: size @@ -380,7 +433,7 @@ function client_header_callback(ptr, size, nmemb, client_data) bind(c) integer(kind=c_size_t) :: client_header_callback !! An integer(kind=c_size_t) value representing the number of bytes received. type(response_type), pointer :: response - character(len=:), allocatable :: buf, h_key, h_value + character(len=:), allocatable :: buf, h_name, h_value integer :: i client_header_callback = int(0, kind=c_size_t) @@ -396,15 +449,15 @@ function client_header_callback(ptr, size, nmemb, client_data) bind(c) call c_f_str_ptr(ptr, buf, nmemb) if (.not. allocated(buf)) return - ! Parsing Header, and storing in array of header_type object + ! Parsing Header, and storing in array of pair_type object i = index(buf, ':') if(i /= 0 .and. len(buf) > 2) then - h_key = trim(buf(:i-1)) + 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_key) > 0) then - call append_header(response%header, h_key, h_value) - ! response%header = [response%header, header_type(h_key, h_value)] + 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) diff --git a/src/http/http_form.f90 b/src/http/http_form.f90 deleted file mode 100644 index 3e98455..0000000 --- a/src/http/http_form.f90 +++ /dev/null @@ -1,15 +0,0 @@ -module http_form - !! This module contains the definition of a form_type derived type, which represents a - !!single field of an HTTP form. - implicit none - private - public :: form_type - - type :: form_type - !! A derived type representing a single field of an HTTP form. - character(:), allocatable :: name - !! The name of the form field - character(:), allocatable :: value - !! The value of the form filed - end type form_type -end module http_form \ No newline at end of file diff --git a/src/http/http_header.f90 b/src/http/http_header.f90 deleted file mode 100644 index 917402c..0000000 --- a/src/http/http_header.f90 +++ /dev/null @@ -1,90 +0,0 @@ -module http_header - - !! This module provides a simple key value type to use for HTTP headers. - !! It also provides procedures to inquire about the presence of a key and - !! its value in a header array, as well as a procedure to append new - !! headers to an existing array of headers. - - use stdlib_ascii, only: to_lower - - implicit none - private - - public :: header_type - public :: append_header - public :: get_header_value - public :: header_has_key - - type :: header_type - character(:), allocatable :: key, value - end type header_type - -contains - - subroutine append_header(header, key, value) - !! Append a new header_type instance with key and value members to the - !! header array. - type(header_type), allocatable, intent(inout) :: header(:) - !! Header array to append to - character(*), intent(in) :: key - !! Key member of header_type to append - character(*), intent(in) :: value - !! Value member of header_type to append - type(header_type), allocatable :: temp(:) - integer :: n - - if (allocated(header)) then - n = size(header) - allocate(temp(n+1)) - temp(1:n) = header - temp(n+1) = header_type(key, value) - call move_alloc(temp, header) - else - allocate(header(1)) - header(1) = header_type(key, value) - end if - end subroutine append_header - - pure function get_header_value(header, key) result(val) - !! Return the value of a requested key in a header array. If the key is - !! not found, the function returns an empty string (unallocated). If - !! there are duplicates of the key in the header array, return the value - !! of the first occurence of the key. - type(header_type), intent(in) :: header(:) - !! Header to search for key - character(*), intent(in) :: key - !! Key to search in header - character(:), allocatable :: val - !! Value of the key to return - integer :: n - - do n = 1, size(header) - if (to_lower(key) == to_lower(header(n)%key)) then - val = header(n)%value - return - end if - end do - - end function get_header_value - - pure logical function header_has_key(header, key) - !! Return .true. if key is present in header, .false. otherwise. - !! HTTP headers are case insensitive, so values are converted to - !! lowercase before comparison. - type(header_type), intent(in) :: header(:) - !! Header to search for key - character(*), intent(in) :: key - !! Key to search in header - integer :: n - - header_has_key = .false. - do n = 1, size(header) - if (to_lower(key) == to_lower(header(n)%key)) then - header_has_key = .true. - return - end if - end do - - end function header_has_key - -end module http_header \ No newline at end of file diff --git a/src/http/http_pair.f90 b/src/http/http_pair.f90 new file mode 100644 index 0000000..b624c35 --- /dev/null +++ b/src/http/http_pair.f90 @@ -0,0 +1,105 @@ +module http_pair + + !! This module provides a simple name-value type to use for HTTP Header, + !! Form, and File. It also provides procedures to inquire about the presence + !! of a name and its value in a pair array, as well as a procedure to append + !! new pairs to an existing array of pairs. + + use stdlib_ascii, only: to_lower + + implicit none + private + + public :: pair_type + public :: append_pair + public :: get_pair_value + public :: pair_has_name + + type :: pair_type + !! A derived type use to store a name-value pair, it is used in many + !! instances like: + !! 1. Storing request and response headers: + !! * `name` to represent the header name. + !! * `value` to represent the header value. + !! 2. Representing fields in a url-encoded HTTP form: + !! * `name` to represent the form field name. + !! * `value` to represent the form field value. + !! 3. Storing information about the file to upload: + !! * `name` to represent the name of the file. + !! * `value` to represent the path of the file on the local system. + character(:), allocatable :: name + !! Name (key) + character(:), allocatable :: value + !! Value + end type pair_type + +contains + + subroutine append_pair(pair, name, value) + !! Append a new `pair_type` instance with name and value members to + !! the pair array. + type(pair_type), allocatable, intent(inout) :: pair(:) + !! `pair_type` array to append to + character(*), intent(in) :: name + !! Name member of `pair_type` to append + character(*), intent(in) :: value + !! Value member of `pair_type` to append + type(pair_type), allocatable :: temp(:) + integer :: n + + if (allocated(pair)) then + n = size(pair) + allocate(temp(n+1)) + temp(1:n) = pair + temp(n+1) = pair_type(name, value) + call move_alloc(temp, pair) + else + allocate(pair(1)) + pair(1) = pair_type(name, value) + end if + end subroutine append_pair + + pure function get_pair_value(pair, name) result(val) + !! Return the value of a requested name in a pair array. + !! The search for the pair by its name is case-insensitive. + !! If the name is not found, the function returns an unallocated string. + !! If there are duplicates of the name in the pair array, return the + !! value of the first occurence of the name. + type(pair_type), intent(in) :: pair(:) + !! Pair to search for name + character(*), intent(in) :: name + !! Name to search in pair + character(:), allocatable :: val + !! Value of the name to return + integer :: n + + do n = 1, size(pair) + if (to_lower(name) == to_lower(pair(n)%name)) then + val = pair(n)%value + return + end if + end do + + end function get_pair_value + + pure logical function pair_has_name(pair, name) + !! Return .true. if name is present in pair, .false. otherwise. + !! HTTP pairs are case insensitive, so values are converted to + !! lowercase before comparison. + type(pair_type), intent(in) :: pair(:) + !! Pair to search for name + character(*), intent(in) :: name + !! Name to search in pair + integer :: n + + pair_has_name = .false. + do n = 1, size(pair) + if (to_lower(name) == to_lower(pair(n)%name)) then + pair_has_name = .true. + return + end if + end do + + end function pair_has_name + +end module http_pair \ No newline at end of file diff --git a/src/http/http_request.f90 b/src/http/http_request.f90 index c32abe4..575e563 100644 --- a/src/http/http_request.f90 +++ b/src/http/http_request.f90 @@ -3,8 +3,8 @@ module http_request !! This module contains the definition of a request_type derived type, which !! represents an HTTP request. - use http_form , only: form_type - use http_header, only: header_type + ! use http_form , only: pair_type + use http_pair, only: pair_type use stdlib_string_type, only: string_type, to_lower, operator(==), char implicit none @@ -32,9 +32,11 @@ module http_request !! The URL-encoded form data. integer :: method !! The HTTP method of the request. - type(header_type), allocatable :: header(:) + type(pair_type), allocatable :: header(:) !! An Array of request headers. - type(form_type), allocatable :: form(:) + type(pair_type), allocatable :: form(:) !! An array of fields in an HTTP form. + type(pair_type) :: file + !! Used to store information about files to be sent in HTTP requests. end type request_type end module http_request diff --git a/src/http/http_response.f90 b/src/http/http_response.f90 index ded6d9a..d715e7c 100644 --- a/src/http/http_response.f90 +++ b/src/http/http_response.f90 @@ -4,7 +4,7 @@ module http_response !! represents an HTTP response. use, intrinsic :: iso_fortran_env, only: int64 - use http_header, only: header_type, get_header_value + use http_pair, only: pair_type, get_pair_value use stdlib_string_type, only: string_type, to_lower, operator(==), char implicit none @@ -29,25 +29,25 @@ module http_response !! length of the response content. logical :: ok = .true. !! true if the response was successful else false. - type(header_type), allocatable :: header(:) + type(pair_type), allocatable :: header(:) !! An Array of response headers. contains procedure :: header_value end type response_type contains - pure function header_value(this, key) result(val) - !! The header_value function takes a key string as input and returns + pure function header_value(this, name) result(val) + !! The header_value function takes a header name string as input and returns !! the corresponding value as a string from a response_type object's !! header array. class(response_type), intent(in) :: this !! An object representing the HTTP response. - character(*), intent(in) :: key - !! The key of the header value to be retrieved. + character(*), intent(in) :: name + !! The name of the header value to be retrieved. character(:), allocatable :: val - !! The value of the specified key in the HTTP response header. + !! The value of the specified name in the HTTP response header. - val = get_header_value(this%header, key) + val = get_pair_value(this%header, name) end function header_value end module http_response diff --git a/test/test_get.f90 b/test/test_get.f90 index c58e1af..c10b5bc 100644 --- a/test/test_get.f90 +++ b/test/test_get.f90 @@ -1,12 +1,12 @@ program test_get use iso_fortran_env, only: stderr => error_unit - use http, only : response_type, request, header_type + use http, only : response_type, request, pair_type implicit none type(response_type) :: res character(:), allocatable :: msg, original_content logical :: ok = .true. - type(header_type), allocatable :: request_header(:) + type(pair_type), allocatable :: request_header(:) integer :: i original_content = '{"id":1,"title":"iPhone 9","description":"An apple mobile which is nothing like & @@ -18,10 +18,10 @@ program test_get ! setting request header request_header = [ & - header_type('Another-One', 'Hello'), & - header_type('Set-Cookie', 'Theme-Light'), & - header_type('Set-Cookie', 'Auth-Token: 12345'), & - header_type('User-Agent', 'my user agent') & + pair_type('Another-One', 'Hello'), & + pair_type('Set-Cookie', 'Theme-Light'), & + pair_type('Set-Cookie', 'Auth-Token: 12345'), & + pair_type('User-Agent', 'my user agent') & ] ! res = request(url='https://reqres.in/api/users/1', header=request_header) diff --git a/test/test_header.f90 b/test/test_header.f90 index d17b62e..7b09b22 100644 --- a/test/test_header.f90 +++ b/test/test_header.f90 @@ -1,16 +1,16 @@ program test_header use iso_fortran_env, only: stderr => error_unit - use http_header, only: get_header_value, header_has_key, header_type + use http_pair, only: get_pair_value, pair_has_name, pair_type implicit none - type(header_type), allocatable :: header(:) + type(pair_type), allocatable :: header(:) logical :: ok = .true. integer :: n header = [ & - header_type('One', '1'), & - header_type('Two', '2') & + pair_type('One', '1'), & + pair_type('Two', '2') & ] if (.not. size(header) == 2) then @@ -28,7 +28,7 @@ program test_header write(stderr, '(a)') 'Failed: Second header value is incorrect.' end if - header = [header, header_type('Three', '3')] + header = [header, pair_type('Three', '3')] if (.not. size(header) == 3) then ok = .false. @@ -41,22 +41,22 @@ program test_header end if do n = 1, size(header) - if (.not. get_header_value(header, header(n)%key) == header(n)%value) then + if (.not. get_pair_value(header, header(n)%name) == header(n)%value) then ok = .false. write(stderr, '(a)') 'Failed: Appended header value is incorrect.' end if end do do n = 1, size(header) - if (.not. header_has_key(header, header(n)%key)) then + if (.not. pair_has_name(header, header(n)%name)) then ok = .false. - write(stderr, '(a)') 'Failed: Incorrect output from header_has_key.' + write(stderr, '(a)') 'Failed: Incorrect output from pair_has_name.' end if end do - if (header_has_key(header, "Non-Existent")) then + if (pair_has_name(header, "Non-Existent")) then ok = .false. - write(stderr, '(a)') 'Failed: Incorrect output from header_has_key for non-existent key.' + write(stderr, '(a)') 'Failed: Incorrect output from pair_has_name for non-existent key.' end if if (.not. ok) then diff --git a/test/test_post.f90 b/test/test_post.f90 index 5010dd2..58483c6 100644 --- a/test/test_post.f90 +++ b/test/test_post.f90 @@ -1,14 +1,14 @@ program test_post use iso_fortran_env, only: stderr => error_unit - use http, only: request, header_type, HTTP_POST, response_type + use http, only: request, pair_type, HTTP_POST, response_type implicit none type(response_type) :: res character(:), allocatable :: json_data, original_content, msg - type(header_type), allocatable :: req_header(:) + type(pair_type), allocatable :: req_header(:) logical :: ok = .true. original_content = '{"id":101,"title":"BMW","description":"A luxurious and high-performance vehicle"}' - req_header = [header_type('Content-Type', 'application/json')] + req_header = [pair_type('Content-Type', 'application/json')] json_data = '{"title":"BMW","description":"A luxurious and high-performance vehicle"}'