Fortranパッケージhttp-clientを使う

## はじめに

`http-client`はfortranでHTTPプロトコルを用いて情報を取得できるパッケージになります。  
`http-client`は`fortran-curl`を利用しており、`fortran-curl`は`libcurl`を利用しています。  

https://github.com/fortran-lang/http-client

## 環境構築

`http-client`は`fortran-curl`と`libcurl`を利用していることから、自力でコンパイルを行うのは面倒なので公式サイトで推奨している`fpm`を使ってコンパイルを行います。その前段階として環境構築を行います。  

### fpmのダウンロード

fpmはpythonで作成されているが、Linuxで一番簡単なのはバイナリデータをダウンロードして組み込むことです。MacOSの場合は`Homebrew`、Windowsの場合は`Winget`で行うとよい。`Anaconda`もあり。pythonからpipでインストールしても可。(環境によってはパスを張る必要がある。)

https://fpm.fortran-lang.org/install/index.html

### gitやlibcurlやlibcurlのヘッダーをインストール

```bash
# Ubuntu 22.04
sudo apt install git libcurl4-openssl-dev
# CentOS Stream 9
dnf install git libcurl-devel
```

※Windowsの場合はlibcurlやlibcurlのヘッダーどうすれば適切に実行できるのか不明。おとなしく[MSYS2](https://www.msys2.org/)やWSL2使ってください。

### gitの設定

```bash
git config --global user.name "<user>"
git config --global user.email "<e-mail>"
git config --global http.proxy "<[protocol://][user[:password]@]proxyhost[:port]>" #必要なら
```

## http-clientの使用方法

https://vldb.gsi.go.jp/sokuchi/surveycalc/api_help.html

ここではこの中のジオイド高を求めるAPIを用いて、httpリクエストを行い、json形式の文字列を取得し、そこからジオイド高を取得することを行う。APIの詳細については上記のURLを参照してください。

### fpmの設定

fpmの詳しい使い方については

https://qiita.com/implicit_none/items/6a65ac1b83efefc782db

こちらを見ていただくとして一連の構築手順を提示する。  

#### プロジェクトの作成

```bash
fpm new geoid_api
```

を実行すると、カレントディレクトリにgeood_apiディレクトリが作成される。中身は以下の通り、

```
.
├── .git
├── app
│   └── main.f90
├── src
│   └── geoid_api.f90
├── test
│   └── check.f90
├── fpm.toml
└── README.md
```

appディレクトリにあるものはメインルーチンが入っているソースコードを入れる。
srcディレクトリにあるものはモジュール形式で格納されているものを入れる。
testディレクトリにあるものは、srcディレクトリのモジュールをテストするためのメインルーチンが入っているソースコードが入る。

#### fpm.toml

dependencies項に今回使用するパッケージ名を指定する。
httpリクエストをするパッケージ名は`http`、jsonを扱うパッケージ名は`json-fortran`なのでそのように記入する。  
名前の見つけ方については、それぞれのgithubのコードの`fpm.toml`に名前が記載されているのでそれを見るか使い方を見る。

```toml:fpm.toml
[dependencies]
http = { git = "https://github.com/fortran-lang/http-client" }
json-fortran = { git = "https://github.com/jacobwilliams/json-fortran" }
stdlib = "*"
```

#### ソースコード

以下の通り作成する。  

```fortran:./app/main.f90
program main
    use, intrinsic :: iso_fortran_env, only: real64
    use geoid_api, only: get_geoidHeight
    implicit none
    real(real64) :: lon, lat, geoidHeight

    ! 国土地理院の座標
    lon = 140.084556_real64 ! f0.8
    lat = 36.104611_real64 ! f0.8
    geoidHeight = get_geoidHeight(lon, lat)

    print *, lon, " ", lat, " ", geoidHeight

end program main
```

```fortran:./src/geoid_api.f90
module geoid_api
    use, intrinsic :: iso_fortran_env, only: real64
    use http, only: response_type, request
    use json_module, only: json_file
    use stdlib_strings, only: to_string, strip
    use stdlib_str2num, only: to_num
    implicit none
    private

    public :: get_geoidHeight

contains
    impure elemental function get_geoidHeight(lon, lat) result(geoidHeight)
        real(real64), intent(in):: lon !! lon
        real(real64), intent(in) :: lat !! lat
        real(real64) :: geoidHeight !! ジオイド高[m]
        type(json_file) :: json_f
        type(response_type) :: response
        character(:), allocatable :: lon_str, lat_str, geoidHeight_str
        character(*), parameter :: outputType = 'json'
        character(*), parameter :: baseurl = 'https://vldb.gsi.go.jp/sokuchi/surveycalc/geoid/calcgh/cgi/geoidcalc.pl'
        character(:), allocatable :: url

        ! 文字列に変換
        !allocate (character(len=12) :: lon_str) ! 140.08455600 f12.8型->12文字
        !allocate (character(len=12) :: lat_str) ! 36.10461100 f12.8型->12文字
        !write (lon_str, '(f0.8)') lon ! 左詰めにするためf0で設定
        !write (lat_str, '(f0.8)') lat ! 左詰めにするためf0で設定
        lon_str = strip(to_string(lon, '(f12.8)'))
        lat_str = strip(to_string(lat, '(f12.8)'))

        !url = baseurl//'?'//"outputType="//outputType//"&latitude="//trim(lat_str)//"&longitude="//trim(lon_str)
        url = baseurl//'?'//"outputType="//outputType//"&latitude="//lat_str//"&longitude="//lon_str

        response = request(url=url)
        if (.not. response%ok) then
            print *, 'Error message:', response%err_msg
            stop
        end if
        print *, 'Response Code    :', response%status_code
        print *, 'Response Length  :', response%content_length
        print *, 'Response Method  :', response%method
        print *, 'Response Content :', response%content
        !content ※lat,lon,geoidが文字列で格納されているので注意
        !--------------
        ! { "OutputData":
        !    {
        !        "latitude": "36.10461100" ,
        !        "longitude": "140.08455600",
        !        "geoidHeight": "40.1986"
        !    }
        ! }
        call json_f%initialize()
        call json_f%deserialize(response%content)
        call json_f%get(path='OutputData.geoidHeight', val=geoidHeight_str)
        call json_f%destroy()
        ! 文字列からrealに
        !read (geoidHeight_str, *) geoidHeight
        geoidHeight = to_num(geoidHeight_str, geoidHeight)

    end function get_geoidHeight
end module geoid_api
```

数値を文字列に、文字列を数値に変換する際~~面倒なことを行っているが~~`stdlib`を利用することにした、注意すべき点は、`response%content`はhttpリクエストで返却された本体部分になっており、改行を含めた文字列がそこに格納されている。`call json_f%deserialize(response%content)`により、json文字列をjsonのポインタに変換し格納する。  


#### ビルド

```bash
fpm build
```

buildディレクトリにgithubからダウンロードしたものやコンパイルしたものが一式格納されている。  

#### インストール

```bash
fpm install --prefix .
```

binディレクトリにgeoid_apiプログラムが作成される。  

#### 実行

```bash
$ ./bin/geoid_api
 Response Code    :         200
 Response Length  :                   94
 Response Method  :GET
 Response Content :{"OutputData":{"latitude":"36.104611000","longitude":"140.084556000","geoidHeight":"40.1986"}}
   140.08455599999999          36.104610999999998          40.198599999999999  
```

## 詳しい使い方

https://http-client.fortran-lang.org/

このサイトの`Procedures`と`Derived Types`のうちglobalになっているものだけを見るとよい。  
現時点で、globalで存在するものは以下の通り

Procedures

- [append_pair](https://http-client.fortran-lang.org/proc/append_pair.html): pair_type型に関係する  
- [get_pair_value](https://http-client.fortran-lang.org/proc/get_pair_value.html): pair_type型に関係する  
- [pair_has_name](https://http-client.fortran-lang.org/proc/pair_has_name.html): pair_type型に関係する  
- [request](https://http-client.fortran-lang.org/interface/request.html): response_type型を返す  

Derived Types

- [pair_type](https://http-client.fortran-lang.org/type/pair_type.html): headers等を格納する型  
- [request_type](https://http-client.fortran-lang.org/type/request_type.html)
- [response_type](https://http-client.fortran-lang.org/type/response_type.html)

## プロキシについて

直接プロキシを設定する項目は今のところないが、`libcurl`を利用しているので環境変数で設定する。`~/.curlrc`は読み込まないので注意

### 環境変数

- http_proxy=scheme://[user:passwd@]proxy.server:port
- HTTPS_PROXY
- https_proxy
- ALL_PROXY
- NO_PROXY
- no_proxy
- CURL_CA_BUNDLE: CA証明書のパス

※http_prxoyは小文字のみ、それ以外は両方OK。両方ある場合は小文字優先。
※proxyにBasic認証がある場合はuser:passwdを入れる。userやpasswdに記号（" # < > ` { } ? =など）が入っている場合はURLエンコードを忘れずに。