curlで送ったPOSTリクエストがリダイレクトされる時-XPOSTを使うか否かで挙動が変わる

# 結論

```
$ curl -X POST -d "" -L <url>
```

でPOSTリクエストを送ると301/302/303リダイレクト後もPOSTが適用される。
一方

```
$ curl -d "" -L <url>
```

とPOSTリクエストを送るとリダイレクト時にGETに切り替わる。

## 補足
-Xはリクエストメソッドを指定するオプションです。

-dはPOSTリクエストで送るデータを指定するオプションです。
このコマンドを利用すると-Xで指定せずともPOST扱いになります。

-Lはリダイレクトを許可するオプションです。

## 実行環境

- macOS 10.14.6
- curl 7.54.0

# マニュアルの記述

```
$ man curl
```

の内容にしっかり記述がありました。

```
-L, --location

    (前略)

    When curl follows a redirect and the request is not a plain GET (for example POST or PUT), it will do the following request with
    a  GET  if  the HTTP response was 301, 302, or 303. If the response code was any other 3xx code, curl will re-send the following
    request using the same unmodified method.

    You can tell curl to not change the non-GET request method to GET after a 30x response by using the dedicated options for  that:
    --post301, --post302 and --post303.
```

基本的に-LオプションはPOSTを301/302/303リダイレクトする時にGETに切り替えるみたいです。
そしてそれを拒否するためのオプションも用意されています。

```
-X, --request <command>

    (前略)

    The  method  string  you  set  with -X, --request will be used for all requests, which if you for example use -L, --location may
    cause unintended side-effects when curl doesn't change request method according to the HTTP 30x response codes - and similar.

    (後略)
```

-Xオプションで指定したメソッドは全てのリクエストに適用されるから-Lオプションの上記の仕様を上書きするみたいです。

# 検証

DockerでNginxサーバー建ててリダイレクトを設定し、curlコマンドを使って動作を検証しました。

## 設定

```
$ tree
.
├── docker-compose.yml
├── html
│   └── after.html
└── nginx.conf
```

```docker-compose.yml
version: '3'

services:
  nginx:
    image: nginx:1.17.3
    ports:
      - 8080:80
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./html:/var/www/html
```

```nginx.conf
server {
  listen 80;
  root /var/www/html;
  rewrite ^/before.html$ after.html redirect;
}
```

```after.html
Redirected.
```

curlでbefore.htmlを取得するとafter.htmlに302リダイレクトされます。

```
$ curl -L http://localhost:8080/before.html
Redirected.
```

## 挙動の確認

-vオプションをつけるとcurlの実行結果の詳細を見ることが出来ます。
このオプションを使って２つの送信方法の結果のdiffを見ようと思います。

```
$ curl -v -X POST -d "" -L http://localhost:8080/before.html 2> result_x_on
$ curl -v -d "" -L http://localhost:8080/before.html 2> result_x_off
$ diff result_x_on result_x_off
```

diffの結果からはタイムスタンプ等本件と関係ない結果を省いています。

```diff
1d0
< Note: Unnecessary use of -X or --request, POST is already inferred.
28c27
< > POST /after.html HTTP/1.1
---
> > GET /after.html HTTP/1.1
33c32
< < HTTP/1.1 405 Not Allowed
---
> < HTTP/1.1 200 OK
```

このように、-X POSTを指定したものは302リダイレクト後もPOSTリクエストをしており、405を返されています。一方-X POSTを省略したものは302リダイレクト後はGETリクエストをしており、200を返されています。

-X POSTを指定したものは1行目で「POSTって事は分かってるから-X必要ないよ」と言われていますが、このように挙動が変わるため注意が必要です。

# 考察

## POSTを301/302/303リダイレクトする時の挙動

多くのブラウザではPOSTを301/302/303リダイレクトする時GETに切り替えるようです。

> GET メソッドは変更しません。
他のメソッドは GET に変更されるかもしれません。
>
>[HTTP のリダイレクト - HTTP | MDN (https://developer.mozilla.org/ja/docs/Web/HTTP/Redirections)](https://developer.mozilla.org/ja/docs/Web/HTTP/Redirections)

Mozillaのドキュメントでは上記の記述があり、わりと曖昧なようです。

> Note: For historical reasons, a user agent MAY change the request
method from POST to GET for the subsequent request.  If this
behavior is undesired, the 307 (Temporary Redirect) status code
can be used instead.
>
>[RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content (https://tools.ietf.org/html/rfc7231)](https://tools.ietf.org/html/rfc7231)

RFCでは上記のように注釈されています。
「歴史的な理由でこういう挙動になってるから嫌だったら307を返してね」
とのことです。

## -Xオプションの使い方

上記のブラウザの仕様を見る限り、おそらく-Xオプションの「リダイレクト時もメソッドを維持する」という仕様を求める状況の方が特殊なのではないでしょうか？
今まで思考停止で
「POSTだったら-XPOSTに-dでデータ付けて〜😊」
とやっていましたが基本的に-dだけにした方が安全じゃないですかね？

# 実際に問題になったシチュエーション

GoogleAppsScript(GAS)のdoPost関数のテストをcurlで行おうとしたらリダイレクトを求められ、-Xオプションと-Lオプションを併用した上に上記の仕様を理解しておらずハマりました。
ご注意ください。

[GASのdoPost関数をcurlでテストする時リダイレクトが必要なら-Xオプションを使わない](https://qiita.com/nkitgamesake/items/9e66ef60831d51105bc0)