今回は、GET メソッドと POST メソッドの 2 つで Laravel での処理の流れを見ていきます。 簡単なプログラムで処理の流れを見ていきましょう。
GETメソッドでのLaravelでの処理の流れ
GET メソッドというものはどのようなものでしょうか?
よく使われる場面としては、ブラウザのアドレスバーに URL を入力してエンターを押すときです。 その時に、ブラウザからサーバ側に GET メソッドのリクエスト(要求)が送信されます。 GET メソッドは、URL で指定したファイルを要求するものです。
たとえば、Yahoo!ニュースにおいて、ある記事の URL は下記となっています。
https://news.yahoo.co.jp/articles/13bb80852778d4d975b05126a51faf543c320357
「/」で区切られた最後の英数字「13bb80852778d4d975b05126a51faf543c320357」が、記事の ID になっていると思われます。サーバ側ではこの ID をもとに記事を取得して、ブラウザに渡しています。
これと同様のしくみのものを Laravel で用意しました。動作させて中身をみていきましょう。(slackにて配布)
ブラウザのアドレスバーにhttp://localhost/articles/10001と打ち込みエンターキーを押します。
すると、ブラウザに、「10001 の記事です」と表示されましたよね。
裏では次の処理が動いています。
- ブラウザからサーバ側に要求(リクエスト)が送信されます。
- サーバ側で返信(レスポンス)を作成して、ブラウザに返します。
サーバ側で返信(レスポンス)を作成する部分で、Laravel が動いています。 Laravel では次の動作が行われています。
- ルータにて指定された URL に紐付いた主処理を呼び出す
- コントローラにて主処理を行う
- ビューにて主処理の結果を取り込んだ画面を作成する
詳細に説明していきます。
ルータ
送信されたリクエストは、サーバ側から Laravel に渡されて、Laravel では事前に定められた URL のarticles
に紐付いたコントローラを呼び出します。
URI(articles
)とコントローラの紐付けは、routes/web.php
で行います。
Route::get('articles/{id}', 'BasicController@showArticle');
これは、GET メソッドでarticle
が呼び出されたとき、BasicController
のshowArticle
メソッドを呼ぶという設定です。
また、articles/{id}
の中括弧(braces,curly bracket)の部分は、パラメータを表しています。
このパラメータは、呼び出されるコントロールのメソッドの引数として自動で設定されます。
コントローラ
紐付けられたコントローラのshowArticle
メソッドの処理は下記になります。
//app/Http/Controllers/BasicController.php
public function showArticle(Request $request,$id) {
//本来ならデータベースなどの外部記憶媒体から$idで指定された記事を取得
$data =['article' => "{$id}の記事です。" ];
return view('show-article', $data);
}
Laravel は、ルートの設定に従って、showArticle
メソッドを呼び出します。メソッドのパラメータである$request
と$id
の引数(argument)は、Laravel が自動で設定します。
$id
は、route で指定したパラーメーターのことです。$request
は、リクエスト情報をまとめたオブジェクトです。
Request の詳細は下記で参照できます。
https://laravel.com/api/7.x/Illuminate/Http/Request.html
このメソッドでは、引数で受け取った$id
をキーとして、記事を取得する処理が実行されます。
ここでは説明を簡略化するために記事取得は省略しています。
その代わりに、"{$id
}の記事です。“という文字列を表示します。
文字列を表示するために、article
をキーとして文字列を連想配列$data
に格納しています。
その後、$data
と、show-article という文字列を引数としてviewヘルパ
というヘルパ関数を呼び出しています。
Laravel のヘルパ関数は、どこからでも呼び出せるグローバル関数です。 ユーティリティ的に使える関数がいくつも用意されています。 https://readouble.com/laravel/8.x/ja/helpers.html
View
view ヘルパは、"<第一引数で指定された文字列>.blade.php"
というファイルを、
resources/views/
以下で探します。
ここでは、show-article.blade.php
というファイルになります。
<!-- resources/views/show-article.blade.php -->
<html>
<head>
<title>show article</title>
</head>
<body>
{{ $article }}
</body>
</html>
これは Laravel のテンプレートエンジンである Blade で書かれたものになります。 テンプレートエンジンというのは、事前に用意したひな型(テンプレート)に、文字を差し込む(埋め込む)ものです。
このshow-article.blade.php
は、ほとんどが HTML ですが、一ヵ所だけ{{}}で囲まれた部分(6 行目)が文字を差し込む箇所です。
view ヘルパの第二引数として渡した連想配列 $data
の中から、article を key として値を取得します。
data['article']
として記述するのではなく、$article
として記述することになります。
テンプレートエンジン内部で、extract 関数で連想配列を展開しています。
レスポンス出力
コントローラーの処理終了後に、Laravel は、html を作成し、ヘッダを付与して responce としてサーバ経由でブラウザに返します。
レスポンスは、ヘッダ+コンテンツで構成されています。コンテンツはブラウザにて目視できますが、ヘッダは普段、見ることはありません。 Laravel がどのようなヘッダを返しているかを見てみましょう。
$ curl -I http://192.168.1.10:81/showname
HTTP/1.1 200 OK
Date: Wed, 11 Nov 2020 03:55:47 GMT
Server: Apache/2.4.38 (Debian)
X-Powered-By: PHP/7.4.0
Cache-Control: no-cache, private
Set-Cookie: XSRF-TOKEN=eyJpdiI6IkJsUXFNSTNCWUdGY1NOUHdiZ1hqRHc9PSIsInZhbHVlIjoiSkxVUDZIbkd0bTlkZGhKR21XUVVrcXdHNWRyUE50Zkc4TVdQcm1vdmwyV1pPbm9kNW1zVDJzSE4xaVNkcXFaR1FwR0R4Um5tT0czSGdiZ043TkJOUGZOa3B1c3IwdnRobXB5dzIzWGoyZVhPN28zM2ZJeGVyRHZWUG9BL0hxbEIiLCJtYWMiOiI0MTI1MDI0M2QwZmI2YzA1OTY0MDFmZGQ0MzQ1NDNmNTliYTljYTI4N2ZhZGYwMTY1YmE2NGQ4YmU0Yzg4YTM3In0%3D; expires=Wed, 11-Nov-2020 05:55:47 GMT; Max-Age=7200; path=/; samesite=lax
Set-Cookie: laravel_session=eyJpdiI6Ikw0NWVyUkdxeVovU2ZIL3doaTBqQ0E9PSIsInZhbHVlIjoiVytWeldKOGlaREJCdStDQlUvQWFEZXVHRGVHWWozMWxLVjZieFYrSlFWTGN5RUFUc2t3bEZFYnRJaGRLcldOaGo4K0xCK0M0bWlCUGtIUHJBTHp6MkN6a0NGa0dxb3lhYzdXNkZhTnB5RzZnVW5zcWVTdENXVmRKWWNESmNwWkIiLCJtYWMiOiI1ODhiMTU5MmYyM2FkOGQwZGYzNjc4NjFiMzA0OWYyYTE1M2UyZWY3ZTg2YzJkNTI2YTlkNzljOGU3ZWY4MGFlIn0%3D; expires=Wed, 11-Nov-2020 05:55:47 GMT; Max-Age=7200; path=/; httponly; samesite=lax
Vary: Accept-Encoding
Content-Length: 127
Content-Type: text/html; charset=UTF-8
curl(カール)コマンドで、-I オプションを付与することでヘッダを取得できます。
POSTメソッドでのLaravelでの処理の流れ
POST メソッドは、新規作成のデータをサーバに送信するとき、使われます。 ちなみに更新時は PUT メソッドになります。
http://localhost/shownameにアクセスしてください。
画面の入力欄に適当な文字を入力して SUBMIT をクリックすると、入力した文字が画面に表示されます。
このしくみを見ていきましょう。
ルータ
GET メソッドの /showname
のルーティングは下記となっています。
Route::get('/showname', function () {
return view('testpost');
});
ここでは、コントローラを呼ばずに第 2 引数をクロージャ(無名関数)として、関数の中で VIEW ヘルパを呼んでいます。
view
testpost という VIEW は下記になります。
<html lang="ja">
<head>
<title>test post</title>
</head>
<body>
<form method="POST" action="{{route('sendname')}}">
@csrf
<div>
name:
<input name="name" type="text" placeholder="Text input" >
</div>
<div>
<button>Submit</button>
</div>
</form>
</body>
</html>
submit ボタンをクリックすると、<form></form> で囲まれた中にあるフォームコントロール(INPUT,TEXT など)に入力された値が送信されます。
ここでは、input 要素の name という名前がついたフォームに入力された内容がサーバ側に送られます。
送信の際の HTTP メソッドは、POST 送信先は、{{route('sendname')}}
になります。
レスポンス出力
この test-post.blade.php
が、Laravel によって html 変換され、ブラウザでは下記が表示されます。
<html lang="ja">
<head>
<title>test post</title>
</head>
<body>
<form method="POST" action="http://localhost/showname">
<input type="hidden" name="_token" value="45TWw3dMuTfAHakN99KxUxKOUfLFIEbyTb1EGQlg">
<div>
name:
<input name="name" type="text" placeholder="Text input">
</div>
<div>
<button>Submit</button>
</div>
</form>
</body>
</html>
先程の{{route('sendname')}}
が [http://localhost/showname] に変換されています。
submit後のルータの処理
このように変換されるのは、routes/web.php
にて下記のようにname('sendname')
として指定しているからです。
Route::post('/showname', 'BasicController@showName')->name('sendname');
それぞれのルートは上記のように名前をつけることができます。url が長くなるときは、名前をつけておくて便利です。
/showname
のアクセスは、BasicController
クラスのshowName
メソッドに渡されます。
{{route('sendname')}}
以外にも、@csrf というものが、<input type="hidden" name="_token" value="45TWw3dMuTfAHakN99KxUxKOUfLFIEbyTb1EGQlg">
に変換されています。これはのちほど詳しく解説します。
submit後のcontrollerの処理とレスポンス出力
ルートと紐付けられたBasicController
のshowName
ではどのような処理を行っているのでしょうか?
public function showName(Request $request) {
$name = $request->name;
return $name;
}
リクエストから、name
と名付けられたデータを取り出します。
それをレスポンスとしてブラウザに返します。
GETメソッドとPOSTメソッドの違い
GET メソッドでは、リクエスト URL にパラーメーターを付け足すことでデータを送信します。 一方、POST メソッドでは、リクエストのボディ部(メッセージボディ)に FORM で記載されたデータを載せて送信しています。
方法が異なるとはいえ、どちらともデータを送信できます。 GET メソッドと POST メソッドの使い分けはどのようにすればよいのでしょうか?
インターネットの標準化団体 IETF(The Internet Engineering Task Force)が発行している、技術仕様があります。 RFC 文書のRFC2616 にそれぞれの用途が記述されています。
要約すると下記になります。
- GET メソッドは、データの要求をするもの。
- POST メソッドは、データの追加を要求するもの。
GET メソッドと POST メソッドでの用途が明確に違うようです。データの修正や追加などを行うときには GET メソッドではなくて POST メソッドを使うことになります。
まとめ
このように Web システムというのは、クライアントからの要求に対して返信をするものです。 要求をリクエスト(request)と言い、返信をレスポンス(responce)と言います。
responce というのは、html ファイルや画像などクライアンに返すものです。 responce を動的に作るのが、PHP の処理です。
Laravel は request と responce を処理する枠組みや便利なツールを提供しています。 Laravel の内部の動きは、下記で説明されています。 https://readouble.com/laravel/7.x/ja/lifecycle.html
CSRF防止について
Laravel は、POST メソッドのリクエストで、送信データに CSRF「トークン」がない場合、419 エラーを返します。 これは、サイバー攻撃の一種であるクロス・サイト・リクエスト・フォージェリ(CSRF)を防止するためのものになります。
CSRF「トークン」
Laravel では、CSRF「トークン」は、@csrf にて、付与できます。
Laravel は、サーバ側の CSRF「トークン」と要求元から送られてきたクライアント側の CSRF「トークン」を比較して、一致して初めて処理を行います。一致しない場合は、419 エラーを返し処理を中断します。
以下は、Laravel で CSRF「トークン」を比較しているメソッドです。
// Illuminate\Foundation\Http\Middleware\VerifyCsrfToken
/**
* Determine if the session and input CSRF tokens match.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function tokensMatch($request)
{
$token = $this->getTokenFromRequest($request);
return is_string($request->session()->token()) &&
is_string($token) &&
hash_equals($request->session()->token(), $token);
}
$this->getTokenFromRequest($request)
で、request の中にあるクライアント側の CSRF「トークン」を取得しています。
また、$request->session()->token()
でセッションに保存されているサーバ側の CSRF「トークン」を取得しています。その両者は、hash_equals で等しいかを確認しています。
CSRF「トークン」有無での動作の違い
実際に動作を見てみましょう。
CSRF「トークン」なしでのPOSTリクエスト
トークンを付与せずに POST メソッドでリクエストします。 curl(カール)というコマンドラインから Http リクエストを送信できるコマンドを利用します。
$ curl -b cookie.txt -X POST "http://localhost/showname" -d "name=test"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Expired</title>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<!-- Styles -->
<style>
html, body {
background-color: #fff;
color: #636b6f;
font-family: 'Nunito', sans-serif;
font-weight: 100;
height: 100vh;
margin: 0;
}
.full-height {
height: 100vh;
}
.flex-center {
align-items: center;
display: flex;
justify-content: center;
}
.position-ref {
position: relative;
}
.code {
border-right: 2px solid;
font-size: 26px;
padding: 0 15px 0 15px;
text-align: center;
}
.message {
font-size: 18px;
text-align: center;
}
</style>
</head>
<body>
<div class="flex-center position-ref full-height">
<div class="code">
419 </div>
<div class="message" style="padding: 10px;">
Page Expired </div>
</div>
</body>
</html>
419 のエラーになりました。
CSRF「トークン」付与でのPOSTリクエスト
次に CSRF「トークン」を取得してから、リクエストを送ります。
まずは、CSRF「トークン」を取得します。
$ curl -c cookie.txt http://192.168.1.10:81/showname
<html lang="ja">
<head>
<title>test post</title>
</head>
<body>
<form method="POST" action="http://192.168.1.10:81/showname">
<input type="hidden" name="_token" value="fGBm5KFPXH8PCqv9N58Bw7JO7sMgBYp69SYwv3mH"> <div>
name:
<input name="name" type="text" placeholder="Text input" >
</div>
<div>
<button>Submit</button>
</div>
</form>
</body>
</html>
上記レスポンスの_token
の value
の値が、CSRF「トークン」になります。
_token
の値をコピーして下記の XXXXXXXX と入れ替えます。
$ curl -b cookie.txt -X POST "http://192.168.1.10:81/showname" -d "_token=XXXXXXXX&name=test"
test
test と文字が表示されましたよね。 CSRF「トークン」がサーバ側のものと一致したため処理が行われました。 CSRF「トークン」は、リクエストの度に異なるものが送信されサーバ側で保持する CSRF「トークン」も同様に書き換ります。また、有効期限は、セッションの保持期間と同じなります。
古い CSRF「トークン」を使用するとエラーになることを確認してみてください。