Skip to Content

HTTPアクション

httpアクションはHTTPリクエストを実行し、レスポンスをアサーションやoutputsから参照できるようにします。

基本的な構文

HTTPのステップにはURLとメソッドを指定し、レスポンスをtestで検証します。

steps: - name: Check the API uses: http with: url: "https://api.example.com/health" method: GET test: res.code == 200

パラメータ

以下のフィールドでリクエストを指定します。いずれもテンプレート式を書けます。

パラメータ型必須デフォルト説明
urlString必須-リクエストURL。メソッド省略記法でパスを渡す場合はベースURL
methodString必須-HTTPメソッド。メソッド省略記法を使う場合はそちらが設定します
headersObject任意-リクエストヘッダー
bodyStringまたはObject任意-リクエストボディ。content-typeがJSONの型のとき、オブジェクトはJSONにシリアライズされます。JSONの型とは、charsetなどのパラメータの有無を問わないapplication/jsonと、+jsonで終わる型です
timeoutDuration任意30sレスポンスの読み取りまで含めた、リクエスト全体の制限時間
basic_authObject任意-HTTP Basic認証のusernameとpassword。Authorizationヘッダーとして送ります
formObject任意-フォームのフィールド。application/x-www-form-urlencodedのボディとして送ります。フォームの送信を参照
multipartObject任意-フォームのフィールドとファイル。multipart/form-dataのボディとして送ります。ファイルのアップロードを参照
cookiesObject任意-送るCookie。名前と値で指定します。Cookieを参照
trace_headerBooleanまたはString任意falseリクエストの実行、ジョブ、ステップを示すヘッダーを送ります。trueならX-Probe-Trace、文字列ならその名前のヘッダーです。リクエストのトレースを参照
keep_cookiesBoolean任意falseサーバーが設定したCookieをジョブで保持し、Cookieを保持する以降のステップで送ります。Cookieを参照
openapiObjectまたはfalse任意-specをパスとするOpenAPIドキュメントとリクエストおよびレスポンスを照合し、どちらかに違反があればステップを失敗させます。request: falseはレスポンスだけを照合し、strict: trueはドキュメントが宣言していないプロパティも違反にし、falseはジョブのdefaultsが求める照合を外します。OpenAPIによる検証を参照

リダイレクトとTLS検証を指定するパラメータはありません。リダイレクトは既定で追跡します。

headersで指定しない限り、すべてのリクエストはAccept: */*とUser-Agent: probe-http/1.0.0を送ります。ヘッダー名は大文字と小文字を区別せずに比較するため、user-agent: my-agentと書けば既定値を置き換えます。

timeout

timeoutには10sや1m30sのようなduration文字列、または秒数の数値を指定します。0を指定すると制限しません。

- name: Slow endpoint uses: http with: url: "{{vars.api_url}}/report" method: GET timeout: 60s test: res.code == 200

制限時間を超えたリクエストはClient.Timeout exceededのエラーになり、ステップは失敗します。

ジョブのdefaultsでまとめて指定できます。

jobs: - name: API checks defaults: http: timeout: 5s steps: - name: Health uses: http with: get: /health test: res.code == 200

ステップのtimeoutはこれとは別の、アクション実行1回ごとの外側の制限です(既定5分)。with.timeoutがHTTPリクエストそのものを、ステップのtimeoutがそれを包むアクション呼び出しを区切ります。応答を返さないまま固まったアクションを止めるのは後者です。

メソッド省略記法

get head post put patch delete connect options traceは、メソッドとパスを1つのキーで指定します。値は完全なURLか、urlからの相対パスです。ジョブのdefaultsと組み合わせると簡潔に書けます。

jobs: - name: API checks defaults: http: url: "{{vars.api_url}}" headers: accept: application/json steps: - name: List users uses: http with: get: /users test: res.code == 200 - name: Create a user uses: http with: post: /users headers: content-type: application/json body: name: "{{vars.user_name}}" test: res.code == 201

フォームの送信

formは、HTMLのフォームと同じく、フィールドをapplication/x-www-form-urlencodedのボディとして送ります。フィールドの値には文字列、数値、真偽値を書けます。リストを書くと、その値ごとに同じ名前のフィールドを送ります。

- name: Log in with a form uses: http with: post: /login form: user: "{{vars.user}}" password: "{{vars.password}}" scope: [read, write] test: res.code == 302

フィールドは名前順に並べてパーセントエンコードします。req.bodyには送った内容がそのまま入ります。

ファイルのアップロード

multipartは、テキストのフィールドとファイルをmultipart/form-dataのボディとして送ります。文字列、数値、真偽値で書いたフィールドはテキストのフィールドです。マップで書いたフィールドはファイルで、中身はfileのパスから読むか、contentに直接書きます。リストを書くと、その値ごとに同じ名前のフィールドを送るので、1つの名前で複数のファイルを送れます。

- name: Upload an avatar uses: http with: post: /api/images multipart: title: avatar image: file: ./fixtures/logo.png attachments: - file: ./fixtures/terms.pdf - content: "a,b\n1,2\n" filename: data.csv content_type: text/csv test: res.code == 201

ファイルには次のキーを書けます。

キー説明
file送るファイルのパス。ほかのアクションのパスと同じく、カレントディレクトリからの相対パスです
contentfileの代わりに送る中身
filenameファイルとともに送るファイル名。既定はfileのベース名、contentの場合はフィールド名です
content_typeファイルのメディアタイプ。既定はfilenameの拡張子から決まるもので、決まらなければapplication/octet-streamです

テキストのフィールドを先に、ファイルを後に、それぞれ名前順で送ります。S3へのPOSTアップロードのように、ファイルより前にあるフィールドしか読まないサーバーがあるためです。リストの値は、テキストのフィールド同士、ファイル同士の中で書いた順に送ります。テキストとファイルを混ぜたリストでは、テキストの値が先になります。ファイルはステップを実行するたびに読むので、リトライしたステップはその時点のファイルを送ります。

ボディは長さを付けて送り、表示はしません。req.bodyは空になり、代わりにreq.multipartに書いた内容が入ります。

formとmultipartはそれぞれContent-Typeヘッダーを設定し、headersやジョブのdefaultsで指定したもの(application/jsonなど)を置き換えます。どちらもbodyとは同時に指定できず、互いに同時に指定することもできません。

レスポンスオブジェクト

リクエストの後、resに応答の内容が入ります。

フィールド型説明
res.codeIntegerステータスコード(例: 200)
res.statusStringステータス行(例: "200 OK")
res.headersObjectレスポンスヘッダー。キーはContent-Typeのような正規形
res.bodyAnyレスポンスボディ。JSONならオブジェクトや配列に解析され、それ以外は文字列
res.rawbodyString解析前のボディ。JSONとして解析したときに入ります
res.filepathStringバイナリレスポンスを保存したファイルのパス
res.cookiesObjectサーバーが設定したCookie。名前と値で入り、リダイレクトの途中で設定されたものも含みます
res.violationsArrayリクエストとレスポンスのうちOpenAPIドキュメントが許可しないもの。すべて許可されていれば空です。openapiを指定したときだけ入ります
res.contractObjectOpenAPIドキュメントでレスポンスを対応付けた先。spec、GET /users/{id}のようなoperation、200、2XX、defaultのようなresponseが入ります。responseは、オペレーションがそのステータスのレスポンスを宣言していない場合は入りません。ドキュメントにリクエストのオペレーションがある場合だけ入ります
rt.durationStringラウンドトリップ時間(例: "120ms")
rt.secFloatラウンドトリップ時間(秒)
statusIntegerステータスコードが2xxなら0、それ以外は1

レスポンス例

JSONレスポンスの値はres.bodyから直接読みます。

test: | res.code == 200 && res.headers["Content-Type"] contains "application/json" && res.body.status == "ok" && len(res.body.items) > 0 outputs: first_id: res.body.items[0].id elapsed_ms: rt.sec * 1000

テキストやHTMLのレスポンスでは、res.bodyは文字列そのものです。

test: | res.code == 200 && res.body contains "<title>" && len(res.body) > 100

一般的なパターン

上記のパラメータの組み合わせは、いくつかの決まった形にまとまります。トークンをステップ間で受け渡す、エラーレスポンスを検証する、まだ反映されていないリクエストをリトライする、といった形です。

認証

トークンはログインのステップの出力として取り出し、後続のステップから参照します。

- name: Log in id: auth uses: http with: url: "{{vars.api_url}}/login" method: POST headers: content-type: application/json body: user: "{{vars.user}}" password: "{{vars.password}}" test: res.code == 200 outputs: token: res.body.access_token - name: Call a protected endpoint uses: http with: url: "{{vars.api_url}}/me" method: GET headers: authorization: "Bearer {{outputs.auth.token}}" test: res.code == 200

HTTP Basic認証を受け付けるサーバーには、basic_authでユーザー名とパスワードを渡します。basic_authはそこからAuthorizationヘッダーを組み立てます。ジョブのdefaultsに書けば、ジョブのすべてのリクエストに適用されます。

- name: Admin API defaults: http: url: "{{vars.api_url}}" basic_auth: username: "{{vars.admin_user}}" password: "{{vars.admin_password}}" steps: - name: List users uses: http with: get: /admin/users test: res.code == 200 && len(res.body) > 0

ユーザー名にはコロンを含められません。そこでユーザー名が終わったと解釈されるためです。パスワードは空でも構いません。送れるのはどちらか一方なので、basic_authとauthorizationヘッダーは同時に指定できません。パスワードと、そこから組み立てたヘッダーは、ほかの資格情報と同じく出力では伏せられます。

サーバーが設定したCookieは、ブラウザと同じくリダイレクト先にも送ります。そのため、セッションのCookieを設定してリダイレクトするログインでも、リダイレクト先のページまで届きます。設定されたCookieは、リダイレクトの途中で設定されたものも含めて、名前と値でres.cookiesに入ります。

keep_cookies: trueを指定すると、サーバーが設定したCookieをジョブで保持し、同じジョブの以降のステップのうち、同じくCookieを保持するステップで送ります。どのCookieをどのURLへ送るかは、ブラウザと同じくドメイン、パス、有効期限、Secure属性に従います。サーバーが削除したCookieや期限が切れたCookieは送りません。ジョブのdefaultsに書けば、ジョブのすべてのリクエストに適用されます。

- name: Logged-in pages defaults: http: url: "{{vars.api_url}}" keep_cookies: true steps: - name: Log in uses: http with: post: /login form: user: "{{vars.user}}" password: "{{vars.password}}" test: res.code == 200 && res.cookies.session != "" - name: My page uses: http with: get: /me test: res.code == 200

Cookieはジョブごとに保持するので、同時に走るジョブ同士で互いのCookieは見えません。繰り返すジョブの各回と、embeddedアクションで実行するジョブは、Cookieを持たない状態から始まります。別のジョブへCookieを渡すには、アウトプットで渡し、渡した先でcookiesを使って送ります。

cookiesは、keep_cookiesの有無にかかわらず、名前と値でCookieを送ります。保持しているCookieに同じ名前のものがあれば、こちらで置き換えます。送り先はリクエストのホストだけで、リダイレクトで別のホストへ移ったときは送りません。cookieヘッダーを書いた場合は、それもあわせて送ります。

- name: Someone else's session uses: http with: get: /me cookies: session: "{{outputs.other.session}}" test: res.code == 200

cookiesとres.cookiesの値は、ほかの認証情報と同じく出力では隠し、名前は表示します。

リクエストのトレース

trace_header: trueを指定すると、各リクエストに送信元を示すX-Probe-Traceヘッダーを付けます。サーバーのアクセスログで、どのリクエストかを見分けられます。

X-Probe-Trace: run=7f3a9c21e4b05d68; job=login; step=auth; repeat=0; attempt=1
フィールド値
runprobeの実行のID。その実行のすべてのリクエストで同じです。JSONレポートのrun_idと同じ値で、embeddedアクションで実行するジョブも同じ値を使います
jobジョブのID
stepステップのID。IDのないステップはstep_<番号>になります
repeat繰り返すジョブの何回目か(0から)
attemptリトライするステップの何回目の試行か(1から)

空白、セミコロン、ASCII以外の文字を含む値は、URLのクエリと同じくエスケープします。ジョブのdefaultsに書けば、ジョブのすべてのリクエストに適用されます。trueの代わりにヘッダー名を書くと、同じ値をその名前のヘッダーで送ります。サーバーが既にログに出しているヘッダーを使う場合に便利です。

- name: Traced checks defaults: http: url: "{{vars.api_url}}" trace_header: X-Correlation-Id steps: - name: List users id: users uses: http with: get: /users test: res.code == 200

このヘッダーはheadersに書いたヘッダーと同じく送られ、req.headersにも表示されます。headersに同じ名前のヘッダーを書いた場合はエラーになります。オプション名がtraceでないのは、traceがTRACEメソッドの省略記法だからです。

OpenAPIによる検証

openapiを指定すると、各リクエストとそのレスポンスをOpenAPIドキュメントと照合します。ステップに書いたtestとは独立に、何が正しいかをドキュメントが決めます。ジョブのdefaultsに書けば、ジョブのすべてのリクエストを照合します。

- name: Users defaults: http: url: "{{vars.api_url}}" headers: content-type: application/json openapi: spec: ./openapi.yml steps: - name: Create a user uses: http with: post: /users body: name: probe test: res.body.name == "probe" - name: Reject a user without a name uses: http with: post: /users body: {} openapi: request: false test: res.code == 400 - name: Health uses: http with: get: /health openapi: false test: res.code == 200

リクエストとレスポンスは、リクエストのメソッドとパスでオペレーションに対応付けます。パスの先頭からはドキュメントのserversのパスを除きます。リクエストはステップが送ったとおりに、cookiesで送ったCookieやkeep_cookiesで保持したCookieも含めて照合し、次の場合にステップは種類contract_requestで失敗します。

  • パス、クエリ、ヘッダー、Cookieのパラメータが、必須なのにない、またはスキーマに合わない
  • ボディが、必須なのにない、Content-Typeがオペレーションの宣言する型ではない、またはスキーマに合わない
  • オペレーションのsecurityが求める認証情報がない

レスポンスは、リダイレクトされた場合は最後のリクエストで照合し、次の場合にステップは種類contract_responseで失敗します。

  • メソッドとパスに対応するオペレーションがドキュメントにない
  • オペレーションがそのステータスコードもdefaultのレスポンスも宣言していない
  • レスポンスのContent-Typeが、そのレスポンスの宣言する型ではない
  • レスポンスが宣言するヘッダー、またはボディがスキーマに合わない
  • JSONのボディが空、またはスキーマがnullを許可しないのにnull

ボディはJSON、テキスト、XML、YAML、CSV、フォームとして照合します。画像やPDFなどほかの型のボディは、Content-Typeだけを照合します。

両方に違反がある場合、種類はcontract_requestです。ワークフローがドキュメントの許可しないものを送っており、それがレスポンスの違反の原因かもしれないからです。testが成り立っていても失敗し、testは評価しません。testのないステップは、リクエストとレスポンスがドキュメントに合っていれば成功します。ドキュメントが検証したからです。retryを指定すれば、testが成り立つまで再試行するのと同じく、リクエストとレスポンスがドキュメントに合うまで再試行します。違反はそれぞれres.violations、端末、レポートに{in, field, reason, message}の形で入ります。inはrequestかresponseで、fieldは$.idのようなボディのフィールドです。fieldは該当するときだけ入ります。

拒否されることを確かめるために、ドキュメントの許可しないものをわざと送るステップにはrequest: falseを書きます。レスポンスは引き続き照合するため、ドキュメントが宣言していない拒否の仕方をすればステップは失敗します。securityが求める認証情報なしで送るリクエストもこれにあたり、401を確かめる場合などに使います。

strict

スキーマは通常、宣言していないプロパティも許可します。そのため、たとえばpassword_hashまで返すレスポンスもスキーマに合っていることになります。strict: trueを指定すると、次の場合にもステップは失敗します。

  • JSONのボディに、スキーマが宣言していないプロパティがある。対象は、プロパティかpatternPropertiesを書き、additionalPropertiesを書いていないスキーマです
  • オペレーションが宣言していないクエリパラメータがある
  • リクエストにreadOnlyのプロパティ、レスポンスにwriteOnlyのプロパティがある
openapi: spec: ./openapi.yml strict: true

additionalPropertiesを書いたスキーマは、ほかに何を許可するかを自ら示しています。trueやスキーマ(labelsのようなマップ)はほかのプロパティを許可し、falseはstrictがなくても違反になります。type: objectとだけ書いたような、プロパティを何も宣言していないスキーマは何でも許可します。allOf、oneOf、anyOfのスキーマが宣言するプロパティは、宣言されたものとして扱います。ヘッダーとCookieはstrictに照合しません。ServerやロードバランサーのCookieなど、プロキシ、サーバー、クライアントが付けるものをドキュメントが宣言することはまれだからです。違反は見つかった場所によってcontract_requestかcontract_responseになり、fieldには$.owner.emailのようにプロパティが入ります。

specはProbeを実行したディレクトリからのパスです。ドキュメントはリクエストを送る前に読むため、読めない、または解析できないドキュメントを指定したステップは、何も送らずにアクションのエラーとして失敗します。ステップにopenapi: falseを書くと、ドキュメントが扱わないエンドポイントなどで照合を外せます。

各ステップのレスポンスを対応付けたオペレーションとレスポンスは、res.contractとJSONレポートに入ります。probe coverageは、そのレポートから、ドキュメントのうちどのステップも検証していないものを示します。

ガードの下での動作

--read-onlyを指定して実行すると、GET、HEAD、OPTIONSだけを送り、それ以外のメソッドは送る前に拒否します。--allow-hostを指定して実行すると、許可されていないホストへのURLやリダイレクトを拒否します。ポートのないURLは、スキームの既定のポートとして扱います。拒否されたステップは種類refusedで失敗します。--read-onlyを参照してください。

エラーレスポンスの検証

ここではエラーが期待される結果です。そのためステータスとエラーのボディを検証します。

- name: Unknown id returns 404 uses: http with: url: "{{vars.api_url}}/users/does-not-exist" method: GET test: res.code == 404 && res.body.error != null

不安定なエンドポイントのリトライ

retryは、テストが通るか試行回数を使い切るまでステップを繰り返します。

- name: Eventually consistent read uses: http retry: max_attempts: 5 interval: 2s with: url: "{{vars.api_url}}/orders/{{outputs.create.order_id}}" method: GET test: res.code == 200

関連項目

更新日時