├── example ├── data │ └── file.txt ├── download_file.f90 ├── basic_auth.f90 ├── get.f90 ├── timeout.f90 ├── post_file.f90 ├── post_form_data.f90 ├── post.f90 └── response_header.f90 ├── tutorial ├── example-project │ ├── github-org-analyzer │ │ ├── test │ │ │ └── check.f90 │ │ ├── fpm.toml │ │ ├── src │ │ │ ├── utils.f90 │ │ │ └── github-org-analyzer.f90 │ │ ├── app │ │ │ └── main.f90 │ │ └── README.md │ └── github-org-analyzer.md └── tutorial.md ├── .gitignore ├── fpm.toml ├── src ├── http │ ├── http_version.f90 │ ├── http_request.f90 │ ├── http_response.f90 │ ├── http_pair.f90 │ └── http_client.f90 └── http.f90 ├── .github └── workflows │ ├── doc-deployment.yml │ └── ci.yml ├── ford.md ├── test ├── test_timeout.f90 ├── test_head.f90 ├── test_auth.f90 ├── test_post.f90 ├── test_delete.f90 ├── test_header.f90 ├── test_put.f90 ├── test_patch.f90 └── test_get.f90 ├── LICENSE └── README.md /example/data/file.txt: -------------------------------------------------------------------------------- 1 | Here is the data from the file that needs to be sent to the server. -------------------------------------------------------------------------------- /tutorial/example-project/github-org-analyzer/test/check.f90: -------------------------------------------------------------------------------- 1 | program check 2 | implicit none 3 | 4 | print *, "Put some tests in here!" 5 | end program check 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /fpm.toml: -------------------------------------------------------------------------------- 1 | name = "http" 2 | version = "0.1.0" 3 | license = "MIT" 4 | author = "Fortran-lang" 5 | copyright = "Copyright 2023, Fortran-lang" 6 | 7 | [build] 8 | auto-executables = true 9 | auto-tests = true 10 | auto-examples = true 11 | module-naming = false 12 | 13 | [install] 14 | library = false 15 | 16 | [fortran] 17 | implicit-typing = false 18 | implicit-external = false 19 | source-form = "free" 20 | 21 | [dependencies] 22 | fortran-curl = {git = "https://github.com/interkosmos/fortran-curl.git", tag = "0.6.0"} 23 | stdlib = "*" 24 | -------------------------------------------------------------------------------- /example/download_file.f90: -------------------------------------------------------------------------------- 1 | program download_file 2 | ! This program demonstrates using a GET request to download a file. 3 | use http, only: response_type, request 4 | use stdlib_io, only: open 5 | implicit none 6 | type(response_type) :: response 7 | integer :: file 8 | 9 | response = request('https://avatars.githubusercontent.com/u/53436240') 10 | 11 | if (response%ok) then 12 | file = open('fortran-lang.png', 'wb') 13 | write(file) response%content 14 | close(file) 15 | else 16 | error stop response%err_msg 17 | end if 18 | 19 | end program download_file 20 | -------------------------------------------------------------------------------- /src/http/http_version.f90: -------------------------------------------------------------------------------- 1 | 2 | !!> This file provides details about the **`version number`** of the package. 3 | 4 | module http_version 5 | 6 | !!> This module store information regarding **`version`** 7 | !!> number of package 8 | 9 | integer, parameter, public :: VERSION_MAJOR = 0 10 | !! major version number 11 | integer, parameter, public :: VERSION_MINOR = 1 12 | !! minor version number 13 | character(len=*), parameter, public :: VERSION_STRING = achar(VERSION_MAJOR + 48) // '.' // achar(VERSION_MINOR + 48) 14 | !! string representation of version number 15 | 16 | end module http_version -------------------------------------------------------------------------------- /tutorial/example-project/github-org-analyzer/fpm.toml: -------------------------------------------------------------------------------- 1 | name = "github-org-analyzer" 2 | version = "0.1.0" 3 | license = "license" 4 | author = "Rajkumar" 5 | maintainer = "rajkumardongre17@gmail.com" 6 | copyright = "Copyright 2023, Rajkumar" 7 | [build] 8 | auto-executables = true 9 | auto-tests = true 10 | auto-examples = true 11 | module-naming = false 12 | [install] 13 | library = false 14 | [fortran] 15 | implicit-typing = false 16 | implicit-external = false 17 | source-form = "free" 18 | 19 | [dependencies] 20 | http.git = "https://github.com/fortran-lang/http-client.git" 21 | stdlib = "*" 22 | json-fortran = { git = "https://github.com/jacobwilliams/json-fortran.git" } 23 | 24 | -------------------------------------------------------------------------------- /example/basic_auth.f90: -------------------------------------------------------------------------------- 1 | program basic_auth 2 | ! Making request with HTTP Basic Auth 3 | use http, only: response_type, request, pair_type 4 | implicit none 5 | type(response_type) :: response 6 | type(pair_type) :: auth 7 | 8 | ! setting username and password 9 | auth = pair_type('user', 'passwd') 10 | 11 | response = request(url='https://httpbin.org/basic-auth/user/passwd', auth=auth) 12 | if(.not. response%ok) then 13 | print *,'Error message : ', response%err_msg 14 | else 15 | print *, 'Response Code : ', response%status_code 16 | print *, 'Response Content : ', response%content 17 | end if 18 | 19 | end program basic_auth 20 | -------------------------------------------------------------------------------- /.github/workflows/doc-deployment.yml: -------------------------------------------------------------------------------- 1 | name: doc-deployment 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-and-deploy: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-python@v1 11 | with: 12 | python-version: '3.x' 13 | 14 | - name: Install dependencies 15 | run: pip install -v ford 16 | 17 | - name: Build Documentation 18 | run: ford ford.md 19 | 20 | - uses: JamesIves/github-pages-deploy-action@3.7.1 21 | if: github.event_name == 'push' 22 | with: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | BRANCH: gh-pages 25 | FOLDER: doc 26 | CLEAN: true -------------------------------------------------------------------------------- /src/http.f90: -------------------------------------------------------------------------------- 1 | 2 | !!> This file provides the **High-Level API** for the **`HTTP`** package. 3 | !!> When the `http` module is imported into the project, all these 4 | !!> high-level functionalities become available for use. 5 | 6 | module http 7 | 8 | !!> This Module contains all **High-level API** for the **`HTTP`** package. 9 | !!> When the `http` module is imported into the project, all these 10 | !!> high-level functionalities become available for use. 11 | 12 | use http_request, only: & 13 | HTTP_DELETE, HTTP_GET, HTTP_HEAD, HTTP_PATCH, HTTP_POST, HTTP_PUT 14 | use http_response, only: response_type 15 | use http_client, only: request 16 | use http_pair, only : pair_type 17 | 18 | end module http 19 | -------------------------------------------------------------------------------- /example/get.f90: -------------------------------------------------------------------------------- 1 | program get_request 2 | ! This program demonstrates sending a simple GET request and printing the 3 | ! status, length of the body, method, and the body of the response. 4 | use http, only: response_type, request 5 | implicit none 6 | type(response_type) :: response 7 | 8 | response = request(url='https://httpbin.org/get') 9 | if(.not. response%ok) then 10 | print *,'Error message : ', response%err_msg 11 | else 12 | print *, 'Response Code : ', response%status_code 13 | print *, 'Response Length : ', response%content_length 14 | print *, 'Response Method : ', response%method 15 | print *, 'Response Content : ', response%content 16 | end if 17 | 18 | end program get_request 19 | -------------------------------------------------------------------------------- /ford.md: -------------------------------------------------------------------------------- 1 | --- 2 | project: HTTP 3 | src_dir: ./src 4 | output_dir: ./doc 5 | project_github: https://github.com/fortran-lang/http-client 6 | summary: The http Fortran package provides a simple and convenient way to make HTTP requests and retrieve responses. It aims to simplify the process of interacting with web services by providing a high-level API. 7 | author: Rajkumar Dongre 8 | github: https://github.com/rajkumardongre 9 | email: rajkumardongre17@gmail.com 10 | twitter: https://twitter.com/ATOM12060827 11 | author_description: Just Love to build Things 🛠️ 12 | graph: true 13 | search: true 14 | display: public 15 | protected 16 | private 17 | source: true 18 | print_creation_date: true 19 | creation_date: %Y-%m-%d %H:%M %z 20 | 21 | --- 22 | 23 | 24 | {!README.md!} 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/timeout.f90: -------------------------------------------------------------------------------- 1 | program timeout 2 | ! This program demonstrates the use of the timeout option. The request below is designed 3 | ! to take more than 10 seconds to complete, but we set the timeout value to 5 seconds. 4 | ! As a result, the request will fail with an error message that says "Timeout was reached". 5 | use http, only: response_type, request 6 | implicit none 7 | type(response_type) :: response 8 | 9 | ! Delay in response for 10 seconds 10 | response = request(url='https://httpbin.org/delay/10', timeout=5) 11 | if(.not. response%ok) then 12 | print *,'Error message : ', response%err_msg 13 | else 14 | print *, 'Response Code : ', response%status_code 15 | print *, 'Response Content : ', response%content 16 | end if 17 | 18 | end program timeout 19 | -------------------------------------------------------------------------------- /example/post_file.f90: -------------------------------------------------------------------------------- 1 | program post_file 2 | 3 | ! This program demonstrates sending File using POST request. 4 | 5 | use http, only : request, response_type, HTTP_POST, pair_type 6 | implicit none 7 | type(response_type) :: response 8 | type(pair_type) :: file_data 9 | 10 | ! pair_type('', '/path/to/file.txt') 11 | file_data = pair_type('file.txt', './example/data/file.txt') 12 | 13 | response = request(url='https://httpbin.org/post', method=HTTP_POST, file=file_data) 14 | 15 | if(.not. response%ok) then 16 | print *,'Error message : ', response%err_msg 17 | else 18 | print *, 'Response Code : ', response%status_code 19 | print *, 'Response Length : ', response%content_length 20 | print *, 'Response Method : ', response%method 21 | print *, 'Response Content : ', response%content 22 | end if 23 | end program post_file -------------------------------------------------------------------------------- /test/test_timeout.f90: -------------------------------------------------------------------------------- 1 | program test_timeout 2 | use iso_fortran_env, only: stderr => error_unit 3 | use http, only : response_type, request 4 | 5 | implicit none 6 | type(response_type) :: res 7 | character(:), allocatable :: msg 8 | logical :: ok = .true. 9 | 10 | 11 | res = request(url='https://httpbin.org/delay/10', timeout=1) 12 | 13 | msg = 'test_timeout: ' 14 | 15 | if(res%err_msg /= 'Timeout was reached') then 16 | ok = .false. 17 | print '(a)', 'Failed : Timeout not reached' 18 | end if 19 | 20 | ! Status Code Validation 21 | if (res%status_code /= 0) then 22 | ok = .false. 23 | print '(a)', 'Failed : Status Code Validation' 24 | end if 25 | 26 | if (.not. ok) then 27 | msg = msg // 'Test Case Failed' 28 | write(stderr, '(a)'), msg 29 | error stop 1 30 | else 31 | msg = msg // 'All tests passed.' 32 | print '(a)', msg 33 | end if 34 | end program test_timeout 35 | -------------------------------------------------------------------------------- /example/post_form_data.f90: -------------------------------------------------------------------------------- 1 | program post_form_data 2 | ! This program demonstrates sending Form data using POST request and printing 3 | ! the status, length of the body, method, and the body of the response. 4 | use http, only: response_type, request, HTTP_POST, pair_type 5 | implicit none 6 | type(response_type) :: response 7 | type(pair_type), allocatable :: form_data(:) 8 | 9 | ! Storing form data in a array of pair_type object, each pair_type object 10 | ! represent a single form field 11 | form_data = [pair_type('param1', 'value1'), pair_type('param2', 'value2')] 12 | 13 | response = request(url='https://httpbin.org/post', method=HTTP_POST, form=form_data) 14 | 15 | if(.not. response%ok) then 16 | print *,'Error message : ', response%err_msg 17 | else 18 | print *, 'Response Code : ', response%status_code 19 | print *, 'Response Length : ', response%content_length 20 | print *, 'Response Method : ', response%method 21 | print *, 'Response Content : ', response%content 22 | end if 23 | 24 | end program post_form_data 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 The Fortran Programming Language 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ${{ matrix.os }} 13 | 14 | strategy: 15 | matrix: 16 | # os: [ubuntu-latest, macos-latest] 17 | os: [ubuntu-latest] 18 | gcc_v: [11, 12] 19 | 20 | env: 21 | FPM_FC: gfortran-${{ matrix.gcc_v }} 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Set up Fortran Package Manager 27 | uses: fortran-lang/setup-fpm@v5 28 | with: 29 | github-token: ${{ secrets.GITHUB_TOKEN }} 30 | 31 | - name: Install curl development headers on Ubuntu 32 | if: contains( matrix.os, 'ubuntu' ) 33 | run: sudo apt install -y libcurl4-openssl-dev 34 | 35 | - name: Install GCC compilers on macOS 36 | if: contains( matrix.os, 'macos') 37 | run: | 38 | brew install gcc@${{ matrix.gcc_v }} || brew upgrade gcc@${{ matrix.gcc_v }} || true 39 | brew link gcc@${{ matrix.gcc_v }} 40 | 41 | - name: Build with GCC 42 | run: fpm build 43 | 44 | - name: Test with GCC 45 | run: fpm test 46 | -------------------------------------------------------------------------------- /example/post.f90: -------------------------------------------------------------------------------- 1 | program post_request 2 | ! This program demonstrates sending JSON data using POST request and printing the 3 | ! status, length of the body, method, and the body of the response. 4 | use http, only: response_type, request, HTTP_POST, pair_type 5 | implicit none 6 | type(response_type) :: response 7 | character(:), allocatable :: json_data 8 | type(pair_type), allocatable :: req_header(:) 9 | 10 | ! Storing request header in array of pair_type object 11 | req_header = [pair_type('Content-Type', 'application/json')] 12 | 13 | ! JSON data we want to send 14 | json_data = '{"name":"Jhon","role":"developer"}' 15 | 16 | response = request(url='https://httpbin.org/post', method=HTTP_POST, data=json_data, header=req_header) 17 | 18 | if(.not. response%ok) then 19 | print *,'Error message : ', response%err_msg 20 | else 21 | print *, 'Response Code : ', response%status_code 22 | print *, 'Response Length : ', response%content_length 23 | print *, 'Response Method : ', response%method 24 | print *, 'Response Content : ', response%content 25 | end if 26 | 27 | end program post_request 28 | -------------------------------------------------------------------------------- /test/test_head.f90: -------------------------------------------------------------------------------- 1 | program test_head 2 | use iso_fortran_env, only: stderr => error_unit 3 | use http, only : response_type, request, HTTP_HEAD 4 | 5 | implicit none 6 | type(response_type) :: res 7 | character(:), allocatable :: msg 8 | logical :: ok = .true. 9 | 10 | res = request(url='https://www.w3schools.com/python/demopage.php', method=HTTP_HEAD) 11 | 12 | msg = 'test_head: ' 13 | 14 | if (.not. res%ok) then 15 | ok = .false. 16 | msg = msg // res%err_msg 17 | write(stderr, '(a)') msg 18 | error stop 1 19 | end if 20 | 21 | ! Status Code Validation 22 | if (res%status_code /= 200) then 23 | ok = .false. 24 | print '(a)', 'Failed : Status Code Validation' 25 | end if 26 | 27 | ! Header Size Validation 28 | ! if (size(res%header) /= 13) then 29 | ! ok = .false. 30 | ! print '(a)', 'Failed : Header Size Validation' 31 | ! end if 32 | 33 | if (.not. ok) then 34 | msg = msg // 'Test Case Failed' 35 | write(stderr, '(a)'), msg 36 | error stop 1 37 | else 38 | msg = msg // 'All tests passed.' 39 | print '(a)', msg 40 | end if 41 | end program test_head 42 | -------------------------------------------------------------------------------- /test/test_auth.f90: -------------------------------------------------------------------------------- 1 | program test_auth 2 | use iso_fortran_env, only: stderr => error_unit 3 | use http, only : response_type, request, pair_type 4 | 5 | implicit none 6 | type(response_type) :: res 7 | character(:), allocatable :: msg 8 | logical :: ok = .true. 9 | type(pair_type) :: auth 10 | 11 | ! setting username and password 12 | auth = pair_type('postman', 'password') 13 | res = request(url='https://postman-echo.com/basic-auth', auth=auth) 14 | 15 | msg = 'test_auth: ' 16 | 17 | if (.not. res%ok) then 18 | ok = .false. 19 | msg = msg // res%err_msg 20 | write(stderr, '(a)') msg 21 | error stop 1 22 | end if 23 | 24 | ! Status Code Validation 25 | if (res%status_code /= 200) then 26 | ok = .false. 27 | print '(a)', 'Failed : Status Code Validation' 28 | end if 29 | 30 | ! Content Length Validation 31 | if (res%content_length /= 27 .or. & 32 | len(res%content) /= 27) then 33 | ok = .false. 34 | print '(a)', 'Failed : Content Length Validation' 35 | end if 36 | 37 | if (.not. ok) then 38 | msg = msg // 'Test Case Failed' 39 | write(stderr, '(a)'), msg 40 | error stop 1 41 | else 42 | msg = msg // 'All tests passed.' 43 | print '(a)', msg 44 | end if 45 | end program test_auth 46 | -------------------------------------------------------------------------------- /tutorial/example-project/github-org-analyzer/src/utils.f90: -------------------------------------------------------------------------------- 1 | ! utils.f90 - Helper functions module 2 | 3 | module utils 4 | implicit none 5 | 6 | ! Declare the function as public to make it accessible to other modules 7 | public :: int_to_str 8 | 9 | contains 10 | 11 | ! Function: int_to_str 12 | ! Converts an integer to a string 13 | ! Inputs: 14 | ! int - The integer to convert 15 | ! Returns: 16 | ! str - The string representation of the input integer 17 | function int_to_str(int) result(str) 18 | integer, intent(in) :: int 19 | character(:), allocatable :: str 20 | integer :: j, temp, rem 21 | integer, allocatable :: nums(:) 22 | 23 | ! Initialize variables 24 | temp = int 25 | str = '' 26 | 27 | ! Convert the integer to its string representation 28 | do while (temp > 9) 29 | rem = mod(temp, 10) 30 | temp = temp / 10 31 | if (allocated(nums)) then 32 | nums = [rem, nums] 33 | else 34 | nums = [rem] 35 | end if 36 | end do 37 | 38 | ! Add the last digit to the string 39 | if (allocated(nums)) then 40 | nums = [temp, nums] 41 | else 42 | nums = [temp] 43 | end if 44 | 45 | ! Convert the individual digits to characters and concatenate them 46 | do j = 1, size(nums) 47 | str = str // achar(nums(j) + 48) 48 | end do 49 | 50 | ! Deallocate the temporary array 51 | deallocate(nums) 52 | end function int_to_str 53 | 54 | end module utils 55 | -------------------------------------------------------------------------------- /example/response_header.f90: -------------------------------------------------------------------------------- 1 | program response_header 2 | ! This program demonstrates sending user-provided headers in a GET request 3 | ! and iterating over the headers of the response sent back by the server. 4 | use stdlib_string_type, only: string_type, write(formatted) 5 | use http, only: response_type, request, pair_type 6 | 7 | implicit none 8 | type(response_type) :: response 9 | type(pair_type), allocatable :: header(:), req_header(:) 10 | character(:), allocatable :: val 11 | integer :: i = 0 12 | 13 | ! Storing request header in array of pair_type object, where each pair_type 14 | ! object represents a single header. (in header-name,header-value format) 15 | req_header = [ & 16 | pair_type('Another-One', 'Hello'), & 17 | pair_type('Set-Cookie', 'Theme-Light'), & 18 | pair_type('Set-Cookie', 'Auth-Token: 12345'), & 19 | pair_type('User-Agent', 'my user agent') & 20 | ] 21 | 22 | response = request(url='https://httpbin.org/get', header=req_header) 23 | 24 | if (.not. response%ok) then 25 | print *,'Error message : ', response%err_msg 26 | else 27 | print*, '::::::::::::::::: Request send by us :::::::::::::::::' 28 | print *, response%content 29 | print*, '::::::::::::::::: Fetching all response header :::::::::::::::::' 30 | header = response%header 31 | ! Iterate over response headers. 32 | do i = 1, size(header) 33 | print *, header(i)%name, ': ', header(i)%value 34 | end do 35 | 36 | ! getting header value by header name 37 | print*, '::::::::::::::::: Fetching individual response header by name :::::::::::::::::' 38 | print *, 'Content-Type: ', response%header_value('Content-Type') 39 | end if 40 | end program response_header 41 | -------------------------------------------------------------------------------- /test/test_post.f90: -------------------------------------------------------------------------------- 1 | program test_post 2 | use iso_fortran_env, only: stderr => error_unit 3 | use http, only: request, pair_type, HTTP_POST, response_type 4 | implicit none 5 | type(response_type) :: res 6 | character(:), allocatable :: json_data, original_content, msg 7 | type(pair_type), allocatable :: req_header(:) 8 | logical :: ok = .true. 9 | 10 | original_content = '{"id":101,"title":"BMW","description":"A luxurious and high-performance vehicle"}' 11 | req_header = [pair_type('Content-Type', 'application/json')] 12 | 13 | json_data = '{"title":"BMW","description":"A luxurious and high-performance vehicle"}' 14 | 15 | res = request(url='https://dummyjson.com/products/add', method=HTTP_POST, data=json_data, header=req_header) 16 | 17 | msg = 'test_post: ' 18 | 19 | if (.not. res%ok) then 20 | ok = .false. 21 | msg = msg // res%err_msg 22 | write(stderr, '(a)') msg 23 | error stop 1 24 | end if 25 | 26 | ! Status Code Validation 27 | if (res%status_code /= 200) then 28 | ok = .false. 29 | print '(a)', 'Failed : Status Code Validation' 30 | end if 31 | 32 | ! Content Length Validation 33 | if (res%content_length /= len(original_content) .or. & 34 | len(res%content) /= len(original_content)) then 35 | ok = .false. 36 | print '(a)', 'Failed : Content Length Validation' 37 | end if 38 | 39 | ! Content Validation 40 | if (res%content /= original_content) then 41 | ok = .false. 42 | print '(a)', 'Failed : Content Validation' 43 | end if 44 | 45 | if (.not. ok) then 46 | msg = msg // 'Test Case Failed' 47 | write(stderr, '(a)'), msg 48 | error stop 1 49 | else 50 | msg = msg // 'All tests passed.' 51 | print '(a)', msg 52 | end if 53 | 54 | end program test_post -------------------------------------------------------------------------------- /src/http/http_request.f90: -------------------------------------------------------------------------------- 1 | 2 | !!> This file defines the **`request_type`** derived type, which 3 | !!> represents an **HTTP request**. 4 | 5 | module http_request 6 | 7 | !!> This module defines the **`request_type`** derived type, which 8 | !!> represents an **HTTP request**. 9 | 10 | use iso_fortran_env, only: int64 11 | use http_pair, only: pair_type 12 | use stdlib_string_type, only: string_type, to_lower, operator(==), char 13 | 14 | implicit none 15 | 16 | private 17 | public :: HTTP_DELETE, HTTP_GET, HTTP_HEAD, HTTP_PATCH, HTTP_POST, HTTP_PUT 18 | public :: request_type 19 | 20 | ! HTTP methods: 21 | integer, parameter :: HTTP_GET = 1 22 | integer, parameter :: HTTP_HEAD = 2 23 | integer, parameter :: HTTP_POST = 3 24 | integer, parameter :: HTTP_PUT = 4 25 | integer, parameter :: HTTP_DELETE = 5 26 | integer, parameter :: HTTP_PATCH = 6 27 | 28 | ! Request Type 29 | type :: request_type 30 | !! Representing an **HTTP `request`**. 31 | character(len=:), allocatable :: url 32 | !! The URL of the request 33 | character(len=:), allocatable :: data 34 | !! The data to be send with request 35 | character(len=:), allocatable :: form_encoded_str 36 | !! The URL-encoded form data. 37 | integer :: method 38 | !! The HTTP method of the request. 39 | type(pair_type), allocatable :: header(:) 40 | !! An Array of request headers. 41 | type(pair_type), allocatable :: form(:) 42 | !! An array of fields in an HTTP form. 43 | type(pair_type), allocatable :: file 44 | !! Used to store information about files to be sent in HTTP requests. 45 | integer(kind=int64) :: timeout 46 | !! **`Timeout`** value for the request in **seconds**. 47 | type(pair_type), allocatable :: auth 48 | !! Stores the username and password for Authentication 49 | end type request_type 50 | end module http_request 51 | -------------------------------------------------------------------------------- /test/test_delete.f90: -------------------------------------------------------------------------------- 1 | program test_delete 2 | use iso_fortran_env, only: stderr => error_unit 3 | use http, only: request, pair_type, HTTP_DELETE, response_type 4 | implicit none 5 | type(response_type) :: res 6 | character(:), allocatable :: original_content, msg 7 | logical :: ok = .true. 8 | 9 | original_content = '{"id":1,"title":"iPhone 9","description":"An apple mobile which is nothing like & 10 | apple","price":549,"discountPercentage":12.96,"rating":4.69,"stock":94,"brand":"Apple","category":& 11 | "smartphones","thumbnail":"https://i.dummyjson.com/data/products/1/thumbnail.jpg","images":& 12 | ["https://i.dummyjson.com/data/products/1/1.jpg","https://i.dummyjson.com/data/products/1/2.jpg",& 13 | "https://i.dummyjson.com/data/products/1/3.jpg","https://i.dummyjson.com/data/products/1/4.jpg",& 14 | "https://i.dummyjson.com/data/products/1/thumbnail.jpg"],"isDeleted":true' 15 | 16 | res = request(url='https://dummyjson.com/products/1', method=HTTP_DELETE) 17 | 18 | msg = 'test_delete: ' 19 | 20 | if (.not. res%ok) then 21 | ok = .false. 22 | msg = msg // res%err_msg 23 | write(stderr, '(a)') msg 24 | error stop 1 25 | end if 26 | 27 | ! Status Code Validation 28 | if (res%status_code /= 200) then 29 | ok = .false. 30 | print '(a)', 'Failed : Status Code Validation' 31 | end if 32 | 33 | ! Content Length Validation 34 | if (res%content_length /= (len(original_content)+40) .or. & 35 | len(res%content) /= (len(original_content)+40)) then 36 | ok = .false. 37 | print '(a)', 'Failed : Content Length Validation' 38 | end if 39 | 40 | ! Content Validation 41 | if (res%content(1:535) /= original_content) then 42 | ok = .false. 43 | print '(a)', 'Failed : Content Validation' 44 | end if 45 | 46 | if (.not. ok) then 47 | msg = msg // 'Test Case Failed' 48 | write(stderr, '(a)'), msg 49 | error stop 1 50 | else 51 | msg = msg // 'All tests passed.' 52 | print '(a)', msg 53 | end if 54 | 55 | end program test_delete -------------------------------------------------------------------------------- /test/test_header.f90: -------------------------------------------------------------------------------- 1 | program test_header 2 | 3 | use iso_fortran_env, only: stderr => error_unit 4 | use http_pair, only: get_pair_value, pair_has_name, pair_type 5 | 6 | implicit none 7 | type(pair_type), allocatable :: header(:) 8 | logical :: ok = .true. 9 | integer :: n 10 | 11 | header = [ & 12 | pair_type('One', '1'), & 13 | pair_type('Two', '2') & 14 | ] 15 | 16 | if (.not. size(header) == 2) then 17 | ok = .false. 18 | write(stderr, '(a)') 'Failed: Header size is incorrect.' 19 | end if 20 | 21 | if (.not. header(1)%value == '1') then 22 | ok = .false. 23 | write(stderr, '(a)') 'Failed: First header value is incorrect.' 24 | end if 25 | 26 | if (.not. header(2)%value == '2') then 27 | ok = .false. 28 | write(stderr, '(a)') 'Failed: Second header value is incorrect.' 29 | end if 30 | 31 | header = [header, pair_type('Three', '3')] 32 | 33 | if (.not. size(header) == 3) then 34 | ok = .false. 35 | write(stderr, '(a)') 'Failed: Appending to header failed.' 36 | end if 37 | 38 | if (.not. header(3)%value == '3') then 39 | ok = .false. 40 | write(stderr, '(a)') 'Failed: Appended header value is incorrect.' 41 | end if 42 | 43 | do n = 1, size(header) 44 | if (.not. get_pair_value(header, header(n)%name) == header(n)%value) then 45 | ok = .false. 46 | write(stderr, '(a)') 'Failed: Appended header value is incorrect.' 47 | end if 48 | end do 49 | 50 | do n = 1, size(header) 51 | if (.not. pair_has_name(header, header(n)%name)) then 52 | ok = .false. 53 | write(stderr, '(a)') 'Failed: Incorrect output from pair_has_name.' 54 | end if 55 | end do 56 | 57 | if (pair_has_name(header, "Non-Existent")) then 58 | ok = .false. 59 | write(stderr, '(a)') 'Failed: Incorrect output from pair_has_name for non-existent key.' 60 | end if 61 | 62 | if (.not. ok) then 63 | write(stderr, '(a)'), 'test_header: One or more tests failed.' 64 | error stop 1 65 | else 66 | print '(a)', 'test_header: All tests passed.' 67 | end if 68 | 69 | end program test_header -------------------------------------------------------------------------------- /test/test_put.f90: -------------------------------------------------------------------------------- 1 | program test_put 2 | use iso_fortran_env, only: stderr => error_unit 3 | use http, only: request, pair_type, HTTP_PUT, response_type 4 | implicit none 5 | type(response_type) :: res 6 | character(:), allocatable :: json_data, original_content, msg 7 | type(pair_type), allocatable :: req_header(:) 8 | logical :: ok = .true. 9 | 10 | original_content = '{"id":1,"title":"iPhone Galaxy +1","price":549,"stock":94,"rating":4.69,& 11 | "images":["https://i.dummyjson.com/data/products/1/1.jpg","https://i.dummyjson.com/data/products/1/2.jpg"& 12 | ,"https://i.dummyjson.com/data/products/1/3.jpg","https://i.dummyjson.com/data/products/1/4.jpg",& 13 | "https://i.dummyjson.com/data/products/1/thumbnail.jpg"],"thumbnail":"https://i.dummyjson.com/data/products/1/thumbnail.jpg",& 14 | "description":"An apple mobile which is nothing like apple","brand":"Apple","category":"smartphones"}' 15 | 16 | req_header = [pair_type('Content-Type', 'application/json')] 17 | 18 | json_data = '{"title":"iPhone Galaxy +1"}' 19 | 20 | res = request(url='https://dummyjson.com/products/1', method=HTTP_PUT, data=json_data, header=req_header) 21 | 22 | msg = 'test_put: ' 23 | 24 | if (.not. res%ok) then 25 | ok = .false. 26 | msg = msg // res%err_msg 27 | write(stderr, '(a)') msg 28 | error stop 1 29 | end if 30 | 31 | ! Status Code Validation 32 | if (res%status_code /= 200) then 33 | ok = .false. 34 | print '(a)', 'Failed : Status Code Validation' 35 | end if 36 | 37 | ! Content Length Validation 38 | if (res%content_length /= len(original_content) .or. & 39 | len(res%content) /= len(original_content)) then 40 | ok = .false. 41 | print '(a)', 'Failed : Content Length Validation' 42 | end if 43 | 44 | ! Content Validation 45 | if (res%content /= original_content) then 46 | ok = .false. 47 | print '(a)', 'Failed : Content Validation' 48 | end if 49 | 50 | if (.not. ok) then 51 | msg = msg // 'Test Case Failed' 52 | write(stderr, '(a)'), msg 53 | error stop 1 54 | else 55 | msg = msg // 'All tests passed.' 56 | print '(a)', msg 57 | end if 58 | 59 | end program test_put -------------------------------------------------------------------------------- /test/test_patch.f90: -------------------------------------------------------------------------------- 1 | program test_patch 2 | use iso_fortran_env, only: stderr => error_unit 3 | use http, only: request, pair_type, HTTP_PATCH, response_type 4 | implicit none 5 | type(response_type) :: res 6 | character(:), allocatable :: json_data, original_content, msg 7 | type(pair_type), allocatable :: req_header(:) 8 | logical :: ok = .true. 9 | 10 | original_content = '{"id":1,"title":"iPhone Galaxy +1","price":549,"stock":94,"rating":4.69,& 11 | "images":["https://i.dummyjson.com/data/products/1/1.jpg","https://i.dummyjson.com/data/products/1/2.jpg"& 12 | ,"https://i.dummyjson.com/data/products/1/3.jpg","https://i.dummyjson.com/data/products/1/4.jpg",& 13 | "https://i.dummyjson.com/data/products/1/thumbnail.jpg"],"thumbnail":"https://i.dummyjson.com/data/products/1/thumbnail.jpg",& 14 | "description":"An apple mobile which is nothing like apple","brand":"Apple","category":"smartphones"}' 15 | 16 | req_header = [pair_type('Content-Type', 'application/json')] 17 | 18 | json_data = '{"title":"iPhone Galaxy +1"}' 19 | 20 | res = request(url='https://dummyjson.com/products/1', method=HTTP_PATCH, data=json_data, header=req_header) 21 | 22 | msg = 'test_patch: ' 23 | 24 | if (.not. res%ok) then 25 | ok = .false. 26 | msg = msg // res%err_msg 27 | write(stderr, '(a)') msg 28 | error stop 1 29 | end if 30 | 31 | ! Status Code Validation 32 | if (res%status_code /= 200) then 33 | ok = .false. 34 | print '(a)', 'Failed : Status Code Validation' 35 | end if 36 | 37 | ! Content Length Validation 38 | if (res%content_length /= len(original_content) .or. & 39 | len(res%content) /= len(original_content)) then 40 | ok = .false. 41 | print '(a)', 'Failed : Content Length Validation' 42 | end if 43 | 44 | ! Content Validation 45 | if (res%content /= original_content) then 46 | ok = .false. 47 | print '(a)', 'Failed : Content Validation' 48 | end if 49 | 50 | if (.not. ok) then 51 | msg = msg // 'Test Case Failed' 52 | write(stderr, '(a)'), msg 53 | error stop 1 54 | else 55 | msg = msg // 'All tests passed.' 56 | print '(a)', msg 57 | end if 58 | 59 | end program test_patch -------------------------------------------------------------------------------- /tutorial/example-project/github-org-analyzer/app/main.f90: -------------------------------------------------------------------------------- 1 | program main 2 | ! This Fortran project, named "GitHub Organization Analyzer," demonstrates the usage of the 3 | ! http-client module to make HTTP requests and interact with the GitHub API. The project 4 | ! provides a set of subroutines that allow users to fetch and display information about GitHub 5 | ! repositories and contributors within a specified GitHub organization. 6 | 7 | use github_org_analyzer, only: analyze_github_organization, get_top_active_repositories, & 8 | get_top_starred_repositories, get_most_used_repositories, get_contributor_from_repositories 9 | 10 | implicit none 11 | 12 | ! Fetching and displaying information about all repositories within the GitHub organization 13 | ! 'fortran-lang' using the analyze_github_organization subroutine. 14 | print *, '::::::: All Repositories :::::::'//new_line('a') 15 | call analyze_github_organization(org_name='fortran-lang') 16 | 17 | ! Fetching and displaying detailed information about the top active repositories within the 18 | ! organization using the get_top_active_repositories subroutine. 19 | print *, '::::::: Top Active Repositories :::::::'//new_line('a') 20 | call get_top_active_repositories(org_name='fortran-lang') 21 | 22 | ! Fetching and displaying detailed information about the top starred repositories within the 23 | ! organization using the get_top_starred_repositories subroutine. 24 | print *, '::::::: Top Starred Repositories :::::::'//new_line('a') 25 | call get_top_starred_repositories(org_name='fortran-lang') 26 | 27 | ! Fetching and displaying detailed information about the most used repositories within the 28 | ! organization (based on the number of forks) using the get_most_used_repositories subroutine. 29 | print *, '::::::: Top Used Repositories :::::::'//new_line('a') 30 | call get_most_used_repositories(org_name='fortran-lang') 31 | 32 | ! Fetching and displaying detailed information about contributors from the repository 'stdlib' 33 | ! within the organization using the get_contributor_from_repositories subroutine. 34 | print *, '::::::: Contributors from a Repositories :::::::'//new_line('a') 35 | call get_contributor_from_repositories(org_name='fortran-lang', repo_name='stdlib') 36 | 37 | end program main 38 | -------------------------------------------------------------------------------- /src/http/http_response.f90: -------------------------------------------------------------------------------- 1 | 2 | !!> This file defines the **`response_type`** derived type, which 3 | !!> represents an **HTTP response** from a web server. 4 | 5 | module http_response 6 | 7 | !!> This module defines the **`response_type`** derived type, which 8 | !!> represents an **HTTP response** from a web server. 9 | 10 | use, intrinsic :: iso_fortran_env, only: int64 11 | use http_pair, only: pair_type, get_pair_value 12 | use stdlib_string_type, only: string_type, to_lower, operator(==), char 13 | 14 | implicit none 15 | 16 | private 17 | public :: response_type 18 | 19 | ! Response Type 20 | type :: response_type 21 | !!> Representing an **HTTP `response`**. 22 | character(len=:), allocatable :: url 23 | !! The URL of the request 24 | character(len=:), allocatable :: content 25 | !! The content of the response. 26 | character(len=:), allocatable :: method 27 | !! The HTTP method of the request. 28 | character(len=:), allocatable :: err_msg 29 | !! The Error message if the response was not successful. 30 | integer :: status_code = 0 31 | !! The HTTP status code of the response 32 | integer(kind=int64) :: content_length = 0 33 | !! length of the response content. 34 | logical :: ok = .true. 35 | !! true if the response was successful else false. 36 | type(pair_type), allocatable :: header(:) 37 | !! An Array of response headers. 38 | contains 39 | procedure :: header_value 40 | end type response_type 41 | 42 | contains 43 | 44 | pure function header_value(this, name) result(val) 45 | 46 | !!> This function is used to retrieve the `value` of a response header. 47 | !!> It takes the response header `name` as input and returns the corresponding 48 | !!> **header value**. 49 | 50 | class(response_type), intent(in) :: this 51 | !! An object representing the HTTP response. 52 | character(*), intent(in) :: name 53 | !! This refers to the name of the header for which we want to retrieve the value. 54 | character(:), allocatable :: val 55 | !! This denotes the value of the specified header name. 56 | 57 | val = get_pair_value(this%header, name) 58 | end function header_value 59 | 60 | end module http_response 61 | -------------------------------------------------------------------------------- /tutorial/example-project/github-org-analyzer/README.md: -------------------------------------------------------------------------------- 1 | # github-org-analyzer 2 | 3 | This Fortran package provides procedures to analyze GitHub organizations and retrieve valuable information about their repositories. By leveraging the power of the `http-client` package, this analyzer fetches data from the GitHub API to generate insightful reports. 4 | 5 | ## Features 6 | 7 | The `github-org-analyzer` package offers the following procedures: 8 | 9 | * `analyze_github_organization`: Provides detailed information about all repositories within the organization. 10 | * `get_top_active_repositories`: Retrieves information about the top active repositories within the organization. 11 | * `get_top_starred_repositories`: Retrieves information about the top starred repositories within the organization. 12 | * `get_most_used_repositories`: Retrieves information about the most used repositories within the organization. 13 | * `get_contributor_from_repositories`: Retrieves information about the contributors of the organization's repositories. 14 | 15 | ## Prerequisites 16 | 17 | To use the `github-org-analyzer` package, ensure you have the following dependencies installed: 18 | 19 | * Fortran compiler 20 | * On Ubuntu, you need to install the curl development headers. Use the following command: 21 | ```bash 22 | sudo apt install -y libcurl4-openssl-dev 23 | ``` 24 | 25 | ## Installation 26 | 27 | 1. Clone the repository: 28 | 29 | ```bash 30 | git clone https://github.com/your-username/github-org-analyzer.git 31 | ``` 32 | 33 | 2. Compile the package using your fpm: 34 | 35 | ```bash 36 | cd github-org-analyzer 37 | fpm build 38 | fpm run 39 | ``` 40 | 41 | ## Usage 42 | 43 | To use the procedures provided by `github-org-analyzer`, follow these steps: 44 | 45 | 1. Import the package into your Fortran project: 46 | 47 | ```fortran 48 | use github_org_analyzer 49 | ``` 50 | 51 | 2. Call the desired procedures to retrieve information about GitHub organizations and their repositories. 52 | 53 | ```fortran 54 | ! Analyze GitHub organization 55 | call analyze_github_organization("your-github-org") 56 | 57 | ! Get information about top active repositories 58 | call get_top_active_repositories("your-github-org") 59 | 60 | ! Get information about top starred repositories 61 | call get_top_starred_repositories("your-github-org") 62 | 63 | ! Get information about most used repositories 64 | call get_most_used_repositories("your-github-org") 65 | 66 | ! Get information about contributors from repositories 67 | call get_contributor_from_repositories("your-github-org", "org-repository") 68 | ``` 69 | 70 | Happy analyzing! -------------------------------------------------------------------------------- /test/test_get.f90: -------------------------------------------------------------------------------- 1 | program test_get 2 | use iso_fortran_env, only: stderr => error_unit 3 | use http, only : response_type, request, pair_type 4 | 5 | implicit none 6 | type(response_type) :: res 7 | character(:), allocatable :: msg, original_content 8 | logical :: ok = .true. 9 | type(pair_type), allocatable :: request_header(:) 10 | integer :: i 11 | 12 | original_content = '{"id":1,"title":"iPhone 9","description":"An apple mobile which is nothing like & 13 | apple","price":549,"discountPercentage":12.96,"rating":4.69,"stock":94,"brand":"Apple","category":& 14 | "smartphones","thumbnail":"https://i.dummyjson.com/data/products/1/thumbnail.jpg","images":& 15 | ["https://i.dummyjson.com/data/products/1/1.jpg","https://i.dummyjson.com/data/products/1/2.jpg",& 16 | "https://i.dummyjson.com/data/products/1/3.jpg","https://i.dummyjson.com/data/products/1/4.jpg",& 17 | "https://i.dummyjson.com/data/products/1/thumbnail.jpg"]}' 18 | 19 | ! setting request header 20 | request_header = [ & 21 | pair_type('Another-One', 'Hello'), & 22 | pair_type('Set-Cookie', 'Theme-Light'), & 23 | pair_type('Set-Cookie', 'Auth-Token: 12345'), & 24 | pair_type('User-Agent', 'my user agent') & 25 | ] 26 | 27 | res = request(url='https://dummyjson.com/products/1', header=request_header) 28 | 29 | msg = 'test_get: ' 30 | 31 | if (.not. res%ok) then 32 | ok = .false. 33 | msg = msg // res%err_msg 34 | write(stderr, '(a)') msg 35 | error stop 1 36 | end if 37 | 38 | ! Status Code Validation 39 | if (res%status_code /= 200) then 40 | ok = .false. 41 | print '(a)', 'Failed : Status Code Validation' 42 | end if 43 | 44 | ! Content Length Validation 45 | if (res%content_length /= len(original_content) .or. & 46 | len(res%content) /= len(original_content)) then 47 | ok = .false. 48 | print '(a)', 'Failed : Content Length Validation' 49 | end if 50 | 51 | ! Content Validation 52 | if (res%content /= original_content) then 53 | ok = .false. 54 | print '(a)', 'Failed : Content Validation' 55 | end if 56 | 57 | ! Header Size Validation 58 | if (size(res%header) /= 16) then 59 | ok = .false. 60 | print '(a)', 'Failed : Header Size Validation' 61 | end if 62 | 63 | ! Header Value Validation 64 | if (res%header_value('content-type') /= 'application/json; charset=utf-8') then 65 | ok = .false. 66 | print '(a)', 'Failed : Header Value Validation' 67 | end if 68 | 69 | if (.not. ok) then 70 | msg = msg // 'Test Case Failed' 71 | write(stderr, '(a)'), msg 72 | error stop 1 73 | else 74 | msg = msg // 'All tests passed.' 75 | print '(a)', msg 76 | end if 77 | end program test_get 78 | -------------------------------------------------------------------------------- /src/http/http_pair.f90: -------------------------------------------------------------------------------- 1 | 2 | !!> This file contains the **`pair_type`** derived type, designed to 3 | !!> store various details like `headers`, `file` information, `form-data`, 4 | !!> and `authentication` details. 5 | 6 | module http_pair 7 | 8 | !!> This module contains the **`pair_type`** derived type, designed to 9 | !!> store various details like `headers`, `file` information, `form-data`, 10 | !!> and `authentication` details. 11 | 12 | use stdlib_ascii, only: to_lower 13 | 14 | implicit none 15 | private 16 | 17 | public :: pair_type 18 | public :: append_pair 19 | public :: get_pair_value 20 | public :: pair_has_name 21 | 22 | type :: pair_type 23 | !!> A derived type use to store a **name-value pair**. 24 | !!>____ 25 | !!>It is used in many instances like: 26 | !!> 27 | !!>1. Storing request and response `headers`: 28 | !!> - `name` to represent the header name. 29 | !!> - `value` to represent the header value. 30 | !!> 31 | !!>2. Representing fields in a url-encoded **HTTP `form`**: 32 | !!> - `name` to represent the form field name. 33 | !!> - `value` to represent the form field value. 34 | !!> 35 | !!>3. Storing information about the `file` to upload: 36 | !!> - `name` to represent the name of the file. 37 | !!> - `value` to represent the path of the file on the local system. 38 | !!> 39 | !!>4. Storing authentication detail, require to authenticate the request. 40 | !!> - `name` to represent the **username** 41 | !!> - `value` to represent the **password** 42 | 43 | character(:), allocatable :: name 44 | !! Name (key) 45 | character(:), allocatable :: value 46 | !! Value 47 | end type pair_type 48 | 49 | contains 50 | 51 | subroutine append_pair(pair, name, value) 52 | 53 | !!> Appends a new `pair_type` instance with the provided `name` 54 | !!> and `value` into the given `pair_type array`(i.e pair). 55 | 56 | type(pair_type), allocatable, intent(inout) :: pair(:) 57 | !! An array of `pair_type` objects, to which a new instance of `pair_type` needs to be added. 58 | character(*), intent(in) :: name 59 | !! The `name` attribute of the `pair_type` to be added. 60 | character(*), intent(in) :: value 61 | !! The `value` attribute of the `pair_type` to be added. 62 | type(pair_type), allocatable :: temp(:) 63 | integer :: n 64 | 65 | if (allocated(pair)) then 66 | n = size(pair) 67 | allocate(temp(n+1)) 68 | temp(1:n) = pair 69 | temp(n+1) = pair_type(name, value) 70 | call move_alloc(temp, pair) 71 | else 72 | allocate(pair(1)) 73 | pair(1) = pair_type(name, value) 74 | end if 75 | end subroutine append_pair 76 | 77 | pure function get_pair_value(pair_arr, name) result(val) 78 | 79 | !!> The function retrieves the `value` associated with a specified 80 | !!> `name` from the passed array of `pair_type` objects (i.e., pair_arr). 81 | !!> The search for the `name` is **case-insensitive**. If the `name` is 82 | !!> not found, the function returns an **unallocated string**. In the case 83 | !!> of duplicate `name` entries in the `pair_arr`, the function returns the 84 | !!> `value` of the **first occurrence** of the `name`. 85 | 86 | type(pair_type), intent(in) :: pair_arr(:) 87 | !! The array in which we want to find a `pair_type` instance with its 88 | !! `name` attribute equal to the given `name`. 89 | character(*), intent(in) :: name 90 | !! The `name` to be searched in the `pair_arr`. 91 | character(:), allocatable :: val 92 | !! Stores the `value` of the corresponding `pair_type` object whose `name` 93 | !! attribute is equal to the given `name`. 94 | integer :: n 95 | 96 | do n = 1, size(pair_arr) 97 | if (to_lower(name) == to_lower(pair_arr(n)%name)) then 98 | val = pair_arr(n)%value 99 | return 100 | end if 101 | end do 102 | 103 | end function get_pair_value 104 | 105 | pure logical function pair_has_name(pair_arr, name) 106 | !!> Return `.true.` if there exists a `pair_type` object inside `pair_arr` with 107 | !!> a `name` attribute equal to the provided `name`; otherwise, return `.false.`. 108 | !!> HTTP pairs are **case-insensitive**, implying that values are **converted to 109 | !!> lowercase** before the comparison is performed. 110 | 111 | type(pair_type), intent(in) :: pair_arr(:) 112 | !! The array in which we want to find a `pair_type` instance with its 113 | !! `name` attribute equal to the given `name`. 114 | character(*), intent(in) :: name 115 | !! The `name` to be searched in the `pair_arr`. 116 | integer :: n 117 | 118 | pair_has_name = .false. 119 | do n = 1, size(pair_arr) 120 | if (to_lower(name) == to_lower(pair_arr(n)%name)) then 121 | pair_has_name = .true. 122 | return 123 | end if 124 | end do 125 | 126 | end function pair_has_name 127 | 128 | end module http_pair -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # http-client 2 | 3 | http-client is Fortran library to make HTTP requests. 4 | It simplifies interacting with web services by providing a high-level and 5 | user-friendly interface. 6 | 7 | ## Features 8 | 9 | * HTTP request methods: 10 | - `GET`: Retrieve data from the server. 11 | - `POST`: Create new data the server. 12 | - `PUT`: Replace an existing resource on the server. 13 | - `DELETE`: Delete a resource from the server. 14 | - `PATCH`: Partially update a resource on the server. 15 | - `HEAD`: Get response headers without the response content. 16 | * Supported data types: 17 | - URL encoded fields 18 | - HTTP form data 19 | - File uploads 20 | * Response handling: 21 | - Retrieve response body (content). 22 | - Get the HTTP status code returned by the server. 23 | - Access response headers. 24 | * Setting custom request headers 25 | * Error handling with informative error messages 26 | * Setting request timeouts 27 | * Basic HTTP authentication 28 | 29 | ## Installation 30 | 31 | Before building the http-client library, ensure that you have the necessary 32 | dependencies installed. On Ubuntu, you need to install the curl development 33 | headers. Use the following command: 34 | 35 | ``` 36 | sudo apt install -y libcurl4-openssl-dev 37 | ``` 38 | 39 | To use http-client as a dependency in your fpm project, add the following to 40 | your the fpm.toml file of your package: 41 | 42 | ```toml 43 | [dependencies] 44 | http = { git = "https://github.com/fortran-lang/http-client.git" } 45 | stdlib = "*" 46 | ``` 47 | 48 | ## Example use 49 | 50 | The following example demonstrates how to use http-client to make a simple `GET` 51 | request and process the response: 52 | 53 | ```fortran 54 | program simple_get 55 | use http, only : response_type, request 56 | implicit none 57 | type(response_type) :: response 58 | 59 | ! Send a GET request to retrieve JSON data 60 | response = request(url='https://jsonplaceholder.typicode.com/todos/1') 61 | 62 | ! Check if the request was successful 63 | if (.not. response%ok) then 64 | print *, 'Error message:', response%err_msg 65 | else 66 | ! Print the response details 67 | print *, 'Response Code :', response%status_code 68 | print *, 'Response Length :', response%content_length 69 | print *, 'Response Method :', response%method 70 | print *, 'Response Content :', response%content 71 | end if 72 | 73 | end program simple_get 74 | 75 | ``` 76 | 77 | Ouptut: 78 | 79 | ``` 80 | Response Code : 200 81 | Response Length : 83 82 | Response Method : GET 83 | Response Content : { 84 | "userId": 1, 85 | "id": 1, 86 | "title": "delectus aut autem", 87 | "completed": false 88 | } 89 | ``` 90 | 91 | In this example, we make a `GET` request to the URL 92 | https://jsonplaceholder.typicode.com/todos/1 to retrieve JSON data. 93 | If the request is successful, we print the response code, content length, 94 | method, and content. If the request fails, we print the error message. 95 | 96 | ## Getting Started Guides 97 | 98 | To begin your journey with our package, dive into the comprehensive tutorial 99 | available here: [tutorial.md](./tutorial/tutorial.md). 100 | 101 | ## Projects using http-client 102 | 103 | * [github-org-analyzer](https://github.com/rajkumardongre/github-org-analyzer): 104 | An example mini-project to demonstrate the use of http-client by downloading 105 | and parsing data from the GitHub API. 106 | * [fortran-xkcd](https://github.com/rajkumardongre/fortran-xkcd/tree/http_client_version): 107 | An fpm example project that displays the latest xkcd comic inside an X window. 108 | As a limitation, only images in PNG format are supported. 109 | The alt text will be printed to console. 110 | * [foropenai](https://github.com/gha3mi/foropenai): A Fortran library to access the OpenAI API. 111 | * [fortelegram-bot](https://github.com/vypivshiy/fortelegram-bot): An Example of simple telegram bot 112 | * [forcompile](https://github.com/gha3mi/forcompile): A Fortran library to access the Compiler Explorer API. 113 | 114 | If you're using http-client in your Fortran project and would like to be 115 | included on this list, we welcome you to contribute by creating a pull request 116 | (PR) and adding your project details. 117 | 118 | ## Contributing to project 119 | 120 | Thank you for your interest in http-client! Contributions from the community 121 | are esential for improving the package. This section provides a guide on how to 122 | get the code, build the library, and run examples and tests. 123 | 124 | ### Get the code 125 | 126 | To get started, follow these steps: 127 | 128 | Clone the repository using Git: 129 | 130 | ``` 131 | git clone https://github.com/fortran-lang/http-client 132 | cd http-client 133 | ``` 134 | 135 | ### Prerequisites 136 | 137 | Before building the library, ensure that you have the necessary dependencies 138 | installed. On Ubuntu, you need to install the curl development headers. 139 | Use the following command: 140 | 141 | ``` 142 | sudo apt install -y libcurl4-openssl-dev 143 | ``` 144 | 145 | ### Build the library 146 | 147 | http-client uses **fpm** as the build system. Make sure you have fpm-0.8.x or 148 | later installed. To build the library, run the following command within the 149 | project directory: 150 | 151 | 152 | ``` 153 | fpm build 154 | ``` 155 | 156 | ### Run examples 157 | 158 | http-client provides example programs that demonstrate its use. To run the 159 | examples, use the following command: 160 | 161 | ``` 162 | fpm run --example 163 | ``` 164 | 165 | Executing this command will execute the example programs, allowing you to see 166 | the package in action and understand how to utilize its features. 167 | 168 | ### Run tests 169 | 170 | http-client includes a test suite to ensure its functionality is working as 171 | expected. To run the tests, type: 172 | 173 | ``` 174 | fpm test 175 | ``` 176 | 177 | Running the tests will validate the behavior of the package and help identify 178 | any issues or regressions. 179 | 180 | ### Generating API Documentation 181 | 182 | Before generating API documentation, ensure that you have FORD 183 | [installed on your system](https://github.com/Fortran-FOSS-Programmers/ford#installation). 184 | 185 | Once FORD is set up, execute the following command to build the API documentation: 186 | 187 | ```bash 188 | ford ford.md 189 | ``` 190 | 191 | ### Supported compilers 192 | 193 | http-client is known to work with the following compilers: 194 | 195 | * GFortran 11 & 12 (tested in CI) 196 | * Intel OneAPI ifx v2023.1.0 and ifort classic v2021.9.0 197 | 198 | ### Contributing guidelines 199 | 200 | When contributing to the http Fortran package, please keep the following guidelines in mind: 201 | 202 | * Before making any substantial changes, it is recommended to open an issue to discuss the proposed changes and ensure they align with the project's goals. 203 | * Fork the repository and create a new branch for your contribution. 204 | * Ensure that your code adheres to the existing coding style and follows good software engineering practices. 205 | * Write clear and concise commit messages. 206 | * Make sure to test your changes and ensure they do not introduce regressions or break existing functionality. 207 | * Submit a pull request with your changes, providing a clear explanation of the problem you are solving and the approach you have taken. 208 | 209 | We appreciate your contributions and look forward to your valuable input in improving http-client. 210 | 211 | Happy coding!👋 -------------------------------------------------------------------------------- /tutorial/example-project/github-org-analyzer.md: -------------------------------------------------------------------------------- 1 | # Building a [GitHub Organization Analyzer](./github-org-analyzer/README.md) in Fortran, using `http-client` 🚀 2 | 3 | In this tutorial, we'll create a simple Fortran program that uses the [GitHub API](https://docs.github.com/en/rest?apiVersion=2022-11-28) to retrieve and display all the repositories of the [`fortran-lang`](https://github.com/fortran-lang) organization. We'll use the [`http-client`](https://github.com/fortran-lang/http-client) and [`json-fortran`](https://github.com/jacobwilliams/json-fortran) libraries to make API requests and handle JSON responses. 4 | 5 | # Prerequisite 🚩 6 | 7 | Before proceeding with building the GitHub Organization Analyzer library and running the program, it's crucial to ensure that you have at least [`fpm`](https://fpm.fortran-lang.org/) v0.9.0 (Fortran Package Manager) installed. Additionally, there is a single dependency required for the [`http-client`](https://github.com/fortran-lang/http-client) library utilized in this project. Please follow the steps provided below to set up your environment: 8 | 9 | ### Step 1: Install fpm 10 | 11 | [`fpm`](https://fpm.fortran-lang.org/) is the Fortran Package Manager used for building and managing Fortran projects. If you do not currently have `fpm` installed, you can follow the installation instructions available on the official `fpm` repository to install version v0.9.0 or a more recent version. The installation guide can be found here: [Installation Guide](https://fpm.fortran-lang.org/install/index.html). 12 | 13 | 14 | ### Step 2: Install libcurl Development Headers 15 | 16 | The `http-client` library requires the [`libcurl`](https://curl.se/libcurl/) development headers to be installed. On Ubuntu-based systems, you can install the required dependencies using the following command: 17 | 18 | ```bash 19 | sudo apt install -y libcurl4-openssl-dev 20 | ``` 21 | 22 | This command will install the necessary development headers for libcurl, enabling the `http-client` library to make API requests to fetch data from the GitHub API. 23 | 24 | Once you have `fpm` installed and the required dependencies set up, you are ready to proceed with building and running the GitHub Organization Analyzer project.🙌 25 | 26 | # Let's Start Building 👨‍💻 27 | 28 | ### Step 1: Set up the Project 29 | 30 | >**Note : This requires at least fpm v0.9.0.** 31 | 32 | 1. Open your terminal or command prompt and create a new directory for the project: 33 | 34 | ```bash 35 | fpm new github-org-analyzer 36 | cd github-org-analyzer 37 | ``` 38 | 39 | 2. The project structure will look like this: 40 | 41 | ``` 42 | . 43 | ├── README.md 44 | ├── app 45 | │ └── main.f90 46 | ├── fpm.toml 47 | ├── src 48 | │ └── github-org-analyzer.f90 49 | └── test 50 | └── check.f90 51 | ``` 52 | 53 | ### Step 2: Add Dependencies to `fpm.toml` 54 | 55 | Open the `fpm.toml` file and add the following dependencies: 56 | 57 | ```toml 58 | [dependencies] 59 | http = { git = "https://github.com/fortran-lang/http-client.git" } 60 | stdlib = "*" 61 | json-fortran = { git = "https://github.com/jacobwilliams/json-fortran.git" } 62 | ``` 63 | 64 | ### Step 3: Import the Libraries 65 | 66 | Open the `github-org-analyzer.f90` file in the `src` folder and import the required libraries: 67 | 68 | ```fortran 69 | module github_org_analyzer 70 | use json_module, only : json_file 71 | use http, only : request, response_type 72 | use stdlib_strings, only : to_string 73 | 74 | ! ... (subroutine to be added later) 75 | end module github_org_analyzer 76 | ``` 77 | * `json_module` : This module provides functionalities for parsing JSON content, and we use it here to work with JSON data obtained from the GitHub API. 78 | 79 | * The `http` module enables us to make API calls and send HTTP requests to fetch data from the GitHub API. 80 | 81 | * `to_string` : We use this function to convert integers to their string representations 82 | 83 | ### Step 4: Create the `print_org_repositories` Subroutine 84 | 85 | Now let's write the `print_org_repositories` subroutine, which fetches and prints all the repositories of the "fortran-lang" organization using the GitHub API. This subroutine utilizes the `http-client` and `json-fortran` libraries to make API requests and handle JSON responses. 86 | 87 | 1. Open the `github_org_analyzer.f90` file in the `src` folder. 88 | 89 | 2. Create the `print_org_repositories` subroutine within the `github_org_analyzer` module: 90 | 91 | ```fortran 92 | module github_org_analyzer 93 | use json_module, only : json_file 94 | use http, only : request, response_type 95 | use stdlib_strings, only : to_string 96 | 97 | implicit none 98 | private 99 | 100 | ! Declare the function as public to make it accessible to other modules 101 | public :: print_org_repositories 102 | contains 103 | subroutine print_org_repositories() 104 | !! subroutine to print all repositories of fortran-lang organization 105 | character(*), parameter :: api_url = 'https://api.github.com/orgs/fortran-lang/repos' 106 | !! Stores GitHub API URL for fetching repositories 107 | integer :: i 108 | !! counter to traverse all the repos return by api 109 | character(:), allocatable :: count 110 | !! stores the string equivalent of counter, i.e variable i 111 | character(:), allocatable :: value 112 | !! stores the individual github repo name for each traversal 113 | type(json_file) :: json 114 | !! give the ability to parse the json content 115 | type(response_type) :: response 116 | !! stores the response from the github api 117 | logical :: found 118 | !! flag for weather the current repo found or not 119 | 120 | ! Make an HTTP GET request to the API URL and store the response in the `response` variable 121 | response = request(url=api_url) 122 | 123 | ! Checking for any errors. If an error occurs during the HTTP call, the `ok` 124 | ! attribute will be set to .false., and the `err_msg` attribute will contain 125 | ! the reason for the error. 126 | if(.not. response%ok) then 127 | print *, 'Request Fail: ', response%err_msg 128 | else 129 | print *, 'Fortran lang All repositories:' 130 | 131 | ! Initialize the `json` object to parse the JSON content 132 | call json%initialize() 133 | 134 | ! Deserialize the JSON response(parsing the json) 135 | call json%deserialize(response%content) 136 | 137 | ! Traverse Repositories and Print Names 138 | 139 | ! Counter to traverse all repos one by one 140 | i = 0 141 | ! Enter the loop to traverse all repositories while they exist 142 | do 143 | ! Increment the counter for the next repository 144 | i = i + 1 145 | 146 | ! Convert the updated counter to its string representation and store it in count variable 147 | count = to_string(i) 148 | 149 | ! Fetch the name of the current repository (based on the `i` counter) and check if it exists 150 | call json%get('['//count//'].name', value, found) 151 | if(.not.found) exit 152 | 153 | ! If the repository name exists (`found` is true), print the repository number and name 154 | print *, count//'. ', value 155 | 156 | end do 157 | end if 158 | end subroutine print_org_repositories 159 | end module github_org_analyzer 160 | ``` 161 | 162 | ### Step 5: Call the Subroutine in `main.f90` 163 | 164 | Open the `main.f90` file in the `app` folder and call the `print_org_repositories` subroutine: 165 | 166 | ```fortran 167 | program main 168 | ! importing `print_org_repositories` subroutine 169 | use github_org_analyzer, only: print_org_repositories 170 | implicit none 171 | 172 | ! Print all repositories inside Fortran lang organization' 173 | call print_org_repositories() 174 | end program main 175 | ``` 176 | 177 | ### Step 6: Run the Program 178 | 179 | Now that you've completed all the steps, it's time to run the program: 180 | 181 | ``` 182 | fpm run 183 | ``` 184 | 185 | You should see the following output🧐: 186 | 187 | ``` 188 | Fortran lang All repositories: 189 | 1. fftpack 190 | 2. vscode-fortran-support 191 | 3. stdlib 192 | 4. stdlib-docs 193 | 5. fpm 194 | 6. fortran-lang.org 195 | 7. talks 196 | 8. benchmarks 197 | 9. fpm-registry 198 | 10. setup-fpm 199 | 11. stdlib-cmake-example 200 | 12. .github 201 | 13. fortran-forum-article-template 202 | 14. test-drive 203 | 15. fpm-haskell 204 | 16. fpm-metadata 205 | 17. minpack 206 | 18. fortls 207 | 19. fpm-docs 208 | 20. homebrew-fortran 209 | 21. playground 210 | 22. contributor-graph 211 | 23. webpage 212 | 24. assets 213 | 25. registry 214 | 26. fpm-on-wheels 215 | 27. http-client 216 | ``` 217 | 218 | 🎉Congratulations! You've successfully built a simple Fortran program that fetches and displays the repositories of the "fortran-lang" organization using the GitHub API. 219 | 220 | 👨‍💻 Feel free to explore the full capabilities of the [`http-client`](https://github.com/fortran-lang/http-client) library to create more advanced projects! 221 | 222 | Moreover, we highly encourage you to actively contribute to the [github-org-analyzer](./github-org-analyzer/README.md) project. You have the opportunity to propose and implement new features, address any bugs, and enhance the existing code. 223 | 224 | Happy Coding! 👋 -------------------------------------------------------------------------------- /tutorial/example-project/github-org-analyzer/src/github-org-analyzer.f90: -------------------------------------------------------------------------------- 1 | module github_org_analyzer 2 | ! This module contains subroutines to analyze and retrieve 3 | ! information about GitHub repositories of a given organization. 4 | ! The module utilizes the json-fortran library to parse JSON 5 | ! content and the http-client library to make API calls to fetch 6 | ! data from the GitHub API 7 | use json_module, only : json_file 8 | ! This module provides functionalities for parsing JSON content, and we use it here to work with JSON data obtained from the GitHub API. 9 | use http, only : request, response_type 10 | ! The http module enables us to make API calls and send HTTP requests to fetch data from the GitHub API. 11 | use utils, only : int_to_str 12 | ! int_to_str : We use this function to convert integers to their string representations 13 | 14 | implicit none 15 | private 16 | 17 | public :: analyze_github_organization 18 | public :: get_top_active_repositories 19 | public :: get_top_starred_repositories 20 | public :: get_most_used_repositories 21 | public :: get_contributor_from_repositories 22 | 23 | contains 24 | 25 | subroutine analyze_github_organization(org_name) 26 | ! This subroutine analyzes a GitHub organization specified by its name (org_name). 27 | ! It fetches data for all the repositories within the organization using the GitHub 28 | ! API and prints detailed information about each repository. 29 | character(*), intent(in) :: org_name 30 | ! The name of the GitHub organization to analyze. 31 | character(:), allocatable :: api_url 32 | ! stores the GitHub API URL to fetch organization repositories' data. 33 | 34 | ! Construct the GitHub API URL (api_url) to fetch all the repositories within the specified organization 35 | api_url = 'https://api.github.com/orgs/'//org_name//'/repos' 36 | 37 | ! print detailed information about each repository within the organization. 38 | call print_org_repositories(api_url) 39 | end subroutine 40 | 41 | subroutine get_top_active_repositories(org_name, count) 42 | ! This subroutine fetches data for the top active repositories within a specified 43 | ! GitHub organization (org_name) using the GitHub API. The number of repositories 44 | ! to fetch (count) is optional, and if not provided, the default value is 5 45 | character(*), intent(in) :: org_name 46 | ! The name of the GitHub organization to fetch top active repositories. 47 | integer, optional, intent(in) :: count 48 | ! The number of top active repositories to fetch. If not provided, the default value is 5. 49 | character(:), allocatable :: api_url 50 | ! stores the GitHub API URL to fetch top active repositories within the organization. 51 | character(:), allocatable :: count_str 52 | ! A string representation of the count variable, used in constructing the API URL. 53 | 54 | if(present(count)) then 55 | count_str = int_to_str(count) 56 | else 57 | count_str = '5' 58 | end if 59 | 60 | api_url = 'https://api.github.com/orgs/'//org_name//'/repos?sort=updated&direction=desc&per_page='//count_str 61 | call print_org_repositories(api_url) 62 | end subroutine get_top_active_repositories 63 | 64 | subroutine get_top_starred_repositories(org_name, count) 65 | ! This subroutine fetches data for the top starred repositories within a 66 | ! specified GitHub organization (org_name) using the GitHub API. The number 67 | ! of repositories to fetch (count) is optional, and if not provided, the 68 | ! default value is 5. 69 | character(*), intent(in) :: org_name 70 | ! The name of the GitHub organization to fetch top starred repositories. 71 | integer, optional, intent(in) :: count 72 | ! The number of top starred repositories to fetch. If not provided, the default value is 5. 73 | character(:), allocatable :: api_url 74 | ! stores the GitHub API URL to fetch top starred repositories within the organization. 75 | character(:), allocatable :: count_str 76 | ! A string representation of the count variable, used in constructing the API URL. 77 | 78 | if(present(count)) then 79 | count_str = int_to_str(count) 80 | else 81 | count_str = '5' 82 | end if 83 | 84 | api_url = 'https://api.github.com/orgs/'//org_name//'/repos?sort=stars&direction=desc&per_page='//count_str 85 | call print_org_repositories(api_url) 86 | end subroutine get_top_starred_repositories 87 | 88 | subroutine get_most_used_repositories(org_name, count) 89 | ! This subroutine fetches data for the most used repositories within a 90 | ! specified GitHub organization (org_name) using the GitHub API. The 91 | ! "most used" criteria is based on the number of forks the repositories 92 | ! have. The number of repositories to fetch (count) is optional, and if 93 | ! not provided, the default value is 5. 94 | 95 | character(*), intent(in) :: org_name 96 | ! The name of the GitHub organization to fetch the most used repositories. 97 | integer, optional, intent(in) :: count 98 | ! The number of most used repositories to fetch. If not provided, the default value is 5. 99 | character(:), allocatable :: api_url 100 | ! stores the GitHub API URL to fetch the most used repositories within the organization. 101 | character(:), allocatable :: count_str 102 | ! A string representation of the count variable, used in constructing the API URL. 103 | 104 | if(present(count)) then 105 | count_str = int_to_str(count) 106 | else 107 | count_str = '5' 108 | end if 109 | 110 | api_url = 'https://api.github.com/orgs/'//org_name//'/repos?sort=forks&direction=desc&per_page='//count_str 111 | call print_org_repositories(api_url) 112 | end subroutine get_most_used_repositories 113 | 114 | subroutine get_contributor_from_repositories(org_name, repo_name, count) 115 | ! This subroutine fetches contributor data from the GitHub API for a specified 116 | ! repository (repo_name) within a specified GitHub organization (org_name). It 117 | ! can fetch a specified number of top contributors (count) or a default of 5 118 | ! contributors if the count is not provided. 119 | character(*), intent(in) :: org_name 120 | ! The name of the GitHub organization to which the repository belongs. 121 | character(*), intent(in) :: repo_name 122 | ! The name of the GitHub repository for which to fetch contributors. 123 | integer, optional, intent(in) :: count 124 | ! The number of top contributors to fetch. If not provided, the default value is 5. 125 | character(:), allocatable :: api_url 126 | ! stores the GitHub API URL to fetch contributor data. 127 | character(:), allocatable :: count_str 128 | ! A string representation of the count variable, used in constructing the API URL. 129 | 130 | ! setting count default value 131 | if(present(count)) then 132 | count_str = int_to_str(count) 133 | else 134 | count_str = '5' 135 | end if 136 | 137 | ! Construct the GitHub API URL (api_url) to fetch the contributors from 138 | ! the specified repository within the organization 139 | api_url = 'https://api.github.com/repos/'//org_name//'/'//repo_name//'/contributors?per_page='//count_str 140 | 141 | ! print detailed information about the contributors. 142 | call print_contributors(api_url) 143 | end subroutine get_contributor_from_repositories 144 | 145 | subroutine print_org_repositories(api_url) 146 | ! This subroutine fetches repository data from the GitHub API for a 147 | ! given API URL (api_url) and prints detailed information about each repository. 148 | character(*), intent(in) :: api_url 149 | ! The URL of the GitHub API to fetch repositories. 150 | character(:), allocatable :: count 151 | ! stores the index of the current repository during traversal. 152 | character(:), allocatable :: value 153 | ! store the fetched values for each repository attribute. 154 | type(json_file) :: json 155 | ! responsible for parsing JSON content. 156 | type(response_type) :: response 157 | ! Store the response from the GitHub API 158 | integer :: i 159 | ! A counter variable used to traverse the repositories one by one. 160 | logical :: found 161 | ! A flag used to check if the current repository exists 162 | ! (i.e., found in the JSON content). 163 | 164 | ! Make an HTTP GET request to the specified api_url using the request function from 165 | ! the http module, and store the response in the response variable: 166 | response = request(url=api_url) 167 | 168 | ! Initialize the json object using the initialize method from the json_file 169 | ! type, and deserialize the JSON content from the API response: 170 | call json%initialize() 171 | call json%deserialize(response%content) 172 | 173 | ! Set the counter i to 1, and convert it to its string representation using the 174 | ! int_to_str function from the utils module, storing the result in the count variable 175 | i = 1 176 | count = int_to_str(i) 177 | 178 | ! Fetch the name of the 1st GitHub repository and check if it exists (found is true) 179 | call json%get('['//count//'].name', value, found) 180 | 181 | ! Enter a loop to traverse all the repositories while they exist 182 | do while(found) 183 | 184 | ! Fetch the attributes of the current repository and print their values if they exist 185 | call json%get('['//count//'].name', value, found); if(found) print*, 'Repository Name: ',value 186 | call json%get('['//count//'].description', value, found); if(found) print*, 'Description : ',value 187 | call json%get('['//count//'].language', value, found); if(found) print*, 'Language : ',value 188 | call json%get('['//count//'].stargazers_count', value, found); if(found) print*, 'Stars : ',value 189 | call json%get('['//count//'].forks_count', value, found); if(found) print*, 'Forks : ',value 190 | call json%get('['//count//'].open_issues_count', value, found); if(found) print*, 'Open Issues : ',value 191 | call json%get('['//count//'].html_url', value, found); if(found) print*, 'URL : ',value 192 | print *, '' 193 | 194 | ! Increment the counter i for the next repository and update the 195 | ! count variable accordingly 196 | i = i+1 197 | count = int_to_str(i) 198 | 199 | ! Fetch the name of the next repository (based on the updated i counter) 200 | ! and update the found flag accordingly 201 | call json%get('['//count//'].name', value, found) 202 | end do 203 | end subroutine print_org_repositories 204 | 205 | subroutine print_contributors(api_url) 206 | ! This subroutine fetches contributor data from the GitHub API for a given API URL 207 | ! (api_url) and prints detailed information about each contributor. 208 | character(*), intent(in) :: api_url 209 | ! The URL of the GitHub API to fetch contributor data. 210 | character(:), allocatable :: count 211 | ! stores the index of the current contributor during traversal in string format. 212 | character(:), allocatable :: value 213 | ! store the fetched values for each contributor attribute. 214 | type(json_file) :: json 215 | ! responsible for parsing JSON content. 216 | type(response_type) :: response 217 | ! stores the response from the GitHub API. 218 | integer :: i 219 | ! A counter variable used to traverse the contributors one by one. 220 | logical :: found 221 | ! A flag used to check if the current contributor exists (i.e., found in the JSON content). 222 | 223 | ! Make an HTTP GET request to the specified api_url using the request function from 224 | ! the http module, and store the response in the response variable 225 | response = request(url=api_url) 226 | 227 | ! Initialize the json object using the initialize method from the json_file type, 228 | ! and deserialize the JSON content from the API response 229 | call json%initialize() 230 | call json%deserialize(response%content) 231 | 232 | ! Set the counter i to 1, and convert it to its string representation using the int_to_str 233 | ! function from the utils module, storing the result in the count variable 234 | i = 1 235 | count = int_to_str(i) 236 | 237 | ! Fetch the login (username) of the 1st GitHub contributor and check if it exists (found is true): 238 | call json%get('['//count//'].login', value, found) 239 | 240 | ! Enter a loop to traverse all the contributors while they exist 241 | do while(found) 242 | ! Fetch the attributes of the current contributor and print their values if they exist 243 | call json%get('['//count//'].login', value, found); if(found) print*, 'User Name : ',value 244 | call json%get('['//count//'].contributions', value, found); if(found) print*, 'Contributions : ',value 245 | call json%get('['//count//'].html_url', value, found); if(found) print*, 'URL : ',value 246 | print *, '' 247 | 248 | ! Increment the counter i for the next contributor and update the count variable accordingly 249 | i = i+1 250 | count = int_to_str(i) 251 | 252 | ! Fetch the login (username) of the next contributor (based on the updated i counter) 253 | ! and update the found flag accordingly 254 | call json%get('['//count//'].login', value, found) 255 | end do 256 | 257 | end subroutine print_contributors 258 | 259 | 260 | end module github_org_analyzer 261 | -------------------------------------------------------------------------------- /tutorial/tutorial.md: -------------------------------------------------------------------------------- 1 | # **Welcome** 2 | ### *Welcome to the Getting Started Guides.* 👋 3 | 4 | ### **Table of contents:** 📜 5 | 6 | 1. ### [**Installation** 🌟](#installation-f09f8c9f-1) 7 | 8 | 2. ### [**Making HTTP Requests** 🚀](#making-http-requests-f09f9a80-1) 9 | - [**Sending `GET` Requests**](#sending-get-requests) 10 | - [*Accessing Response `Content`*](#accessing-response-content) 11 | - [*Extracting `Content Length`*](#extracting-content-length) 12 | - [*Retrieving `Status Codes`*](#retrieving-status-codes) 13 | - [*Handling `Errors`*](#handling-errors) 14 | - [*Getting Response `Headers`*](#getting-response-headers) 15 | - [*Understanding `pair_type` derived type*](#understanding-pair_type-derived-type) 16 | - [*Sending Custom `Headers`*](#sending-custom-headers) 17 | - [*Setting Request `Timeout`*](#setting-request-timeout) 18 | - [*Setting `Authentication`*](#setting-authentication) 19 | - [**Sending `POST` Requests**](#sending-post-request) 20 | - [*Sending Data using `data`*](#sending-data-using-data) 21 | - [*Sending Data using `form`*](#sending-data-using-form) 22 | - [*Sending Data using `file`*](#sending-data-using-file) 23 | - [**Sending `PUT` Requests**](#sending-put-requests) 24 | - [**Sending `PATCH` Requests**](#sending-patch-requests) 25 | - [**Sending `DELETE` Requests**](#sending-delete-requests) 26 | - [**Sending `HEAD` Requests**](#sending-head-requests) 27 | 28 | 3. ### [**Real Projects** 🤖](#real-projects-f09fa496-1) 29 | 30 | 31 | # **Installation** 🌟 32 | 33 | Before building the `http-client` library, ensure that you have the necessary dependencies installed. On Ubuntu, you need to install the curl development headers. Use the following command: 34 | 35 | ``` 36 | sudo apt install -y libcurl4-openssl-dev 37 | ``` 38 | 39 | To use `http-client` within your fpm project, add the following to your package `fpm.toml` file: 40 | 41 | ```toml 42 | [dependencies] 43 | http = { git = "https://github.com/fortran-lang/http-client.git" } 44 | stdlib = "*" 45 | ``` 46 | 47 | [Go Top](#table-of-contents-📜) 48 | 49 | # **Making HTTP Requests** 🚀 50 | ## **Sending `GET` Requests** 51 | Let's First Import `http` package into our program 52 | ```fortran 53 | program send_get_request 54 | use http, only : request, response_type 55 | ... 56 | end program send_get_request 57 | ``` 58 | For making a `GET` request we required to import `request` function which is use to configure our HTTP request(like setting request `URL`, `method`, `headers` and etc.). 59 | 60 | The `request()` function returns a `response_type` object, containing server response. so we also have to import `response_type` derived type to store server response, like below. 61 | 62 | ```fortran 63 | type(response_type) :: response 64 | ``` 65 | Now Let's make the HTTP `GET` request. 66 | ```fortran 67 | response = request(url='https://httpbin.org/get') 68 | ``` 69 | The above code will make a HTTP `GET` request to https://httpbin.org/get url and store the server response in `response` variable. 70 | 71 | Now let's study the `response` object in detail. 🧐 72 | 73 | The `response` object contain rich information about server response. It contains following attribute. 74 | 75 | * `url` : The URL of the request. 76 | * `method` : The HTTP method of the request. 77 | * `content` : The content of the server response. 78 | * `err_msg` : The Error message if the response was not successful. 79 | * `status_code` : The HTTP status code of the response. 80 | * `content_length` : length of the response content. 81 | * `ok` : Boolean flag, which indicates weather the request was sucessfull or not. 82 | * `header` : An array of **name-value** pairs representing response headers. 83 | 84 | ### **Accessing Response `Content`** 85 | To access the content of the server's response, use the `content` attribute: 86 | 87 | ```fortran 88 | print *, 'Response Content: ', response%content 89 | ``` 90 | 91 | [Go Top](#table-of-contents-📜) 92 | 93 | ### **Extracting `Content Length`** 94 | 95 | You can retrieve the response content length using the `content_length` attribute: 96 | 97 | ```fortran 98 | print *, 'Response Length: ', response%content_length 99 | ``` 100 | [Go Top](#table-of-contents-📜) 101 | 102 | 103 | ### **Retrieving `Status Codes`** 104 | 105 | HTTP response [`status codes`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) indicate whether a request was successful or not. The status code is stored in the `status_code` attribute: 106 | 107 | ```fortran 108 | print *, 'Response Code: ', response%status_code 109 | ``` 110 | [Go Top](#table-of-contents-📜) 111 | 112 | ### **Handling `Errors`** 113 | 114 | Ensuring robust error handling is crucial when working with HTTP requests. You can verify the success of your request by inspecting the `ok` attribute in the response. If the attribute is `.false.`, it indicates a request failure. Conversely, if it's `.true.`, the HTTP request was successful. 115 | 116 | ```fortran 117 | print *, 'Request is Successful: ', response%ok 118 | ``` 119 | 120 | In cases where the request encounters an issue, the `err_msg` attribute in the response will contain information about the reason for the failure. It's essential to handle errors gracefully: 121 | 122 | ```fortran 123 | if (.not. response%ok) then 124 | print *, response%err_msg 125 | ! Code for handling the failed request 126 | else 127 | ! Code for processing a successful request 128 | end if 129 | ``` 130 | 131 | > **Note:** 132 | > 133 | > Always prioritize checking the `ok` attribute of the response to identify any request failures. Incorporate the provided code snippet whenever you are processing the response to ensure comprehensive error management. This practice will help you build more robust and reliable HTTP interactions. 134 | 135 | [Go Top](#table-of-contents-📜) 136 | 137 | ### **Getting Response `Headers`** 138 | 139 | The response headers are stored as array of [`pair_type`](#pair_type-derived-type) object in `header` attribute. 140 | 141 | ```fortran 142 | print *, 'Response Header Size : ', size(response%header) 143 | ``` 144 | We can iterate over all the `headers` in this way. 145 | ```fortran 146 | implicit none 147 | character(:), allocatable :: header_name, header_value 148 | integer :: i 149 | !... 150 | !... 151 | do i=1, size(response%header) 152 | header_name = response%header(i)%name 153 | header_value = response%header(i)%value 154 | print *,header_name, ': ', header_value 155 | end do 156 | ``` 157 | 158 | Complete program for sending `GET` request : 159 | ```fortran 160 | program get 161 | use http, only : request, response_type 162 | implicit none 163 | type(response_type) :: response 164 | character(:), allocatable :: header_name, header_value 165 | integer :: i 166 | 167 | ! Making a GET request 168 | response = request(url='https://httpbin.org/get') 169 | 170 | ! Checking any errors 171 | if(.not. response%ok) then 172 | print *, 'Request Fail: ', response%err_msg 173 | else 174 | print *, 'Status Code : ', response%status_code 175 | print *, 'Content : ', response%content 176 | print *, 'Content Length : ', response%content_length 177 | print *, 'Response Header Size : ', size(response%header) 178 | 179 | print *, 'All response headers :' 180 | ! Traversing over all response headers 181 | do i=1, size(response%header) 182 | ! Extracting header name 183 | header_name = response%header(i)%name 184 | ! Extracting corresponding header value 185 | header_value = response%header(i)%value 186 | print *, header_name, ': ', header_value 187 | end do 188 | end if 189 | end program get 190 | ``` 191 | 192 | 193 | Before we proceed, it's crucial to grasp the `pair_type` derived type, as we will be utilizing it in various scenarios. 194 | 195 | [Go Top](#table-of-contents-📜) 196 | 197 | 198 | ### **Understanding `pair_type` derived type** 199 | 200 | It is use to store a **name-value pair**. 201 | 202 | ```fortran 203 | ! pair_type defination 204 | type :: pair_type 205 | character(:), allocatable :: name, value 206 | end type pair_type 207 | ``` 208 | It serves various purposes within the `http` package. 209 | 210 | 1. Storing request and response `headers` as array of `pair_type` objects, where : 211 | - `name` represent the header name. 212 | - `value` represent the header value. 213 | 214 | 2. Representing fields in a url-encoded **HTTP `form`**: 215 | - `name` to represent the form field name. 216 | - `value` to represent the form field value. 217 | 218 | 3. Storing information about the `file` to upload: 219 | - `name` to represent the name of the file. 220 | - `value` to represent the path of the file on the local system. 221 | 222 | 4. Storing authentication detail, require to authenticate the request. 223 | - `name` to represent the **username** 224 | - `value` to represent the **password** 225 | 226 | [Go Top](#table-of-contents-📜) 227 | 228 | ### **Sending Custom `Headers`** 229 | 230 | Much like [response headers](#getting-response-headers), request headers are also stored as an array of [`pair_type`](#pair_type-derived-type) objects. These headers are provided through the `header` attribute of the `request()` function. 231 | 232 | ```fortran 233 | program send_headers 234 | use http, only : request, response_type, pair_type 235 | implicit none 236 | type(response_type) :: response 237 | type(pair_type), allocatable :: req_headers(:) ! will store request headers 238 | 239 | ! Storing request header in array of pair_type object, where each pair_type 240 | ! object represents a single header. (in header-name,header-value format) 241 | req_headers = [ & 242 | pair_type('my-header-1', 'Hello World'), & 243 | pair_type('my-header-2', 'Hello Universe'), & 244 | pair_type('Set-Cookie', 'Theme-Light'), & 245 | pair_type('Set-Cookie', 'Auth-Token: 12345'), & 246 | pair_type('User-Agent', 'my user agent') & 247 | ] 248 | 249 | ! Congiguring request with API URL and request headers 250 | response = request( & 251 | url='https://httpbin.org/headers', & 252 | header=req_headers & 253 | ) 254 | 255 | ! Checking for any error 256 | if(.not. response%ok) then 257 | print *, 'Request Fail: ', response%err_msg 258 | else 259 | print *, 'Content : ', response%content 260 | end if 261 | end program send_headers 262 | ``` 263 | 264 | [Go Top](#table-of-contents-📜) 265 | 266 | ### **Setting Request `Timeout`** 267 | 268 | The overall `timeout` for the request, which is the time the entire request must complete. The value of this timeout(**in seconds**) can be set by passing the `timeout` parameter to the `request()` function. 269 | 270 | ```fortran 271 | program timeout 272 | ! The request below is designed to take more than 10 seconds to complete, 273 | ! but we set the timeout value to 5 seconds. 274 | ! As a result, the request will fail with an error message that says 275 | ! "Timeout was reached". 276 | use http, only: response_type, request 277 | implicit none 278 | type(response_type) :: response 279 | 280 | ! Delay in response for 10 seconds 281 | response = request( & 282 | url='https://httpbin.org/delay/10', & 283 | timeout=5 & 284 | ) 285 | 286 | ! Checking for any error 287 | if(.not. response%ok) then 288 | print *,'Error message : ', response%err_msg 289 | else 290 | print *, 'Response Content : ', response%content 291 | end if 292 | end program timeout 293 | ``` 294 | 295 | [Go Top](#table-of-contents-📜) 296 | 297 | 298 | ### **Setting `Authentication`** 299 | 300 | We can set a Basic Authentication to the request by setting `auth` parameter to the `request()` function. 301 | 302 | The `auth` parameter takes `pair_type` object as value, in which `name` represent the **username** and `value` represent the **password**. 303 | 304 | ```fortran 305 | program authentication 306 | ! Making request with HTTP Basic Auth 307 | use http, only: response_type, request, pair_type 308 | implicit none 309 | type(response_type) :: response 310 | type(pair_type) :: req_auth 311 | 312 | ! setting username and password required for authentication 313 | req_auth = pair_type('user', 'passwd') 314 | 315 | ! Configuring request 316 | response = request( & 317 | url='https://httpbin.org/basic-auth/user/passwd', & 318 | auth=req_auth & 319 | ) 320 | 321 | ! Checking for any error 322 | if(.not. response%ok) then 323 | print *,'Error message : ', response%err_msg 324 | else 325 | print *, 'Response Code : ', response%status_code 326 | print *, 'Response Content : ', response%content 327 | end if 328 | end program authentication 329 | ``` 330 | Output : 331 | ```bash 332 | Response Code : 200 333 | Response Content : { 334 | "authenticated": true, 335 | "user": "user" 336 | } 337 | ``` 338 | > **Note :** 339 | > 340 | >It sends the **username** and **password** over the network **in plain text, easily captured by others**. 341 | 342 | [Go Top](#table-of-contents-📜) 343 | ## **Sending `POST` Request** 344 | 345 | An HTTP `POST` request is used to send data to a server, where data are shared via the body of a request. You can send a `POST` request by setting `method` parameter of `request()` function to `HTTP_POST`. 346 | 347 | ```fortran 348 | program post 349 | use http, only: response_type, request, HTTP_POST 350 | implicit none 351 | type(response_type) :: response 352 | 353 | ! Setting HTTP Method for request 354 | response = request( & 355 | url='https://example.com/post', & 356 | method=HTTP_POST & 357 | ) 358 | 359 | end program post 360 | ``` 361 | 362 | Within the `http` package, there are several options for sending data, accomplished through three mainly parameters: `data`, `form`, and `file` within the `request()` function. 363 | 364 | Now let's see each of them 🧐 365 | 366 | [Go Top](#table-of-contents-📜) 367 | 368 | ### **Sending Data using `data`** 369 | 370 | The `data` parameter allows us to transmit a variety of data. When utilizing this parameter, it's essential to include the `Content-Type` header, indicating the type of data being transmitted. 371 | 372 | **Sending plain text :** 373 | ```fortran 374 | program post 375 | ! This program demonstrates sending plain text data using POST request 376 | use http, only: response_type, request, HTTP_POST, pair_type 377 | implicit none 378 | type(response_type) :: response 379 | character(:), allocatable :: req_data 380 | type(pair_type), allocatable :: req_header(:) 381 | 382 | ! Setting Content-type header for sending plain text 383 | req_header = [pair_type('Content-Type', 'text/plain')] 384 | 385 | ! plain-text data we want to send 386 | req_data = 'Hello, this data needs to be sent to the server.' 387 | 388 | ! Setting HTTP POST method and Data to be send on server 389 | response = request( & 390 | url='https://httpbin.org/post', & 391 | method=HTTP_POST, & 392 | data=req_data, & 393 | header=req_header & 394 | ) 395 | 396 | ! Checking for any error 397 | if(.not. response%ok) then 398 | print *, 'Request Fail: ', response%err_msg 399 | else 400 | print *, 'Response Content : ', response%content 401 | end if 402 | end program post 403 | ``` 404 | **Sending JSON data :** 405 | ```fortran 406 | program post 407 | ! This program demonstrates sending JSON data using POST request. 408 | use http, only: response_type, request, HTTP_POST, pair_type 409 | implicit none 410 | type(response_type) :: response 411 | character(:), allocatable :: json_data 412 | type(pair_type), allocatable :: req_header(:) 413 | 414 | ! Setting Content-type header for sending JSON 415 | req_header = [pair_type('Content-Type', 'application/json')] 416 | 417 | ! JSON data we want to send 418 | json_data = '{"name":"Jhon","role":"developer"}' 419 | 420 | ! Configuring request with HTTP Method, JSON data and request headers 421 | response = request( & 422 | url='https://httpbin.org/post',& 423 | method=HTTP_POST, & 424 | data=json_data, & 425 | header=req_header & 426 | ) 427 | 428 | ! Checking for any errors 429 | if(.not. response%ok) then 430 | print *, 'Request Fail: ', response%err_msg 431 | else 432 | print *, 'Response Content : ', response%content 433 | end if 434 | end program post 435 | ``` 436 | 437 | [Go Top](#table-of-contents-📜) 438 | 439 | ### **Sending Data using `form`** 440 | 441 | When you need to transmit HTML form data to the server, you can utilize the `form` parameter to pass the data. The `form` parameter accepts an array of `pair_type` objects, where each `pair_type` object represents a **single form field**. 442 | 443 | The `form` data is initially URL encoded and then sent as the request's body. If no `Content-type` header is specified, a default `Content-type` header with the value `application/x-www-form-urlencoded` will be automatically set. 444 | 445 | ```fortran 446 | program post_form_data 447 | ! This program demonstrates sending Form data using a POST request. 448 | use http, only: response_type, request, HTTP_POST, pair_type 449 | implicit none 450 | type(response_type) :: response 451 | type(pair_type), allocatable :: form_data(:) 452 | 453 | ! Store form data in an array of pair_type objects, where each 454 | ! pair_type object represents a single form field 455 | form_data = [ & 456 | pair_type('name', 'John'), & 457 | pair_type('job', 'Developer') & 458 | ] 459 | 460 | ! Make the HTTP POST request with the form data 461 | response = request( & 462 | url='https://httpbin.org/post', & 463 | method=HTTP_POST, & 464 | form=form_data & 465 | ) 466 | 467 | ! Checking for any errors 468 | if (.not. response%ok) then 469 | print *, 'Error message: ', response%err_msg 470 | else 471 | print *, 'Response Content: ', response%content 472 | end if 473 | end program post_form_data 474 | ``` 475 | [Go Top](#table-of-contents-📜) 476 | 477 | ### **Sending Data using `file`** 478 | 479 | When you need to send a file (such as .png, .jpg, .txt, etc.) to a server, you can utilize the `file` parameter within the `request()` function. This parameter takes a `pair_type` object as its value. In this `pair_type` object, the `name` member specifies the field name under which the file will be sent to the server, and the `value` member represents the path to the file you want to send. 480 | 481 | If you don't explicitly provide a `Content-type` header, a default `Content-type` header with the value `multipart/form-data` will be automatically set. 482 | 483 | ```fortran 484 | program post_file 485 | 486 | ! This program demonstrates sending a File using a POST request. 487 | 488 | use http, only : request, response_type, HTTP_POST, pair_type 489 | implicit none 490 | type(response_type) :: response 491 | type(pair_type) :: file_data 492 | 493 | ! Specify the pair_type object as ('', '/path/to/file.txt') 494 | file_data = pair_type('my_file', '/path/to/file.txt') 495 | 496 | ! Make the HTTP POST request with the file data 497 | response = request( & 498 | url='https://httpbin.org/post', & 499 | method=HTTP_POST, & 500 | file=file_data & 501 | ) 502 | 503 | ! Checking for any errors 504 | if (.not. response%ok) then 505 | print *, 'Error message: ', response%err_msg 506 | else 507 | print *, 'Response Content: ', response%content 508 | end if 509 | end program post_file 510 | ``` 511 | 512 | #### **Note** : 513 | - 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. 514 | 515 | - 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. 516 | 517 | - If `data`, `form`, and `file` are all provided, only `data` is sent, and the `form` and `file` inputs are ignored. 518 | 519 | [Go Top](#table-of-contents-📜) 520 | 521 | ## **Sending `PUT` Requests** 522 | 523 | Sending a `PUT` request is quite similar to sending a [`POST`](#sending-post-request) request. In this case, the `method` parameter should be set to `HTTP_PUT`. 524 | 525 | ```fortran 526 | program put 527 | ! This program demonstrates sending JSON data with PUT request. 528 | use http, only: response_type, request, HTTP_PUT, pair_type 529 | implicit none 530 | type(response_type) :: response 531 | character(:), allocatable :: json_data 532 | type(pair_type), allocatable :: req_header(:) 533 | 534 | req_header = [pair_type('Content-Type', 'application/json')] 535 | 536 | ! JSON data we want to send 537 | json_data = '{"name":"Jhon","role":"developer"}' 538 | 539 | response = request( & 540 | url='https://httpbin.org/put',& 541 | method=HTTP_PUT, & 542 | data=json_data, & 543 | header=req_header & 544 | ) 545 | 546 | if(.not. response%ok) then 547 | print *, 'Request Fail: ', response%err_msg 548 | else 549 | print *, 'Response Content : ', response%content 550 | end if 551 | end program put 552 | ``` 553 | 554 | [Go Top](#table-of-contents-📜) 555 | 556 | ## **Sending `PATCH` Requests** 557 | 558 | Sending a `PATCH` request is quite similar to sending a [`POST`](#sending-post-request) request. In this case, the `method` parameter should be set to `HTTP_PATCH`. 559 | 560 | ```fortran 561 | program patch 562 | ! This program demonstrates sending JSON data with PATCH request. 563 | use http, only: response_type, request, HTTP_PATCH, pair_type 564 | implicit none 565 | type(response_type) :: response 566 | character(:), allocatable :: json_data 567 | type(pair_type), allocatable :: req_header(:) 568 | 569 | req_header = [pair_type('Content-Type', 'application/json')] 570 | 571 | ! JSON data we want to send 572 | json_data = '{"name":"Jhon","role":"developer"}' 573 | 574 | response = request( & 575 | url='https://httpbin.org/patch',& 576 | method=HTTP_PATCH, & 577 | data=json_data, & 578 | header=req_header & 579 | ) 580 | 581 | if(.not. response%ok) then 582 | print *, 'Request Fail: ', response%err_msg 583 | else 584 | print *, 'Response Content : ', response%content 585 | end if 586 | end program patch 587 | ``` 588 | 589 | [Go Top](#table-of-contents-📜) 590 | 591 | ## **Sending `DELETE` Requests** 592 | 593 | To send a `DELETE` request, simply set the `method` parameter to `HTTP_DELETE`. 594 | 595 | ```fortran 596 | program delete 597 | ! This program demonstrates sending DELETE request. 598 | use http, only: response_type, request, HTTP_DELETE 599 | implicit none 600 | type(response_type) :: response 601 | 602 | response = request( & 603 | url='https://httpbin.org/delete',& 604 | method=HTTP_DELETE & 605 | ) 606 | 607 | if(.not. response%ok) then 608 | print *, 'Request Fail: ', response%err_msg 609 | else 610 | print *, 'Response Content : ', response%content 611 | end if 612 | end program delete 613 | ``` 614 | [Go Top](#table-of-contents-📜) 615 | 616 | ## **Sending `HEAD` Requests** 617 | 618 | To send a `HEAD` request, simply set the `method` parameter to `HTTP_HEAD`. 619 | 620 | ```fortran 621 | program head 622 | ! This program demonstrates sending HEAD request. 623 | use http, only: response_type, request, HTTP_HEAD 624 | implicit none 625 | type(response_type) :: response 626 | 627 | response = request( & 628 | url='https://www.w3schools.com/python/demopage.php',& 629 | method=HTTP_HEAD & 630 | ) 631 | 632 | if(.not. response%ok) then 633 | print *, 'Request Fail: ', response%err_msg 634 | else 635 | print *, 'Request is Successfull!!!' 636 | end if 637 | end program head 638 | ``` 639 | [Go Top](#table-of-contents-📜) 640 | 641 | # **Real Projects** 🤖 642 | 643 | - [**GitHub organization analyzer**](example-project/github-org-analyzer.md) : 644 | 645 | This Fortran project **provides procedures to analyze GitHub organizations and retrieve valuable information about their repositories**. By leveraging the power of the `http-client` package, this analyzer fetches data from the GitHub API to generate insightful reports. 646 | 647 | - There are many more to come... 648 | 649 | Happy Coding! 👋 650 | 651 | [Go Top](#table-of-contents-📜) 652 | -------------------------------------------------------------------------------- /src/http/http_client.f90: -------------------------------------------------------------------------------- 1 | 2 | !!> This file defines the **`client_type`** derived type, which handles the 3 | !!> process of making **HTTP requests**. The actual HTTP requests are executed 4 | !!> using the [Fortran-curl](https://github.com/interkosmos/fortran-curl) 5 | !!> package as the underlying mechanism. 6 | 7 | module http_client 8 | 9 | !!> This module defines the **`client_type`** derived type, which handles the 10 | !!> process of making **HTTP requests**. The actual HTTP requests are executed 11 | !!> using the [Fortran-curl](https://github.com/interkosmos/fortran-curl) 12 | !!> package as the underlying mechanism. 13 | 14 | use iso_fortran_env, only: int64 15 | use iso_c_binding, only: c_associated, c_f_pointer, c_funloc, c_loc, & 16 | c_null_ptr, c_ptr, c_size_t, c_null_char 17 | use curl, only: c_f_str_ptr, curl_easy_cleanup, curl_easy_getinfo, & 18 | curl_easy_init, curl_easy_perform, curl_easy_setopt, & 19 | curl_easy_strerror, curl_slist_append, CURLE_OK, & 20 | CURLINFO_RESPONSE_CODE, CURLOPT_CUSTOMREQUEST, CURLOPT_HEADERDATA, & 21 | CURLOPT_HEADERFUNCTION, CURLOPT_HTTPHEADER, CURLOPT_URL, & 22 | CURLOPT_WRITEDATA, CURLOPT_WRITEFUNCTION, & 23 | CURLOPT_POSTFIELDS, CURLOPT_POSTFIELDSIZE_LARGE, curl_easy_escape, & 24 | curl_mime_init, curl_mime_addpart, curl_mime_filedata,curl_mime_name, & 25 | CURLOPT_MIMEPOST,curl_mime_data, CURL_ZERO_TERMINATED, & 26 | CURLOPT_TIMEOUT, CURLOPT_CONNECTTIMEOUT, & 27 | CURLOPT_HTTPAUTH, CURLAUTH_BASIC, CURLOPT_USERNAME, CURLOPT_PASSWORD 28 | use stdlib_optval, only: optval 29 | use http_request, only: request_type 30 | use http_response, only: response_type 31 | use http_pair, only: append_pair, pair_has_name, pair_type 32 | use http_version, only: VERSION_STRING 33 | 34 | implicit none 35 | 36 | private 37 | public :: request 38 | 39 | ! http_client Type 40 | type :: client_type 41 | !!> A derived type, responsible for making **actual HTTP `request`** using 42 | !!> fortran-curl at backend. 43 | type(request_type) :: request 44 | contains 45 | procedure :: client_get_response 46 | end type client_type 47 | 48 | interface client_type 49 | !!> Interface for `new_client` function. 50 | module procedure new_client 51 | end interface client_type 52 | 53 | interface request 54 | !!> Interface for `new_request` function. 55 | module procedure new_request 56 | end interface request 57 | 58 | contains 59 | 60 | function new_request(url, method, header, data, form, file, timeout, auth) result(response) 61 | 62 | !!> This function create a `request_type` object and populates it. 63 | !!> The function returns the `response_type` object containing the 64 | !!> **server's response**. 65 | !!____ 66 | !!> #### Note : 67 | !!> If the `header` argument is not provided, **default `user-agent` 68 | !!> header is set to `http-client/0.1`**. 69 | 70 | integer, intent(in), optional :: method 71 | !! Specifies the HTTP `method` to use for the request. 72 | !! The **default value is 1**, which corresponds to the **`HTTP_GET`** method. 73 | character(len=*), intent(in) :: url 74 | !! Specifies the **`URL`** of the server. 75 | character(len=*), intent(in), optional :: data 76 | !! Specifies the **`data`** that needs to be sent to the server. 77 | type(pair_type), intent(in), optional :: header(:) 78 | !! Specifies the **request `headers`** that need to be sent to the server. 79 | type(pair_type), intent(in), optional :: form(:) 80 | !! Specifies the **`form data`** that needs to be sent to the server. 81 | type(pair_type), intent(in), optional :: file 82 | !! Specifies the **`file`** that needs to be sent to the server. 83 | integer, intent(in), optional :: timeout 84 | !! **`Timeout`** value for the request in **seconds**. 85 | type(pair_type), intent(in), optional :: auth 86 | !! stores the `username` and `password` for **`authentication`** purposes. 87 | type(response_type) :: response 88 | !! Stores the server's **`response`**. 89 | 90 | type(request_type) :: request 91 | type(client_type) :: client 92 | integer :: i 93 | 94 | ! setting request url 95 | request%url = url 96 | 97 | ! Set default HTTP method. 98 | request%method = optval(method, 1) 99 | 100 | ! Set request header 101 | if (present(header)) then 102 | request%header = header 103 | ! Set default request headers. 104 | if (.not. pair_has_name(header, 'user-agent')) then 105 | call append_pair(request%header, 'user-agent', 'http-client/'//VERSION_STRING) 106 | end if 107 | else 108 | ! Set default request headers. 109 | request%header = [pair_type('user-agent', 'http-client/'//VERSION_STRING)] 110 | end if 111 | 112 | ! setting the request data to be send 113 | if(present(data)) then 114 | request%data = data 115 | end if 116 | 117 | ! setting request form 118 | if(present(form)) then 119 | request%form = form 120 | end if 121 | 122 | ! setting request file 123 | if(present(file)) then 124 | request%file = file 125 | end if 126 | 127 | ! Set request timeout. 128 | request%timeout = optval(timeout, -1) 129 | 130 | ! setting username and password for Authentication 131 | if(present(auth)) then 132 | request%auth = auth 133 | end if 134 | 135 | ! Populates the response 136 | client = client_type(request=request) 137 | response = client%client_get_response() 138 | end function new_request 139 | 140 | function new_client(request) result(client) 141 | 142 | !!> This is the constructor for the `client_type` derived type. 143 | 144 | type(request_type), intent(in) :: request 145 | !! Specifies the **HTTP `request`** to send. 146 | type(client_type) :: client 147 | !! A `client_type` object containing the `request` field set to the input `request` object. 148 | 149 | client%request = request 150 | end function new_client 151 | 152 | function client_get_response(this) result(response) 153 | 154 | !!> This function sends an HTTP `request` to a server using the 155 | !!> [fortran-curl](https://github.com/interkosmos/fortran-curl) package 156 | !!> and stores the server's response in a `response_type` 157 | !!> object. 158 | 159 | class(client_type), intent(inout) :: this 160 | !! Contains the HTTP `request` to send. 161 | type(response_type), target :: response 162 | !! Contains the **server's response**. 163 | 164 | type(c_ptr) :: curl_ptr, header_list_ptr 165 | integer :: rc, i 166 | 167 | curl_ptr = c_null_ptr 168 | header_list_ptr = c_null_ptr 169 | 170 | response%url = this%request%url 171 | 172 | curl_ptr = curl_easy_init() 173 | 174 | if (.not. c_associated(curl_ptr)) then 175 | response%ok = .false. 176 | response%err_msg = "The initialization of a new easy handle using the 'curl_easy_init()'& 177 | & function failed. This can occur due to insufficient memory available in the system. & 178 | & Additionally, if libcurl is not installed or configured properly on the system" 179 | return 180 | end if 181 | 182 | ! setting request URL 183 | rc = curl_easy_setopt(curl_ptr, CURLOPT_URL, this%request%url) 184 | 185 | ! setting request method 186 | rc = set_method(curl_ptr, this%request%method, response) 187 | 188 | ! setting request timeout 189 | rc = set_timeout(curl_ptr, this%request%timeout) 190 | 191 | ! setting request body 192 | rc = set_body(curl_ptr, this%request) 193 | 194 | ! setting request authentication 195 | rc = set_auth(curl_ptr, this%request) 196 | 197 | ! prepare headers for curl 198 | call prepare_request_header_ptr(header_list_ptr, this%request%header) 199 | 200 | ! setting request header 201 | rc = curl_easy_setopt(curl_ptr, CURLOPT_HTTPHEADER, header_list_ptr); 202 | 203 | ! setting callback for writing received data 204 | rc = curl_easy_setopt(curl_ptr, CURLOPT_WRITEFUNCTION, c_funloc(client_response_callback)) 205 | 206 | ! setting response content pointer to write callback 207 | rc = curl_easy_setopt(curl_ptr, CURLOPT_WRITEDATA, c_loc(response)) 208 | 209 | ! setting callback for writing received headers 210 | rc = curl_easy_setopt(curl_ptr, CURLOPT_HEADERFUNCTION, c_funloc(client_header_callback)) 211 | 212 | ! setting response header pointer to write callback 213 | rc = curl_easy_setopt(curl_ptr, CURLOPT_HEADERDATA, c_loc(response)) 214 | 215 | ! Send request. 216 | rc = curl_easy_perform(curl_ptr) 217 | 218 | if (rc /= CURLE_OK) then 219 | response%ok = .false. 220 | response%err_msg = curl_easy_strerror(rc) 221 | end if 222 | 223 | ! setting response status_code 224 | rc = curl_easy_getinfo(curl_ptr, CURLINFO_RESPONSE_CODE, response%status_code) 225 | 226 | call curl_easy_cleanup(curl_ptr) 227 | 228 | end function client_get_response 229 | 230 | function prepare_form_encoded_str(curl_ptr, request) result(form_encoded_str) 231 | 232 | !!> This subroutine converts the `request%form` into a **URL-encoded name-value 233 | !!> string** and returns it. 234 | 235 | ! This subroutine takes a request object containing a list of name-value pairs 236 | ! representing the form data. It iterates over the list and URL-encodes each 237 | ! name and value using the curl_easy_escape function, which replaces special 238 | ! characters with their corresponding escape sequences. 239 | ! The encoded name-value pairs are concatenated into a single string, separated 240 | ! by '&' characters. The resulting string is returned 241 | 242 | type(c_ptr), intent(out) :: curl_ptr 243 | !! Pointer to the `curl` handler. 244 | type(request_type), intent(inout) :: request 245 | !! The HTTP `request` to send, which includes the `form` data to be encoded. 246 | character(:), allocatable :: form_encoded_str 247 | !! Stores the **URL Encoded string**. 248 | 249 | integer :: i 250 | if(allocated(request%form)) then 251 | do i=1, size(request%form) 252 | if(.not. allocated(form_encoded_str)) then 253 | form_encoded_str = curl_easy_escape(curl_ptr, request%form(i)%name, & 254 | len(request%form(i)%name)) // '=' // curl_easy_escape(curl_ptr, & 255 | request%form(i)%value, len(request%form(i)%value)) 256 | else 257 | form_encoded_str = form_encoded_str // '&' // & 258 | curl_easy_escape(curl_ptr, request%form(i)%name, len(request%form(i)%name))& 259 | // '=' // curl_easy_escape(curl_ptr, request%form(i)%value, len(request%form(i)%value)) 260 | end if 261 | end do 262 | end if 263 | end function prepare_form_encoded_str 264 | 265 | subroutine prepare_request_header_ptr(header_list_ptr, req_headers) 266 | 267 | !!> This subroutine prepares `headers` in required format(Linked list) for an HTTP request. 268 | !!____ 269 | !!> This subroutine prepares a **linked list** of `headers` for an HTTP request using the 270 | !!> [fortran-curl](https://github.com/interkosmos/fortran-curl) package. 271 | !!> The function takes an array of `pair_type` objects(i.e. `req_headers`) that contain the 272 | !!> **key-value** pairs of the headers to include in the request. 273 | !!> It iterates over the array and constructs a string for each header in the format **`key:value`**. 274 | !!> The subroutine then appends each string to the linked list using the `curl_slist_append` function. 275 | !!> The resulting linked list is returned via the `header_list_ptr` argument. 276 | 277 | type(c_ptr), intent(out) :: header_list_ptr 278 | !! A `Pointer` that is allocated and points to a linked list of headers. 279 | type(pair_type), allocatable, intent(in) :: req_headers(:) 280 | !! The `headers` to be included in the request. 281 | character(:), allocatable :: h_name, h_val, final_header_string 282 | integer :: i 283 | 284 | do i = 1, size(req_headers) 285 | h_name = req_headers(i)%name 286 | h_val = req_headers(i)%value 287 | final_header_string = h_name // ':' // h_val 288 | header_list_ptr = curl_slist_append(header_list_ptr, final_header_string) 289 | end do 290 | end subroutine prepare_request_header_ptr 291 | 292 | function set_method(curl_ptr, method, response) result(status) 293 | !!> This function sets the **HTTP `method`** for the request. 294 | !!____ 295 | !!#### **The `method` argument can take one of the following values:** 296 | !!> `HTTP_GET`, `HTTP_HEAD`, `HTTP_POST`, `HTTP_PUT`, `HTTP_DELETE`, 297 | !!> `HTTP_PATCH`. If any other value is provided, an **error will be thrown**. 298 | 299 | type(c_ptr), intent(out) :: curl_ptr 300 | !! Pointer to the `curl` handler. 301 | integer, intent(in) :: method 302 | !! Specifies the HTTP `method` to use. 303 | type(response_type), intent(out) :: response 304 | !! The HTTP `response` from the server. 305 | integer :: status 306 | !! The `status` of setting HTTP method. 307 | 308 | select case(method) 309 | case(1) 310 | status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'GET' ) 311 | response%method = 'GET' 312 | case(2) 313 | status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'HEAD' ) 314 | response%method = 'HEAD' 315 | case(3) 316 | status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'POST' ) 317 | response%method = 'POST' 318 | case(4) 319 | status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'PUT' ) 320 | response%method = 'PUT' 321 | case(5) 322 | status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'DELETE' ) 323 | response%method = 'DELETE' 324 | case(6) 325 | status = curl_easy_setopt(curl_ptr, CURLOPT_CUSTOMREQUEST, 'PATCH' ) 326 | response%method = 'PATCH' 327 | case default 328 | error stop 'Method argument can be either HTTP_GET, HTTP_HEAD, HTTP_POST, HTTP_PUT, HTTP_DELETE, HTTP_PATCH' 329 | end select 330 | end function set_method 331 | 332 | function set_timeout(curl_ptr, timeout) result(status) 333 | !!> This function sets the `timeout` value **(in seconds)**. 334 | !!> 335 | !!> If the `timeout` value is **less than zero**, it is ignored and a success status is returned. 336 | type(c_ptr), intent(out) :: curl_ptr 337 | !! Pointer to the `curl` handle. 338 | integer(kind=int64), intent(in) :: timeout 339 | !! `Timeout` seconds for request. 340 | integer :: status 341 | !! `Status code` indicating whether the operation was successful. 342 | if(timeout < 0) then 343 | status = 0 344 | else 345 | ! setting the maximum time allowed for the connection to established.(in seconds) 346 | status = curl_easy_setopt(curl_ptr, CURLOPT_CONNECTTIMEOUT, timeout) 347 | ! setting maximum time allowed for transfer operation.(in seconds) 348 | status = curl_easy_setopt(curl_ptr, CURLOPT_TIMEOUT, timeout) 349 | end if 350 | end function set_timeout 351 | 352 | ! This function determines the type of data to include in the `request body` 353 | ! based on the inputs provided. 354 | 355 | ! If `data` member is provided, it is sent as the body of the 356 | ! request. If along with `data` member `file` or `form` or both `file` and `form` members 357 | ! are provided then both `form` and `file` member will be ignored and only `data` member will be 358 | ! sent in request body. `data` argument takes the highest priority. 359 | 360 | ! If only `file` member is provided then `file` is sent as the body of the request. and 361 | ! a default `Content-type` header with value `multipart/form-data` will be set, if no `Content-type` 362 | ! header is provided. If both `form` and `file` members are provided, then `file` 363 | ! and the `form` is sent as part of the body. having default `Content-type` header with value `multipart/form-data` 364 | ! if no `Content-type` header is provided. 365 | ! If data, form, and file are all provided, only data is sent and the form and file 366 | ! inputs are ignored. 367 | ! 368 | ! If only `form` member is provided then `form` data is URL encoded and sent 369 | ! as the body of the request. and a default `Content-type` header with value 370 | ! `application/x-www-form-urlencoded` will be set, if no `Content-type` header is 371 | ! provided 372 | ! 373 | ! 374 | ! data -> data 375 | ! form -> form 376 | ! file -> file 377 | ! data + form + file -> data 378 | ! form + file -> form + file (in multipart/form-data) 379 | ! 380 | ! Note : At a time only one file can be send 381 | function set_body(curl_ptr, request) result(status) 382 | !!> The function sets the request `body`. 383 | !!____ 384 | 385 | !!> This function determines and set the type of data to include in the `request body` 386 | !!> based on the inputs provided to the `request()` procedure. 387 | 388 | !!> The function handles different combinations of `data`, `file`, and `form` members 389 | !!> to decide the content and the default header for the request body. 390 | 391 | !!> - If `data` member is provided, it takes the highest priority and is sent as the 392 | !!> body of the request. Any other provided `file` or `form` members will be ignored, 393 | !!> and only the `data` member will be included in the request body. 394 | 395 | !!> - If only the `file` member is provided, the `file` is sent as the body of the request. 396 | !!> If no `Content-type` header is provided, a default `Content-type` header with value 397 | !!> `multipart/form-data` will be set. 398 | 399 | !!> - If only the `form` member is provided, the `form` data is URL encoded and sent as 400 | !!> the body of the request. If no `Content-type` header is provided, a default `Content-type` 401 | !!> header with value `application/x-www-form-urlencoded` will be set. 402 | 403 | !!> - If both `form` and `file` members are provided, both `form` and `file` data are included 404 | !!> as part of the request body. A default `Content-type` header with value `multipart/form-data` 405 | !!> will be set if no `Content-type` header is provided. 406 | 407 | !!> - If `data`, `form`, and `file` are all provided, only `data` is sent, and the `form` and `file` 408 | !!> inputs are ignored. 409 | 410 | !!> ### **Combination Behavior Table** 411 | 412 | 413 | !!> | Passed Arguments | Request Body | Default Header | Behavior | 414 | !! |--------------------|---------------------------------|-------------------------------|---------------------------------------------------------| 415 | !! | data | data | None | The `data` is sent as the body of the request. | 416 | !! | file | file | multipart/form-data | The `file` is sent as the body of the request with the default header. | 417 | !! | 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. | 418 | !! | data + file | data (file ignored) | None | The `file` member is ignored, and the `data` is sent as the body of the request. | 419 | !! | data + form | data (form ignored) | None | The `form` member is ignored, and the `data` is sent as the body of the request. | 420 | !! | file + form | both file and form | multipart/form-data | Both `form` and `file` are sent as part of the request. | 421 | !! | 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. | 422 | 423 | !!> 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. 424 | 425 | type(c_ptr), intent(out) :: curl_ptr 426 | !! Pointer to the `curl` handle. 427 | type(request_type), intent(inout) :: request 428 | !! The HTTP request 429 | integer :: status 430 | !! An integer value representing the status of the function call. 431 | 432 | integer :: i 433 | type(c_ptr) :: mime_ptr, part_ptr 434 | 435 | ! if only data is passed 436 | if (allocated(request%data)) then 437 | status = set_postfields(curl_ptr, request%data) 438 | 439 | ! if file is passsed 440 | else if (allocated(request%file)) then 441 | mime_ptr = curl_mime_init(curl_ptr) 442 | part_ptr = curl_mime_addpart(mime_ptr) 443 | status = curl_mime_filedata(part_ptr, request%file%value) 444 | status = curl_mime_name(part_ptr, request%file%name) 445 | 446 | ! if both file and form are passed 447 | if(allocated(request%form)) then 448 | do i=1, size(request%form) 449 | part_ptr = curl_mime_addpart(mime_ptr) 450 | status = curl_mime_data(part_ptr, request%form(i)%value, CURL_ZERO_TERMINATED) 451 | status = curl_mime_name(part_ptr, request%form(i)%name) 452 | end do 453 | end if 454 | status = curl_easy_setopt(curl_ptr, CURLOPT_MIMEPOST, mime_ptr) 455 | 456 | ! setting the Content-Type header to multipart/form-data, used for sending binary data 457 | if (.not. pair_has_name(request%header, 'Content-Type')) then 458 | call append_pair(request%header, 'Content-Type', 'multipart/form-data') 459 | end if 460 | 461 | ! if only form is passed 462 | else if (allocated(request%form)) then 463 | request%form_encoded_str = prepare_form_encoded_str(curl_ptr, request) 464 | status = set_postfields(curl_ptr, request%form_encoded_str) 465 | 466 | ! setting the Content-Type header to application/x-www-form-urlencoded, used for sending form data 467 | if (.not. pair_has_name(request%header, 'Content-Type')) then 468 | call append_pair(request%header, 'Content-Type', 'application/x-www-form-urlencoded') 469 | end if 470 | else 471 | ! No curl function was called so set status to zero. 472 | status = 0 473 | end if 474 | 475 | end function set_body 476 | 477 | function set_postfields(curl_ptr, data) result(status) 478 | !!> Set the data to be sent in the HTTP POST request body. 479 | !!____ 480 | !!> Use as helper function by `set_body` procedure to set request body 481 | type(c_ptr), intent(inout) :: curl_ptr 482 | !! Pointer to the CURL handle. 483 | character(*), intent(in), target :: data 484 | !! The data to be sent in the request body. 485 | integer :: status 486 | !! An integer indicating whether the operation was successful (0) or not (non-zero). 487 | 488 | status = curl_easy_setopt(curl_ptr, CURLOPT_POSTFIELDS, c_loc(data)) 489 | status = curl_easy_setopt(curl_ptr, CURLOPT_POSTFIELDSIZE_LARGE, len(data, kind=int64)) 490 | 491 | end function set_postfields 492 | 493 | function set_auth(curl_ptr, request) result(status) 494 | !!> Set the user name and password for Authentication. 495 | !!_____ 496 | !!> It sends the user name and password over the network in plain text, easily captured by others. 497 | type(c_ptr), intent(out) :: curl_ptr 498 | !! Pointer to the CURL handle. 499 | type(request_type), intent(inout) :: request 500 | !! The HTTP request 501 | integer :: status 502 | !! An integer indicating whether the operation was successful (0) or not (non-zero). 503 | 504 | if(allocated(request%auth)) then 505 | status = curl_easy_setopt(curl_ptr, CURLOPT_HTTPAUTH, CURLAUTH_BASIC) 506 | status = curl_easy_setopt(curl_ptr, CURLOPT_USERNAME, request%auth%name) 507 | status = curl_easy_setopt(curl_ptr, CURLOPT_PASSWORD, request%auth%value) 508 | else 509 | ! No curl function was called so set status to zero. 510 | status = 0 511 | end if 512 | end function set_auth 513 | 514 | function client_response_callback(ptr, size, nmemb, client_data) bind(c) 515 | !!> This function is a `callback` function used by the fortran-curl package to handle HTTP responses. 516 | !!> It is called for each `chunk` of data received from the server and appends the data to a 517 | !!> `response_type` object. 518 | !!>_____ 519 | !!> It is called for each `chunk` of data received from the server and appends the data to a `response_type` 520 | !!> object passed(i.e `client_data`). The function takes four input arguments: `ptr`, `size`, `nmemb`, 521 | !!> and `client_data`. `ptr` is a pointer to the received data buffer, `size` specifies the size of each 522 | !!> data element, `nmemb` specifies the number of data elements received, and `client_data` is a pointer to 523 | !!> a `response_type` object. The function uses `c_f_pointer` to convert the C pointer to a Fortran pointer and 524 | !!> appends the received data to the `content` field of the `response_type` object. The function returns an integer 525 | !!> value representing the **number of bytes received.** 526 | 527 | type(c_ptr), intent(in), value :: ptr 528 | !! Pointer to the CURL handle. 529 | integer(kind=c_size_t), intent(in), value :: size 530 | !! Specifies the size of each data element. 531 | integer(kind=c_size_t), intent(in), value :: nmemb 532 | !! Specifies the number of data elements received. 533 | type(c_ptr), intent(in), value :: client_data 534 | !! Points to a response_type object. 535 | integer(kind=c_size_t) :: client_response_callback 536 | !! The number of bytes received. 537 | type(response_type), pointer :: response 538 | character(len=:), allocatable :: buf 539 | 540 | client_response_callback = int(0, kind=c_size_t) 541 | 542 | ! Are the passed C pointers associated? 543 | if (.not. c_associated(ptr)) return 544 | if (.not. c_associated(client_data)) return 545 | 546 | ! Convert C pointer to Fortran pointer. 547 | call c_f_pointer(client_data, response) 548 | if (.not. allocated(response%content)) response%content = '' 549 | 550 | ! Convert C pointer to Fortran allocatable character. 551 | call c_f_str_ptr(ptr, buf, nmemb) 552 | if (.not. allocated(buf)) return 553 | response%content = response%content // buf 554 | deallocate (buf) 555 | response%content_length = response%content_length + nmemb 556 | 557 | ! Return number of received bytes. 558 | client_response_callback = nmemb 559 | 560 | end function client_response_callback 561 | 562 | function client_header_callback(ptr, size, nmemb, client_data) bind(c) 563 | !!> This function is a `callback` function used by the `fortran-curl` package to handle HTTP headers. 564 | !!>_____ 565 | !!> It is called for each header received from the server and stores the header in an `header` member 566 | !!> of `response_type` object. 567 | type(c_ptr), intent(in), value :: ptr 568 | !! Pointer to the CURL handle. that points to the received header buffer. 569 | integer(kind=c_size_t), intent(in), value :: size 570 | !! Specifies the size of each header element. 571 | integer(kind=c_size_t), intent(in), value :: nmemb 572 | !! Specifies the number of header elements received. 573 | type(c_ptr), intent(in), value :: client_data 574 | !! Pointer to a `response_type` object. 575 | integer(kind=c_size_t) :: client_header_callback 576 | !! The number of bytes received. 577 | type(response_type), pointer :: response 578 | character(len=:), allocatable :: buf, h_name, h_value 579 | integer :: i 580 | 581 | client_header_callback = int(0, kind=c_size_t) 582 | 583 | ! Are the passed C pointers associated? 584 | if (.not. c_associated(ptr)) return 585 | if (.not. c_associated(client_data)) return 586 | 587 | ! Convert C pointer to Fortran pointer. 588 | call c_f_pointer(client_data, response) 589 | 590 | ! Convert C pointer to Fortran allocatable character. 591 | call c_f_str_ptr(ptr, buf, nmemb) 592 | if (.not. allocated(buf)) return 593 | 594 | ! Parsing Header, and storing in array of pair_type object 595 | i = index(buf, ':') 596 | if(i /= 0 .and. len(buf) > 2) then 597 | h_name = trim(buf(:i-1)) 598 | h_value = buf(i+2 : ) 599 | h_value = h_value( : len(h_value)-2) 600 | if(len(h_value) > 0 .and. len(h_name) > 0) then 601 | call append_pair(response%header, h_name, h_value) 602 | ! response%header = [response%header, pair_type(h_name, h_value)] 603 | end if 604 | end if 605 | deallocate(buf) 606 | 607 | ! Return number of received bytes. 608 | client_header_callback = nmemb 609 | 610 | end function client_header_callback 611 | 612 | end module http_client 613 | --------------------------------------------------------------------------------