RUST製ちょっと便利な cURL !? Hurl 

## 1. Hurl とはなにか
- [Hurl とは](https://hurl.dev/)シンプルなプレーンテキスト形式で定義されたHTTPリクエストを実行するコマンドラインツールです。 
    - [Githubのリポジトリ](https://github.com/Orange-OpenSource/hurl) は Star 7.6k, Fork 227
- リクエストを連続で送る際に、１つ目のリクエストのレスポンスを[キャプチャ](https://hurl.dev/docs/capturing-response.html)して次のリクエスト情報に組み込んだり、ヘッダやボディのレスポンスやステータスコードの情報を簡易でチェック([アサーション](https://hurl.dev/docs/asserting-response.html#asserting-response))することができます。
- Rustで作られた軽量なバイナリであり、内部では libcurl(curl)で動いています。
    - cURLと同じような意味論で同じようにオプションを指定できます。

## 2. Hurlの特徴

|||
|:---|:--|
|テキスト形式|オペレーター(devops)とデベロッパーの両方が扱いやすい。|
|CLI|ローカル開発および継続的インテグレーションで扱いやすい。|
|シングルバイナリ|ランタイム不要で簡単にインストール可能。|

## 3. 導入方法
導入方法は[公式サイト](https://hurl.dev/docs/installation.html)より、抜粋してますが、下記のようになります。

### Linux
```
$ INSTALL_DIR=/tmp
$ VERSION=4.2.0
$ curl --silent --location https://github.com/Orange-OpenSource/hurl/releases/download/$VERSION/hurl-$VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $INSTALL_DIR
$ export PATH=$INSTALL_DIR/hurl-$VERSION:$PATH
```

### Debian / Ubuntu
```
$ VERSION=4.2.0
$curl --location --remote-name https://github.com/Orange-OpenSource/hurl/releases/download/$VERSION/hurl_$VERSION_amd64.deb
$ sudo apt update && sudo apt install ./hurl_$VERSION_amd64.deb
```

### Alpine
```
$ apk add --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing hurl
```

### macOS
```
$ brew install hurl
```

### Windows
[Githubのリリース](https://github.com/Orange-OpenSource/hurl/releases)のページから、インストーラーなどを入手可能

## 4. 実際に導入してみる
私の環境は Mac なので brew で入れてみます。
`$ brew install hurl`

コマンドがあるか確かめるため、バージョン情報を出してみます。
```
$ hurl -V
hurl 4.2.0 (x86_64-apple-darwin21.0) libcurl/7.79.1 (SecureTransport) LibreSSL/3.3.6 zlib/1.2.11 nghttp2/1.45.1
Features (libcurl):  alt-svc AsynchDNS HSTS HTTP2 IPv6 Largefile libz NTLM NTLM_WB SPNEGO SSL UnixSockets
Features (built-in): brotli
```

## 5. 実際に動かしてみる

### 5.1 リクエストの呼び出し
Hurl では下記の２つの方法でリクエストを呼び出すことができます。

1. 入力用のファイル(Hurlファイル)を指定して実行
`$ hurl test.hurl`

```
$ cat test.hurl
GET https://httpbin.org/get
```

2. 標準入力から読み取り (入力ファイルが指定されていない場合)
`$ echo GET https://httpbin.org/get | hurl`

### 5.2 リクエストの出力
入力用のファイルから読み込む場合と、標準入力から読み取る場合もアウトプットは変わらず、基本的には標準出力に表示されます。

```
$ hurl test.hurl
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "User-Agent": "hurl/4.2.0",
    "X-Amzn-Trace-Id": "Root=1-65af2091-3f584dce1f74589d40c462af"
  },
  "origin": "***.***.***.1",
  "url": "https://httpbin.org/get"
}
```

```
$ echo GET https://httpbin.org/get | hurl
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "User-Agent": "hurl/4.2.0",
    "X-Amzn-Trace-Id": "Root=1-65af1093-2d0deca2663982c22d966733"
  },
  "origin": "***.***.***.1",
  "url": "https://httpbin.org/get"
}
```

### 5.3 ファイルへの出力の吐き出し
出力をファイルに保存する場合はオプション(-o ないしは　--output)の指定が必要です。

```
$ hurl -o output test.hurl
```

```
$ cat output
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "User-Agent": "hurl/4.2.0",
    "X-Amzn-Trace-Id": "Root=1-65af212c-5c48bd556d1bba63264aa9a0"
  },
  "origin": "***.***.***.1",
  "url": "https://httpbin.org/get"
}
```

## 6. Hurlファイルについて
Hurlファイルは１つないしは、複数のHTTPリクエストで構成された、`.hurl` の拡張子のファイルです。
また、HurlファイルはUTF-8でエンコードする必要があります。

### 6.1 コメント
`#` で始まる行は行末までがコメントとなります。

```
# test Hurl file
GET https://httpbin.org/get
```

### 6.2 アサーション
HTTPレスポンスの様々なプロパティをテストすることができます

アサートは暗黙的なもの 
- バージョン
- ステータスコード
- ヘッダ

と、[Asserts] セクションで明示的なものがあります。個々の詳細は[公式doc](https://hurl.dev/docs/asserting-response.html#explicit-asserts)をご確認ください。

- status
- header
- url
- cookie
- body
- bytes
- xpath
- jsonpath
- regex
- sha256
- md5
- variable
- duration
- certificate

リクエスト/レスポンスの区切り文字は HTTP <STATUS-CODE> で、この区切り文字の後に暗黙的なアサートがあり、その後に明示的なチェックを含む [Asserts] セクションを並べます。


下記の例では HTTP ステータスコードが２００であるかをアサーションしてみます。
```
$ cat test.hurl
GET https://httpbin.org/get
HTTP 200
```

#### 6.2.1 アサーションの成功
アサーションが成功すると、想定した標準出力を得られます。
```
$ hurl test.hurl
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "User-Agent": "hurl/4.2.0",
    "X-Amzn-Trace-Id": "Root=1-65af72d8-2e122cd21e42aa9824e20dd4"
  },
  "origin": "***.***.***.1",
  "url": "https://httpbin.org/get"
}
```

#### 6.2.2 アサーションの失敗
アサーションを失敗させるため下記のように書き換えます。
```
$ cat test.hurl
GET https://httpbin.org/get
HTTP 201
```

アサーションが失敗時は下記のようにエラーが表示されます。
```
$ hurl test.hurl
error: Assert status code
  --> test.hurl:2:6
   |
 2 | HTTP 201
   |      ^^^ actual value is <200>
   |
```


### 6.3 キャプチャ(値の取得）
HTTPのレスポンスの情報を変数に保存して、後のHTTPリクエストなどで利用することができます。

キャプチャの構造は下記のような形です
- 変数名 : クエリ

例示すると下記のような形です。
- my_bar:status

クエリはHTTPレスポンスから値を抽出するために使用され、次のタイプがあります。
個々の詳細は[公式doc](https://hurl.dev/docs/capturing-response.html#query)をご確認ください。

- status
- header
- url
- cookie
- body
- bytes
- xpath
- jsonpath
- regex
- variable
- duration
- certificate

キャプチャを使うことで、CSRFや、リダイレクトなどに柔軟に対応できるようになります。下記はリダイレクトの動きを含めたHurlファイルの例です。

```
$ cat test.hurl
GET https://httpbin.org/redirect-to?url=%2Fget&status_code=302
HTTP 302
[Captures]
next_url: header "Location"
GET https://httpbin.org{{next_url}}
```

## 7. オプションについて
HurlのオプションはcURLと全く同じ意味を持ちます。１つ前の章のリダイレクトする処理は下記のように代替できます。

```
$ cat test.hurl
GET https://httpbin.org/redirect-to?url=%2Fget&status_code=302

$ hurl --location test.hurl
```


ただし、コマンドラインで指定されたオプションは、すべての Hurl ファイルのエントリに対して定義されます。`Options` セクションで特定のエントリのみにオプションを定義することもできます。
```
GET https://example.org
HTTP 301

GET https://example.org
[Options]
location: true
HTTP 200
```

オプションについては下記のようなものなどがあります。

- `--delay`
    - 各リクエストの前の遅延を設定

- `--http3`
    - URL 内のホストに対して HTTP/3 を試行

- `-i` , `--include`
    - 出力にHTTPヘッダーを含める。

- `-k`, `--insecure`
    - Hurl が「安全でない」SSL 接続と転送を実行することを明示的に許可

- `--retry <NUM>`
    - 再試行の最大数。0 は再試行なし、-1 は無制限に再試行します。エラー (アサート、キャプチャ、ランタイムなど) が発生すると、再試行が行われる。

- `-v`, `--verbose`
    - 標準エラー ストリームでの詳細出力をオンにする。
        - 「>」で始まる行は、Hurl によって送信されたデータを意味します。
        - 「<」で始まる行は、Hurl が受信したデータを意味します。
        - 「*」で始まる行は、Hurl によって提供される追加情報を意味します。

その他のオプションについては[公式doc](https://hurl.dev/docs/manual.html#options)をご確認ください。

## 8. あとがき
cURLを含んだ複雑なシェルスクリプトを場合によっては少しシンプルな表現に書き換えられるかもしれないHurlを見てみました。私としてはcURLを愛用しておりますが、今後はHurlの選択肢も合わせて検討してみようかと思います。

また、比較的安全なコードがかけるということで Rust製 のツールやミドルウェアなどが緩やかに増えて言っている印象があります。今後はそれらのツールなども検証してみたいと思っております。

この記事がどなたかの参考になれば幸いです。