この記事で理解してもらいたいこと
Web(ブラウザ)は単なるインタフェースに過ぎないよという話。
WebはHTTPリクエストに簡単に送るためのツール、HTTPレスポンスをわかりやすく表示するためのツールといっても過言ではありません。
以下3つを使って説明していきます。
- curl
- Postman
- ブラウザ
事前準備
Railsのアプリを作っておく。scaffoldでOK。
$ rails new curl_example
$ rails g scaffold article title:string content:text
$ rails db:migrate
$ rails s
=> Booting Puma
=> Rails 6.0.3.3 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.6 (ruby 2.6.6-p146), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop
class ApplicationController < ActionController::Base
# 今回の検証をしやすくするためこの一行追加
# 実務では安易に追加してはいけない。詳細はCSRFでググってください。
protect_from_forgery with: :null_session
end
curl
GET(記事一覧取得)
$ curl http://localhost:3000/articles
<!DOCTYPE html>
<html>
<head>
<title>CurlExample</title>
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="QM6SZATnSEC1vD1WJJZM1mzLLWkSZLKsGqj60vWLMxYWomphdmHIEj+Bc5LDUiHzMGRj/AKqGN9rDqgJQ5aPjA==" />
<link rel="stylesheet" media="all" href="/assets/application.debug-4024757a3d614102a20eedb0b76535ad941829836b18b0e2347eeede95da3921.css" data-turbolinks-track="reload" />
<script src="/packs/js/application-9afcbb5693aa87623e69.js" data-turbolinks-track="reload"></script>
</head>
<body>
<p id="notice"></p>
<h1>Articles</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Content</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<br>
<a href="/articles/new">New Article</a>
</body>
</html>
→ 〜〜〜って見覚えないですか?HTMLですよね。よく見ると一覧画面のHTMLのテキストですね。
POST(記事新規作成)
$ curl -X POST -F 'article[title]=hoge' -F 'article[content]=fuga' http://localhost:3000/articles
<html><body>You are being <a href="http://localhost:3000/articles/1">redirected</a>.</body></html>
→ このレスポンスは気にしなくてOKです。リダイレクトなので。
→ 新規作成できてる。入力フォームとかないのに新規作成できてる。よくわからないコマンド打っただけでなんか新規作成できてる。→ これがHTTPリクエストです。
余談ですがこれでも新規作成できる
$ curl -X POST -d 'article[title]=hoge&article[content]=fuga' http://localhost:3000/articles
Postman
GET(記事一覧取得)
POST(記事新規作成)
ブラウザ
GET(記事一覧取得)
http://localhost:3000/articles にアクセス
→ 当然こんな画面が表示される。
開発者ツールのnetworkタブの該当のリクエストの中身を見るとRequest URLにhttp://localhost:3000/articlesが設定されてる
→ responseタブを見ると一覧画面のHTMLのテキストがずらーっと見える。curlでもpostmanでも見えてたやつ。ブラウザは一見わかりづらいこのテキストをユーザーにとってみやすく画面を構築してくれる便利ツール。
POST(記事新規作成)
→ Request URLとFormDataのところがミソ。
Request URLには http://localhost:3000/articles が設定されている
FormDataには
article[title]
という名前にi am form
という値が紐づけられている
article[content]
という名前に`hello formという値が紐づけられている
curlやPostmanでも同じことをしましたね。
余談
Request URLってどこでどう設定されるのか?
article[title]
とかarticle[content]
ってどこでどう設定されるのか?
→ RequestURLはformのaction属性に指定したものです。FormDataの名前はinput要素やtextareaのname属性に指定したものです。form_withを使うとその辺をいい感じに設定した状態でHTMLを吐いてくれます。
まとめ
世の中のシステムはだいたい
- どこに(エンドポイント e.g. http://localhost:3000/articles)
- どんなデータを(e.g. article[title]=hoge)
- どんな方法で(HTTPメソッド e.g. POST)
で成り立っている。
curlだろうがPostmanだろうがHTTPリクエストを送れればツールはなんだって良い。
ただ、一般ユーザーにとってcurlやPostmanを使ってもらうわけにはいかない。なのでブラウザという誰にとってもわかりやすいインタフェースを作ってあげる必要がある。Railsはそれがめちゃめちゃ作りやすいフレームワーク。
余談
HTTPレスポンスは大きく「ステータスライン」「レスポンスヘッダ」「レスポンスボディ」に分かれる。
引用 HTTPレスポンスヘッダとは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
curlに-i
オプションをつけるとステータスラインとレスポンスヘッダ部分も確認できる。
$ curl -i http://localhost:3000/articles
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: text/html; charset=utf-8
ETag: W/"c97efccfebc4d13dfc90d25f7e67ab78"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: _curl_example_session=PhunKXciBkyLwuk9msnHqbk%2BwC9GGQKu96vEabdvZlemacHf%2FwboM746FBuA%2FtJMoQ068BqdenbMCXjS1K5mEMW0bphL7uYoqbLvbH43wed2c1vyG33SVrZDAhNKFy4aR8YTAoI9E%2F0FucI8SKhSujxNveG3Pvo2Lwuqm0rWqk1F8qXUkOYzufZmvIdKOC900sUjGSrTr402znqehJ00yluz0bYepyTi7RVblaDU54bt5I8WX4zg49PhwfiO4EptCgiHv2OlL9xnuhBmbqo2Qj4IfMi%2FRSgvVTJ7lPU%3D--KaQXqkIv8lYW4G54--Yq0oVArpQfhCMjX1eQx2uA%3D%3D; path=/; HttpOnly
X-Request-Id: 69a9c964-08fd-4b85-a557-6a5e9be5fed9
X-Runtime: 0.008425
Transfer-Encoding: chunked
<!DOCTYPE html>
<html>
<head>
<title>CurlExample</title>
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="RJuaT2poSWU4BD4DDbW1Cak4zVdJN3dPqu6OQJh8Due7aVwSCPx8s+aP5jVAlaFMSrBhLKN8KuflSA2HbCujKg==" />
<link rel="stylesheet" media="all" href="/assets/application.debug-4024757a3d614102a20eedb0b76535ad941829836b18b0e2347eeede95da3921.css" data-turbolinks-track="reload" />
<script src="/packs/js/application-9afcbb5693aa87623e69.js" data-turbolinks-track="reload"></script>
</head>
<body>
<p id="notice"></p>
<h1>Articles</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Content</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<tr>
<td>hello</td>
<td>world</td>
<td><a href="/articles/8">Show</a></td>
<td><a href="/articles/8/edit">Edit</a></td>
<td><a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/articles/8">Destroy</a></td>
</tr>
</tbody>
</table>
<br>
<a href="/articles/new">New Article</a>
</body>
</html>
本質的にユーザーが求めているものは<html>~~~</html>
の部分であり、それは「レスポンスボディ」に設定されて返ってくることがわかる。
次に試しにrender json: @articles
を追記してみる。
class ArticlesController < ApplicationController
def index
@articles = Article.all
render json: @articles # 追加
end
end
$ curl -i http://localhost:3000/articles
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
ETag: W/"c330b5c01cb4a59b3579b5d672195fc2"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 2eee0398-5764-42ff-9d40-5df6988abd43
X-Runtime: 0.060005
Transfer-Encoding: chunked
[{"id":8,"title":"hello","content":"world","created_at":"2020-10-09T13:15:08.437Z","updated_at":"2020-10-10T00:31:59.271Z"}]
レスポンスボディにはJSON形式のものが設定されていることがわかる。
Railsなどのサーバの役割は究極的には何らかのレスポンスを返すにすぎない。
そのレスポンスはクライアントに応じて適切な形式で返す必要がある。従来のWebアプリであれは同期的にページを遷移していくのでまるっとHTMLの形式で返す必要があるだろうし、一方でSPAであれば非同期的に都度必要な情報を取得する形になるのでJSON形式で返す必要がある。
演習
- curlコマンドで特定の記事を更新してください
- curlコマンドで特定の記事を削除してください
- postmanで特定の記事を更新してください
- postmanで特定の記事を削除してください