Windows 上の curl で REST API に日本語を POST する 4 つの方法

Windows 上の curl で日本語 UTF-8 を含む JSON を POST する方法をいくつか試し、以下の方法でうまくいきました。

- Git Bash でファイルを読み込む
- Git Bash で iconv で変換して標準入力から読み込む
- ワールドワイド言語サポートで Unicode UTF-8 を使用して Git Bash を使う
- WSL を使う
    - 普通に使う (接続先が外部の場合)
    - `networkingMode=Mirrored` で使う


Windows 11 24H2 で Windows Terminal を使って動作確認しました。


## 実験環境の準備

https://fastapi.tiangolo.com/tutorial/body/ に従って適当な REST のサービスを作りました。

```console:powershell
mkdir fastapi-request-body
cd .\fastapi-request-body\
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install "fastapi[standard]"
```

```main.py
from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item
```

```console:powershell
fastapi dev main.py
```


## 成功例: Git Bash で英語で記述

```console:bash
$ curl --json '{"name": "water", "price": 120}' http://127.0.0.1:8000/items/
{"name":"water","description":null,"price":120.0,"tax":null}
```


## 失敗例: Git Bash で日本語を直接記述

```console:bash
$ curl --json '{"name": "水", "price": 120}' http://127.0.0.1:8000/items/
{"detail":"There was an error parsing the body"}
```


## Git Bash でファイルを読み込む

カレント ディレクトリーに UTF-8 で BODY を記述しておき、`-d @file` の形式で読み込みます。

```item.json
{"name": "水", "price": 120}
```

```console:bash
$ curl --json @item.json http://127.0.0.1:8000/items/
{"name":"水","description":null,"price":120.0,"tax":null}
```


## Git Bash で iconv で変換して標準入力から読み込む

`-d @-` の形式で、UTF-8 に変換した文字列を標準入力から読み込みます。

```console:bash
$ echo '{"name": "水", "price": 120}' | iconv -t UTF-8 | curl --json @- http://127.0.0.1:8000/items/
{"name":"水","description":null,"price":120.0,"tax":null}
```


## ワールドワイド言語サポートで Unicode UTF-8 を使用して Git Bash を使う

この方法は、Windows のデフォルトの動作に依存するプログラムが正しく動作しなくなる可能性があるため、おすすめしません。

設定 > 時刻と言語 > 言語と地域 > 管理用の言語の設定 > Unicode 対応でないプログラムの言語 > システム ロケールの変更 > ベータ: ワールドワイド言語サポートで Unicode UTF-8 を使用 にチェックして再起動。

あとは普通に使うだけです。

```console:bash
$ curl --json -d '{"name": "水", "price": 120}' http://127.0.0.1:8000/items/
{"name":"水","description":null,"price":120.0,"tax":null}
```


## WSL を使う

### 普通に使う (接続先が外部の場合)

接続先が外部の場合は普通に curl を使うだけです。以下は外部のフリーな API を使った例です。

```console:wsl-ubuntu-bash
$ curl --json '{"name": "水", "price": 120}' https://echo.free.beeceptor.com
{
  "method": "POST",
  "protocol": "https",
  "host": "echo.free.beeceptor.com",
  "path": "/",
  "ip": "xxx.xxx.xxx.xxx:42088",
  "headers": {
    "Host": "echo.free.beeceptor.com",
    "User-Agent": "curl/8.5.0",
    "Content-Length": "29",
    "Accept": "*/*",
    "Content-Type": "application/json",
    "Accept-Encoding": "gzip"
  },
  "parsedQueryParams": {},
  "parsedBody": {
    "name": "水",
    "price": 120
  }
}
```

### `networkingMode=Mirrored` で使う

接続先がホスト環境の場合は、素朴にやると失敗してしまいます。

```console:wsl-ubuntu-bash
$ curl --json '{"name": "水", "price": 120}' http://127.0.0.1:8000/items/
curl: (7) Failed to connect to 127.0.0.1 port 8000 after 0 ms: Couldn't connect to server
```

しかし、`%USERPROFILE%` ディレクトリーに .wslconfig を作成し、`networkingMode=Mirrored` を記述してから実行するとうまくいきます ([Mirrored mode networking](https://learn.microsoft.com/en-us/windows/wsl/networking#mirrored-mode-networking))。

```.wslconfig
[wsl2]
networkingMode=Mirrored
```

```console:powershell
wsl --shutdown
```

```console:wsl-ubuntu-bash
$ curl --json '{"name": "水", "price": 120}' http://127.0.0.1:8000/items/
{"name":"水","description":null,"price":120.0,"tax":null}
```


## 最後に

以上のうち、私のおすすめは Git Bash で iconv で変換して標準入力から読み込む方法です。

ほかにも良い方法があったらコメントいただけると嬉しいです。