curl備忘録2020

公開:2020/09/25
更新:2020/09/25
17 min読了の目安(約10400字TECH技術記事

はじめに

https://qiita.com/hijili/items/63a7ed4885974810e036

man読めって話なんですが、curlのmanってボリューミーなわりに使うとこ一部なので、
そこそこ使うけど探しにくかった的な使い方をメモる備忘録です。

という上記Qiitaのview数が多かったけど更新してないなあ…と思ってたので、zennに移築してみます。この番組は以下のバージョン・WSL環境(Fedora Remix for WSL)下においてお届けしております。(versionでプロトコルとか出るようになってたんやな…)

$ curl --version
curl 7.66.0 (x86_64-redhat-linux-gnu) libcurl/7.66.0 OpenSSL/1.1.1g-fips zlib/1.2.11 brotli/1.0.7 libidn2/2.3.0 libpsl/0.21.0 (+libidn2/2.2.0) libssh/0.9.4/openssl/zlib nghttp2/1.41.0
Release-Date: 2019-09-11
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS brotli GSS-API HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz Metalink NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets

$ uname -a
Linux xxx 4.4.0-18362-Microsoft #1049-Microsoft Thu Aug 14 12:01:00 PST 2020 x86_64 x86_64 x86_64 GNU/Linux

versionに気をつけよう

例えば、centosだけでも差異がけっこうあります(以下は2020/09/23時点)。実行環境がバラつくときは、下位互換の無いオプションに気をつけましょう。

centos8: curl-7.61.1-12.el8.x86_64.rpm

http://mirror.centos.org/centos/8/BaseOS/x86_64/os/Packages/

centos7: curl-7.29.0-57.el7.x86_64.rpm

http://mirror.centos.org/centos/7/os/x86_64/Packages/

centos6: curl-7.19.7-53.el6_9.x86_64.rpm (2020/11/30でサポート終了だよ)

http://mirror.centos.org/centos/6/os/x86_64/Packages/

タイムアウト関連

タイムアウト周りはわりとハマりがち。大体以下を抑えておけば良さそう。

大抵は -m {待てる限界秒} を入れておけば大抵おkということで(雑)
-m なしで、途中で応答が来なくなると、一生刺さるので注意。

--connect-timeout <seconds>

サーバに接続するまでのタイムアウト時間。一応300sがデフォルト値(後述)

-m | --max-time <second>

curlの全処理にかかる時間の最大値

--retry <num>

失敗時(タイムアウトや5xx)のリトライ回数 (デフォルト 0)
最初は1秒待ってから、次もダメなら2秒待ってから、のように倍々で待ち、最大10分まで伸びる
この辺は --retry-delay と --retry-max-time で指定可能


分かりにくいからちょっと試してみようかなあと思ったけど、やはり先人はいらっしゃるもので…

curlだけでなくwgetとかncとかのタイムアウトのデフォルト値を検証されている。

余談: --happy-eyeballs-timeout-ms <milliseconds>

happy-eyeballsアルゴリズムのv6ちょと優先する時間? (デフォルト 200ms)

しばらく見ないうちにこんなオプションが...(7.59.0から)。happy-eyeballsは、v6とv4両方行けるホストがあったときにどっち使うかいい感じにやるもの(雑)ですが、これのv6優先秒を指定するとかみたいですね。manの説明とRFC6555(8305で更新)の説明が合ってない気がするけど… まあ、深く突っ込まない(おい

タイムアウトのデフォルト値的な話

タイムアウトを何も指定しなかったとき値はどうなるん?的な話がたまに出るんですが、manにも書かれていないんですね。これには環境要因が含まれます。

例えば、どうやっても届かないところにcurlを打つと、AWSのAmazonLinux1(微妙なend of lifeが近い とかだと、約2分でタイムアウトになる。表示は Connection timed out (exit code 7)

[AmazonLinux]$ time LANG=C curl -v 8.8.8.8:81
* Rebuilt URL to: 8.8.8.8:81/
*   Trying 8.8.8.8...
* TCP_NODELAY set
* connect to 8.8.8.8 port 81 failed: Connection timed out
* Failed to connect to 8.8.8.8 port 81: Connection timed out
* Closing connection 0
curl: (7) Failed to connect to 8.8.8.8 port 81: Connection timed out

real    2m9.518s
user    0m0.006s
sys     0m0.005s

ちょうど試していたWSL1環境だと、20秒でタイムアウト。 Connection refused (exit code 7)

[WSL1 fedora-limix]$ time LANG=C curl -v 8.8.8.8:81
*   Trying 8.8.8.8:81...
* TCP_NODELAY set
* connect to 8.8.8.8 port 81 failed: Connection refused
* Failed to connect to 8.8.8.8 port 81: Connection refused
* Closing connection 0
curl: (7) Failed to connect to 8.8.8.8 port 81: Connection refused

real    0m21.037s
user    0m0.000s
sys     0m0.047s

WSL2だと、30秒でタイムアウト(これはちょっと予想外!!!)。表示は Connection timed out (exit code 7)

[WSL2 fedora-limix]$ time LANG=C curl -v 8.8.8.8:81
*   Trying 8.8.8.8:81...
* TCP_NODELAY set
* connect to 8.8.8.8 port 81 failed: Connection timed out
* Failed to connect to 8.8.8.8 port 81: Connection timed out
* Closing connection 0
curl: (7) Failed to connect to 8.8.8.8 port 81: Connection timed out

real    0m32.123s
user    0m0.018s
sys     0m0.001s

なにこの違い。まあ、Linuxの動きについてはstraceとかで見るのがいいよね。

$ strace -s0 -ft curl -v 8.8.8.8:81
12:00:15 socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
  :
12:00:15 connect(3, {sa_family=AF_INET, sin_port=htons(81), sin_addr=inet_addr("8.8.8.8")}, 16) = -1 EINPROGRESS (Operation now in progress)
  :
12:00:00 poll([{fd=3, events=POLLOUT|POLLWRNORM}], 1, 0) = 0 (Timeout)
  :
12:00:00 poll([{fd=3, events=POLLOUT}], 1, 1000) = 1 ([{fd=3, revents=POLLOUT|POLLHUP}])

# AmazonLinux
12:02:25 getsockopt(3, SOL_SOCKET, SO_ERROR, [110], [4]) = 0

# WSL1
12:00:00 getsockopt(3, SOL_SOCKET, SO_ERROR, [ECONNREFUSED], [4]) = 0

connectシステムコールの後で返ってくる時間およびerrnoが異なるんですね。じゃあconnectシステムコールの後ってどうなるんだっけ、というと、LinuxはもちろんLinuxカーネルが実行しますが、WSLはWindowシステムコールに変換されるんでしたね… この辺は以下サイト様などが分かりやすいと思います。この辺の違いにより、何も指定しないときの動きが変わるんですね。

WSLのアーキテクチャ - roy-n-roy メモ

通常LinuxのTCPタイムアウトによるcurlエラー

AmazonLinux(通常のLinux)の場合は前述のタイムアウト調査の中でも出てきましたが、カーネルパラメータのnet.ipv4.tcp_syn_retries により動きが決まります。tcpdumpでパケット見るとわかりますね。

[AmazonLinux]$ cat /proc/sys/net/ipv4/tcp_syn_retries
6

# pingを打ちつつパケットを見ると、6回リトライしていることが分かる
[AmazonLinux]$ tcpdump -i eth0 -nn host 8.8.8.8
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
14:14:00.678034 IP 172.26.13.8.47618 > 8.8.8.8.81: Flags [S], seq 2744376366, win 26883, options [mss 8961,sackOK,TS val 228126919 ecr 0,nop,wscale 7], length 0
14:14:01.680106 IP 172.26.13.8.47618 > 8.8.8.8.81: Flags [S], seq 2744376366, win 26883, options [mss 8961,sackOK,TS val 228127921 ecr 0,nop,wscale 7], length 0
14:14:03.696105 IP 172.26.13.8.47618 > 8.8.8.8.81: Flags [S], seq 2744376366, win 26883, options [mss 8961,sackOK,TS val 228129937 ecr 0,nop,wscale 7], length 0
14:14:07.952101 IP 172.26.13.8.47618 > 8.8.8.8.81: Flags [S], seq 2744376366, win 26883, options [mss 8961,sackOK,TS val 228134193 ecr 0,nop,wscale 7], length 0
14:14:16.144096 IP 172.26.13.8.47618 > 8.8.8.8.81: Flags [S], seq 2744376366, win 26883, options [mss 8961,sackOK,TS val 228142385 ecr 0,nop,wscale 7], length 0
14:14:32.272101 IP 172.26.13.8.47618 > 8.8.8.8.81: Flags [S], seq 2744376366, win 26883, options [mss 8961,sackOK,TS val 228158514 ecr 0,nop,wscale 7], length 0
14:15:05.808103 IP 172.26.13.8.47618 > 8.8.8.8.81: Flags [S], seq 2744376366, win 26883, options [mss 8961,sackOK,TS val 228192051 ecr 0,nop,wscale 7], length 0
★ここで諦める

straceで見たように、このときconnectのエラーとして110(ETIMEDOUT)が返ると、curlのexitは7となり、メッセージにはETIMEDOUTのエラー内容(Connection timed out)が表示されるわけですね。

ちなみに、実はcurlのデフォルト値としては300秒(5分)が設定されているようです。

https://github.com/curl/curl/blob/master/lib/connect.h#L43

なので、retry数を増やすとこれが効いてきます。

[AmazonLinux]$ echo 10 > /proc/sys/net/ipv4/tcp_syn_retries
[AmazonLinux]$ time LANG=C curl -v 8.8.8.8:81
* Rebuilt URL to: 8.8.8.8:81/
*   Trying 8.8.8.8...
* TCP_NODELAY set
* Connection timed out after 300482 milliseconds
* Closing connection 0
curl: (28) Connection timed out after 300482 milliseconds

real    5m0.490s
user    0m0.016s
sys     0m0.000s

この場合は、Linuxカーネルのエラー前に、curlのデフォルトタイムアウトの300秒が勝つのでエラーコードも28(curlのタイムアウト)になります。5分はなかなか顕在化しないでしょうけどね。

WSL1のTCPタイムアウトによるcurlエラー

前述したサイト様を見て分かる通り、WSL1でのシステムコールは最終的にはWindowsのシステムコールになります。要は、Windows側のシステムコールで発生するTCPタイムアウト時間に依存するわけですね。21秒っていうのはWindowsだとお決まりなんですかね… このへん詳しくないんですけど、軽くググると以下などが出てきますね。
TCPの接続タイムアウトの21秒について

PowerShellのcurlでも同じ結果だからまあそういうことなんでしょうね(適当)。connection refusedになってくるのはなんでだろうなあ…

PS C:\Users\hijili> Measure-Command { curl 8.8.8.8:81 -v }
詳細: GET http://8.8.8.8:81/ with 0-byte payload
curl : リモート サーバーに接続できません。
発生場所 行:1 文字:19
+ Measure-Command { curl 8.8.8.8:81 -v }
+                   ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest]、WebExce
    ption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 21
Milliseconds      : 43
Ticks             : 210431872
TotalDays         : 0.000243555407407407
TotalHours        : 0.00584532977777778
TotalMinutes      : 0.350719786666667
TotalSeconds      : 21.0431872
TotalMilliseconds : 21043.1872

WSL2のTCPタイムアウトによるcurlエラー

30秒...? なるほど分からん。WSL2はほぼ独立したVMと思ってもそんなに間違いじゃないのかなあと思っていたのですが… LinuxKernelとしてのtcpカーネルパラーメタは、生Linuxとほとんど同じだったので、30秒でtimeoutになる道理が分からん… Windowsに詳しい人教えてください(結局丸投げ

ということで、環境によりタイムアウトの扱い違ったりするから指定しようね、というお話でした。

WebアプリにちょっとしたテストデータをPOSTしたい

ちょっとしたasciiなら: -d | --data

$ curl -X POST -d "hogehoge" 127.0.0.1 

ファイルの内容を送りたい: @をつけるとファイル指定

$ curl -X POST -d @input_file.txt 127.0.0.1

標準入力で渡したい: @- でおk

$ cat input_file.txt | curl -X POST -d @- 127.0.0.1

あかん、URLエンコードしてなかった!: 安心してください --data-urlencode ですよ

$ curl -X POST --data-urlencode @input_file.txt 127.0.0.1

誰だと思ってるんだ、jsonさんだぞ: -H | --header で ハゲ ヘッダを指定しよう

$ curl -v -X POST -H "Content-Type: application/json" --data-urlencode @input_file.json 127.0.0.1

HTTPでリダイレクトされたとき

リダイレクトってどういうヘッダなんだっけ試したい、とか思ったときはyahooさんの80番にアクセスしてみると443にリダイレクトされる。 # ごめんなさいyahooさん

$ curl -v www.yahoo.co.jp
*   Trying 183.79.250.251:80...
* TCP_NODELAY set
* Connected to www.yahoo.co.jp (183.79.250.251) port 80 (#0)
> GET / HTTP/1.1
> Host: www.yahoo.co.jp
> User-Agent: curl/7.66.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Redirect
< Date: Wed, 23 Sep 2020 11:06:32 GMT
< Connection: keep-alive
< Via: http/1.1 edge2573.img.umd.yahoo.co.jp (ApacheTrafficServer [c s f ])
< Server: ATS
< Cache-Control: no-store
< Location: https://www.yahoo.co.jp:443/
< Content-Type: text/html
< Content-Language: en
< Content-Length: 1
<

* Connection #0 to host www.yahoo.co.jp left intact

# -LをつければLocationヘッダに従ってcurlがつないでくれる
$ curl -L -v www.yahoo.co.jp
   :

bodyまで見たくねーよと思ったら以下を付けてみたり。
-v: ヘッダ出力(標準エラーに出る)
-Ss: 進捗非表示

$ curl -v -Ss -L www.yahoo.jp > /dev/null

参考

curlの使いかたまとめはいっぱいあるからなあ… 基本的なとこは以下サイト様で揃ってたり。。。
curl コマンド | hydroculのメモ

おわりに

とりあえずzenn使ってみたいだけの記事でした。こんな薄い記事、zenn zenn (全然)要らねーよ、と言われても仕方がないと思っている(1スベリ)

あと、記事に画像選べるってどうすりゃいいのかなと結構悩んだんですが、一応、curlの記事なので、カールを刈ーる画像を選んでみたんだけど、みんな、分かったかな!?(分かるか2スベリ)

え?もしかしてポエム行き必至ですか…???