- curlのサイト
http://curl.haxx.se/
curlの実験準備
今回はJenkinsさんのAPIを叩くことにします。
curlのインストール
最近のLinuxには初めからcurlがインストールされていると思いますが、ない場合はapt
や yum
など、使用しているディストリビューションに沿った方法でインストールして下さい。
今回使用したcurlバージョン
7.38.0
$ curl --version
curl 7.38.0 (x86_64-pc-linux-gnu) libcurl/7.38.0 OpenSSL/1.0.1k zlib/1.2.8 libidn/1.29 libssh2/1.4.3 librtmp/2.3
Jenkinsのインストール
こちらも、apt
や yum
などで適当にインストールします。
実験用に幾つか設定をしたり、ジョブを作りますが、その都度、簡単に説明することとします。
jqのインストール
jq (https://stedolan.github.io/jq/) は、jsonをうまいことパースしてくれるコマンドです。
今回は、Jenkinsのレスポンスをちょっと見やすくするためだけにjqを使用しているので、ここでは特に説明はしません。
GET
ただGETリクエストを投げたいだけならば、何もオプションは要りません。
$ curl localhost:8080/api/json | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 371 100 371 0 0 53489 0 --:--:-- --:--:-- --:--:-- 61833
{
"assignedLabels": [
{}
],
"mode": "NORMAL",
"nodeDescription": "ノード",
"nodeName": "",
"numExecutors": 2,
"description": null,
"jobs": [],
"overallLoad": {},
"primaryView": {
"name": "すべて",
"url": "http://localhost:8080/"
},
"quietingDown": false,
"slaveAgentPort": 0,
"unlabeledLoad": {},
"useCrumbs": false,
"useSecurity": false,
"views": [
{
"name": "すべて",
"url": "http://localhost:8080/"
}
]
}
Jenkinsの一番トップのAPIを叩いています。
jq .
で、Jsonを見やすい形に整列しています。
もっともJenkinsでは、json
を json?pretty=true
に変えればほぼ同じ出力になります。
ファイルに出力 -o
-O
-o
オプションでファイルに出力できます。
$ curl localhost:8080/api/json -o response
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 371 100 371 0 0 68538 0 --:--:-- --:--:-- --:--:-- 74200
$ cat response | jq .
{
"assignedLabels": [
{}
],
"mode": "NORMAL",
"nodeDescription": "ノード",
"nodeName": "",
"numExecutors": 2,
"description": null,
"jobs": [],
"overallLoad": {},
"primaryView": {
"name": "すべて",
"url": "http://localhost:8080/"
},
"quietingDown": false,
"slaveAgentPort": 0,
"unlabeledLoad": {},
"useCrumbs": false,
"useSecurity": false,
"views": [
{
"name": "すべて",
"url": "http://localhost:8080/"
}
]
}
-O
だと、リクエスト先の名前でファイルを保存します。
$ curl localhost:8080/api/json -O
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 371 100 371 0 0 64985 0 --:--:-- --:--:-- --:--:-- 74200
# 先ほど保存した response と同じ内容か比較
$ diff json response; echo $?
0
Progress Meter
curlはデフォルトで、↓のような転送情報をコンソールに出力します。
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 371 100 371 0 0 53489 0 --:--:-- --:--:-- --:--:-- 61833
-#
オプションを使うと、プログレスバーのような表記に変更できます。
$ curl localhost:8080/api/json -o response -#
######################################################################## 100.0%
どちらにせよ、このプログレス情報を邪魔に思う日もあるでしょう。
stderrに出力されているので、/dev/nullにでも突っ込んでしまうのも手ですが、素直に-s
オプションで制御します。
$ curl -s localhost:8080/api/json -O
ただしこの時、エラーメッセージまで消えてしまいます。
URLやPortを間違えた時に気づけないので、-S
を一緒に指定しておいた方が無難です。
# Portを間違えてしまったが、エラーメッセージがない
$ curl -s localhost:8081/api/json
# -Sの効果で、エラーメッセージが出力される。
$ curl -Ss localhost:8081/api/json
curl: (7) Failed to connect to localhost port 8081: 接続を拒否されました
HTTP Headerを確認する -I
-i
-v
-I
で、Headerのみ取得し、出力することができます。
$ curl -I -s 'localhost:8080/api/json?'
HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
X-Jenkins: 1.619
X-Jenkins-Session: b34411df
Content-Type: application/json;charset=UTF-8
Content-Length: 371
Server: Jetty(winstone-2.8)
-i
ならば、Respnse Header, Body 両方を出力できます。
$ curl -i -s 'localhost:8080/api/json?'
HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
X-Jenkins: 1.619
X-Jenkins-Session: b34411df
Content-Type: application/json;charset=UTF-8
Content-Length: 371
Server: Jetty(winstone-2.8)
{"assignedLabels":[{}],"mode":"NORMAL","nodeDescription":"ノード","nodeName":"","numExecutors":2,"description":null,"jobs":[],"overallLoad":{},"primaryView":{"name":"すべて","url":"http://localhost:8080/"},"quietingDown":false,"slaveAgentPort":0,"unlabeledLoad":{},"useCrumbs":false,"useSecurity":false,"views":[{"name":"すべて","url":"http://localhost:8080/"}]}
ただし、これはレスポンスのHttp Headerだけです。
curlでリクエストした時のHeaderも見たければ、-v
が使用できます。
$ curl -v -s 'localhost:8080/api/json' | jq .
* Hostname was NOT found in DNS cache
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/json HTTP/1.1
> User-Agent: curl/7.38.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Content-Type-Options: nosniff
< X-Jenkins: 1.619
< X-Jenkins-Session: b34411df
< Content-Type: application/json;charset=UTF-8
< Content-Length: 371
* Server Jetty(winstone-2.8) is not blacklisted
< Server: Jetty(winstone-2.8)
<
{ [data not shown]
* Connection #0 to host localhost left intact
{
"assignedLabels": [
{}
],
"mode": "NORMAL",
"nodeDescription": "ノード",
"nodeName": "",
"numExecutors": 2,
"description": null,
"jobs": [],
"overallLoad": {},
"primaryView": {
"name": "すべて",
"url": "http://localhost:8080/"
},
"quietingDown": false,
"slaveAgentPort": 0,
"unlabeledLoad": {},
"useCrumbs": false,
"useSecurity": false,
"views": [
{
"name": "すべて",
"url": "http://localhost:8080/"
}
]
}
もっと HTTP パケットのデータを確認する --trace
--trace-ascii
--trace
や --trace-ascii
を使用すると、HTTPリクエスト・レスポンスのデータを全て dump することができます。
まずは --trace
の場合です。バイナリエディタとかでよく見る感じですね。
$ curl -sS localhost:8080 -X POST -F "hoge=fuga" --trace trace.log -o /dev/null
$ cat trace.log | head
== Info: Rebuilt URL to: localhost:8080/
== Info: Hostname was NOT found in DNS cache
== Info: Trying 127.0.0.1...
== Info: Connected to localhost (127.0.0.1) port 8080 (#0)
=> Send header, 208 bytes (0xd0)
0000: 50 4f 53 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d POST / HTTP/1.1.
0010: 0a 55 73 65 72 2d 41 67 65 6e 74 3a 20 63 75 72 .User-Agent: cur
0020: 6c 2f 37 2e 33 35 2e 30 0d 0a 48 6f 73 74 3a 20 l/7.35.0..Host:
0030: 6c 6f 63 61 6c 68 6f 73 74 3a 38 30 38 30 0d 0a localhost:8080..
0040: 41 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 43 6f 6e Accept: */*..Con
次に --trace-ascii
の場合です。人間に優しい感じですね。
$ curl -sS localhost:8080 -X POST -v -F "hoge=fuga" --trace-ascii trace-ascii.log -o /dev/null
$ cat trace-ascii.log | head
== Info: Rebuilt URL to: localhost:8080/
== Info: Hostname was NOT found in DNS cache
== Info: Trying 127.0.0.1...
== Info: Connected to localhost (127.0.0.1) port 8080 (#0)
=> Send header, 208 bytes (0xd0)
0000: POST / HTTP/1.1
0011: User-Agent: curl/7.35.0
002a: Host: localhost:8080
0040: Accept: */*
004d: Content-Length: 143
ちなみに、 --trace-time
というオプションを付与すると、出力に日時が付与されるようになります。
$ curl -sS localhost:8080 -X POST -v -F "hoge=fuga" --trace-ascii trace-ascii.log -o /dev/null --trace-time
$ cat trace-ascii.log | head
08:01:57.605505 == Info: Rebuilt URL to: localhost:8080/
08:01:57.605768 == Info: Hostname was NOT found in DNS cache
08:01:57.610090 == Info: Trying 127.0.0.1...
08:01:57.611293 == Info: Connected to localhost (127.0.0.1) port 8080 (#0)
08:01:57.612398 => Send header, 208 bytes (0xd0)
0000: POST / HTTP/1.1
0011: User-Agent: curl/7.35.0
002a: Host: localhost:8080
0040: Accept: */*
004d: Content-Length: 143
なお、-v
--trace
--trace-ascii
は、どれか一つしか作用しません。これらを同時に指定した場合、最後に指定したオプションのみ有効になります。
POST
POSTリクエストを送るには、-X POST
を付与します。
curl -sS -w '\n' -X POST 'localhost:8080/'
パラメータ付きPOST
POSTする際は、何らかのパラメータやデータを付与してリクエストすることが大多数だと思います。
--data
またはその省略系である -d
で、POSTで送信するデータを記述できます。
実際にJenkinsにPOSTし、ジョブの作成を試してみます。
# Jobを作成するPOST
$ curl -w '\n' 'http://localhost:8080/createItem' --data 'name=sample&mode=hudson.model.FreeStyleProject&Submit=OK' -XPOST
# 作成されたことを確認
$ curl -sS 'http://localhost:8080/api/json' | jq .jobs
[
{
"name": "sample",
"url": "http://localhost:8080/job/sample/",
"color": "notbuilt"
}
]
パラメータ付きPOSTとURLエンコード
--data
はURLエンコードしてくれませんので、あらかじめ自分でエンコードしておく必要があります。
試しに、上で作成したジョブ「sample」に、ファイルパラメータの設定を追加してみます。
# URLエンコード済みのパラメータを付与して、ジョブの設定を更新するPOST
$ curl -w '\n' 'http://localhost:8080/job/sample/configSubmit' --data 'json=%7b%22properties%22%3a+%7b%22hudson-model-ParametersDefinitionProperty%22%3a+%7b%22parameterized%22%3a+%7b%22parameter%22%3a+%7b%22name%22%3a+%22FileParameter%22%2c+%22description%22%3a+%22%22%2c+%22stapler-class%22%3a+%22hudson.model.FileParameterDefinition%22%2c+%22%24class%22%3a+%22hudson.model.FileParameterDefinition%22%7d%7d%7d%7d%7d%0d%0a&Submit=Save' -XPOST
# ジョブの中身を確認し、更新されていることを確認
$ curl -sS 'http://localhost:8080/job/sample/api/json' | jq .actions
[
{
"parameterDefinitions": [
{
"defaultParameterValue": null,
"description": "",
"name": "FileParameter",
"type": "FileParameterDefinition"
}
]
}
]
自前でURLエンコードするのは面倒ですし、ぱっと見て何かわかりにくいです。
--data-urlencode
を使うと、curlがURLエンコードしてくれるので、そのまま書いてしまうことができます。
試しに、上で追加したパラメータにDescriptionを追加してみます。
# URLエンコードはcurlに任せてPOST
$ curl -w '\n' 'http://localhost:8080/job/sample/configSubmit' --data-urlencode 'json={"properties": {"hudson-model-ParametersDefinitionProperty": {"parameterized": {"parameter": {"name": "FileParameter", "description": "Upload file to Jenkins.", "stapler-class": "hudson.model.FileParameterDefinition", "$class": "hudson.model.FileParameterDefinition"}}}}}' -d 'Submit=Save' -XPOST
# descriptionが "Upload file to Jenkins." に更新されてることを確認
$ curl -sS 'http://localhost:8080/job/sample/api/json' | jq .actions
[
{
"parameterDefinitions": [
{
"defaultParameterValue": null,
"description": "Upload file to Jenkins.",
"name": "FileParameter",
"type": "FileParameterDefinition"
}
]
}
]
この時、&
で複数のパラメータを定義しようとしても、肝心の&
がエンコードされてしまいます。そのためSubmit=Save
は別途-d
で付与しています。
ファイルをUploadするPOST
次は、POSTでJenkinsジョブをビルドしてみます。
JenkinsでPOSTと言ったらジョブのビルドですからね。
Jenkinsのファイルパラメータは、ローカルのファイルをJenkinsにUploadしてビルドすることができる機能です。
そのため、curlでファイルをUploadするPOSTをコマンドで表現する必要があります。
--form
もしくは -F
を使用します。
# Upload用ファイルを用意
$ echo "Sample file" > sample.txt
# Fileパラメータにsample.txtを指定してビルド実行
$ curl -sS 'http://localhost:8080/job/sample/build' -X POST -F "[email protected]" -F 'json={"parameter": [{"name":"FileParameter", "file":"file"}]}'
# ビルドが実行され、ファイルがUploadできていることを確認
$ curl -sS 'http://localhost:8080/job/sample/api/json' | jq .builds
[
{
"number": 1,
"url": "http://localhost:8080/job/sample/1/"
}
]
$ curl -sS 'http://localhost:8080/job/sample/1/parameters/parameter/FileParameter/sample.txt'
Sample file
@
以降で、ファイルを相対パスで指定します。
ちなみにJenkinsのparameterizedビルドのcurlの使用例はJenkinsのHPに書いてあります。
下記ページ Submitting jobs の部分です。
https://wiki.jenkins-ci.org/display/JENKINS/Remote+access+API
-F
と-d
は併用できません
$ curl localhost:8080 -X POST -F "hoge=fuga" -d "fuga=piyo"
Warning: You can only select one HTTP request!
こんな感じでエラーになります。同様に --data-urlencode
も、-F
とは併用できません。
( -F
を使いつつ、curlにURLエンコードを任せてPOSTするにはどうすれば。。。)
それぞれのオプションを付与した際のHTTPリクエストを観察すると、
-F
では Content-Type: multipart/form-data
で、
-d
では Content-Type: application/x-www-form-urlencoded
となります。
-F
でマルチパートでPOSTすると設定しているのに、-d
で違うContent-Typeで送ろうとしているから実行できない、という説明で良いでしょうか。。。もっと適切な表現があるとは思いますが。
ちなみに、 -F
と -d
を同時に使った場合のエラーメッセージは、-S
では表示されません。
$ curl -sS localhost:8080 -X POST -F "hoge=fuga" --data-urlencode "fuga=piyo"
# warning 行が表示されない
# 終了コードは 0 ではないです
$ echo $?
4
エラー時に気付くために -S
というのに。。
認証とcurl
今までは、アカウント管理のない真っ裸なJenkinsにアクセスしていました。
ユーザアカウントとパスワードが必要な場合のcurlも試してみます。
Jenkinsの設定
詳細は割愛しますが、
Jenkinsのセキュリティ設定のMatrix-based securityで、
特定のユーザ(yasuhiroki)以外からはJenkinsにアクセスできないようにしています。
この設定をした状態でcurlを叩くと下のようになります。
$ curl -sS 'http://localhost:8080/api/json' -I
HTTP/1.1 403 Forbidden
X-Content-Type-Options: nosniff
Set-Cookie: JSESSIONID.827aac98=qv6zzok692pe1397gebsgms19;Path=/;HttpOnly
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Type: text/html;charset=UTF-8
X-Hudson: 1.395
X-Jenkins: 1.619
X-Jenkins-Session: 85e6322e
X-Hudson-CLI-Port: 52325
X-Jenkins-CLI-Port: 52325
X-Jenkins-CLI2-Port: 52325
X-You-Are-Authenticated-As: anonymous
X-You-Are-In-Group:
X-Required-Permission: hudson.model.Hudson.Read
X-Permission-Implied-By: hudson.security.Permission.GenericRead
X-Permission-Implied-By: hudson.model.Hudson.Administer
Content-Length: 813
Server: Jetty(winstone-2.8)
想定通り、アカウント制限が機能しています。
アカウント名とパスワードを指定してリクエスト -u
いわゆるBASIC認証です。
今回は、ユーザ名はyasuhiroki
、パスワードはsample
で作成したので、
-u yasuhiroki:sample
と付与することになります。
見ての通り、コンソール上にパスワードがもろ残ってしまいます。
$ curl -sS 'http://localhost:8080/api/json' -I -u yasuhiroki:sample
HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
X-Jenkins: 1.619
X-Jenkins-Session: 85e6322e
Content-Type: application/json;charset=UTF-8
Content-Length: 444
Server: Jetty(winstone-2.8)
コマンドにベタ書きしなければ、curl実行後に入力を求められます。
$ curl -sS 'http://localhost:8080/api/json' -I -u yasuhiroki
Enter host password for user 'yasuhiroki':
HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
X-Jenkins: 1.619
X-Jenkins-Session: 85e6322e
Content-Type: application/json;charset=UTF-8
Content-Length: 444
Server: Jetty(winstone-2.8)
別に-u
オプションを使わなくても、http://user:pass@hostname
でやってしまう方法もあります。
$ curl -sS 'http://yasuhiroki:sample@localhost:8080/api/json' -I
HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
X-Jenkins: 1.619
X-Jenkins-Session: 85e6322e
Content-Type: application/json;charset=UTF-8
Content-Length: 444
Server: Jetty(winstone-2.8)
ちなみにJenkins自体に、パスワードとは別に使えるAPIトークンがあるので、そちらを使う手もあります。(むしろ、そちらを使うべき)
トークンはJenkinsのアカウントの設定で確認、変更ができます。
# JenkinsのAPIトークンを使用した例
$ curl -sS 'http://localhost:8080/user/yasuhiroki/api/json' -I -u yasuhiroki:a752ad2b9fc7b314562f386b603fc11f
HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
X-Jenkins: 1.619
X-Jenkins-Session: 85e6322e
Content-Type: application/json;charset=UTF-8
Content-Length: 214
Server: Jetty(winstone-2.8)
SessionをCookieから取得して使用 -c
-b
Sessionを保持して使用する必要がある場合、curlを単発で叩いては実現が難しい動作もあるでしょう。
-c cookie.txt
で、レスポンスのCookieをcookie.txtに保存し、
-b cookie.txt
で、cookie.txtの中身をリクエストのCookieに含めることができます。
Jenkinsの操作をSessionでどうにかできないか試しましたが、
何となくできそうな気がするものの、時間が掛かりそうなので割愛します...。
いつかリベンジしたいですね。
ところで、認証に失敗した時の終了コードは? --fail
-f
たとえば、-u
で渡すパスワードを間違えたとします。
$ curl -sS 'http://localhost:8080/api/json' -I -u yasuhiroki:miss
HTTP/1.1 401 Invalid password/token for user: yasuhiroki
X-Content-Type-Options: nosniff
WWW-Authenticate: Basic realm="Jenkins"
Content-Type: text/html;charset=ISO-8859-1
Cache-Control: must-revalidate,no-cache,no-store
Content-Length: 1441
Server: Jetty(winstone-2.8)
$ echo $?
0
終了コードは0なのです。
もし、何かエラーがあった時は、終了コードを 0
以外のを返して欲しい場合は、 --fail (-f)
オプションが利用できます。
$ curl -sS 'http://localhost:8080/api/json' -u yasuhiroki:miss -f
curl: (22) The requested URL returned error: 401
$ echo $?
22
ただし、 401
407
エラーのような、認証関連の場合は確実性に欠けているそうです。
This method is not fail-safe and there are occasions where non-successful response codes will slip through,
especially when authentication is involved (response codes 401 and 407).
他のTips
-w
でいろんな情報を表示する
curlの実行結果に、追加してさらに情報を出力することが可能です。
$ curl -sSO 'http://localhost:8080/api/json' -u yasuhiroki:a752ad2b9fc7b314562f386b603fc11f -w 'hogefuga'
hogefuga
-w
では、特定の変数を使用すると、その変数が持つ値を表示できます。
例えば、HTTPステータスだけを表示したいなら、-w '%{http_code}\n'
でOKです。改行はお好みで。
$ curl -sS -w '%{http_code}\n' 'http://localhost:8080/' -o /dev/null
200
ちなみに、manを探ったところ、27の変数が見つりました。
URLエンコードするだけ
-w '%{url_effective}'
--data-urlencode
-G
を使用して、文字列をURLエンコードして表示するだけのシェルスクリプトです。
-G
(--get
) は、-d
や --data-urlencode
で指定した値を、クエリーとして自動的に付与しなおしてくれるオプションです。
例えば、-d 'val=A' -G
としておけば、リクエストURLに、host/?val=A
などとクエリーとして付与したうえで送信してくれます。
これと、-w
を組み合わせることで、curlがURLエンコードしてくれた結果を出力することができます。/?
がくっつくのが玉に瑕です。
$ curl -s -w '%{url_effective}\n' --data-urlencode 'じぇんきんす' -G ''
/?%E3%81%98%E3%81%87%E3%82%93%E3%81%8D%E3%82%93%E3%81%99
/?
が邪魔な場合は、例えばこんな感じでしょうか。
$ urlencoded_str=$(curl -s -w '%{url_effective}\n' --data-urlencode 'じぇんきんす' -G '')
$ urlencoded_str=${urlencoded_str:2}
$ echo ${urlencoded_str}
%E3%81%98%E3%81%87%E3%82%93%E3%81%8D%E3%82%93%E3%81%99
他によく使うオプション
-k
SSL証明書を無視する。主に、オレオレ証明書を使っているWebサーバーにアクセスする時に。
-x
Proxyを指定。
-H
Request Headerを追加する。Content-Typeを指定する時など
- 例: -H "Content-Type: application/json"
-L
リダイレクト先までアクセスする。
おまけ
発音、何て読む?
初めて見た時から「カール」と呼んでいましたが、公式サイトでは、
The fact it can also be pronounced 'see URL' also helped
- http://curl.haxx.se/docs/faq.html#What_is_cURL
'see URL' と同じ、つまり、「シーユーアールエル」だそうです。
と、いいつつドキュメントの続きをよく読むと、
We pronounce curl with an initial k sound. It rhymes with words like girl and earl.
なんて書いてあり、おまけに発音例を .wav ファイルで提供しており、「カール」と呼んでいます。
(じゃあもう最初から カール で良いじゃないか。。。)
User-Agentを偽装する
Webサイトによっては、curl
コマンドによるアクセスを制御している場合があるそうです。
そういう時は、 -A
オプションを指定して、User-Agentを空にするとうまく行くかもしれません。
$ curl -s -w '%{http_code}\n' http://www.amazon.co.jp/dp/B00JEYPPOE/ -o /dev/null
503
$ curl -s -w '%{http_code}\n' http://www.amazon.co.jp/dp/B00JEYPPOE/ -o /dev/null -A ''
200
参考) http://jarp.does.notwork.org/diary/201508c.html#20150825
Optionの数
どれくらいあるのか、ざっくり調べたところ、170ありました。
$ man curl | egrep -- '^[[:space:]]{7}-' | wc -l
170
$ curl -h | egrep -- '^[[:space:]]+-' | wc -l
168
# helpには、 --environment と --proxy-header がなかった
もっと便利そうな http
コマンド (httpie)
この記事を書き始めた後、 httpie
というものを知りました。
https://github.com/jkbrzt/httpie
例えば、json形式のファイルをPOST時に渡せば勝手に Content-Type: application/json
を設定してくれるなど、curl
よりも気が利いています。