สื่อตัวกลาง (Middleware)
สื่อตัวกลาง หมายถึง กลไกการทำงานในการตรวจสอบหรือกรองการทำงานของคำขอ(request) HTTP คำขอ HTTP เช่น คำขอในด้าน input, cookies, file ที่ส่งมาพร้อมกับคำของ ยกตัวอย่างเช่น การตรวจสอบคำขอถึงสิทธิ์ในการเข้าระบบเว็บ ถ้าคำขอที่ส่งข้อมูลระบุตัวตนถูกต้อง ก็จะถือว่าเข้าระบบได้ ในการทำงานนี้สื่อตัวกลางจึงทำหน้าที่นี้ เป็นเหมือนตัวขั้นกลางก่อนการทำงานอื่นต่อไป
สร้างสื่อตัวกลาง
การสร้างใช้ CLI สมมุติให้ชื่อ EnsureTokenIsValid เพื่อจำลองการอ่านรหัสลับ (Token) ที่ส่งมาตรวจสอบก่อนที่จะให้สามารถผ่านไปหน้าเว็บต่อไปได้ (การสร้างพิมพ์สร้างสื่อตัวกลางนี้ make:middleware ต้องพิมพ์ติดกัน)
>php artisan make:middleware EnsureTokenIsValid
เมื่อสร้างจาก CLI ให้แก้ไขในส่วนฟังก์ชัน handle( ) โดยการตรวจสอบว่า รหัสลับต้องเป็น my-secret-token จึงจะไปยังหน้าต่อไปได้ ถ้ารหัสไม่ผ่านก็ให้กลับไปยังหน้า home หน้าเว็บ home ให้สร้างไฟล์นี้ชื่อ home.blade.php รอรับการทำงานไว้ด้วย
Code 1. app/Http/Middleware/EnsureTokenisValid.php
<?php
namespace App\Http\Middleware;
use \Illuminate\Http\Request;
use Closure;
class EnsureTokenIsValid
{
public function handle(Request $request, Closure $next)
{
if ($request->input('token') !== 'my-secret-token') {
return redirect('home');
}
return $next($request);
}
}
ลงทะเบียนการใช้สื่อตัวกลาง
เพื่อการใช้งานสื่อตัวกลาง ให้ลงทะเบียนไว้ที่ไฟล์ Kernel.php ไฟล์นี้จะทำให้สื่อตัวกลางที่ลงทะเบียนใช้งานได้ทั้งเว็บแอปฯ โดยการเขียนชื่อของสื่อตัวกลางในส่วนอาร์เรย์ของ $routeMiddleware ในชื่อที่ใดก็ได้ ในที่นี้ใช้ชื่อว่า ‘ensuretoken’ โดยผูกไว้กับไฟล์สื่อตัวกลางที่ได้สร้างไว้
Code 2. app/Http/Kernel.php
protected $routeMiddleware = [
// สื่อตัวกลางอื่น ๆ
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'ensuretoken' => \App\Http\Middleware\EnsureTokenIsValid::class,
];
สร้างเส้นทางรองรับสื่อตัวกลาง
จากสื่อตัวกลางที่ใช้ตรวจสอบข้อมูลเข้าจากคำขอของ HTTP โดยสมมุติว่า มีเส้นทางไปยังหน้าเว็บ welcome ต้องผ่านสื่อกลางก่อนให้ใช้ฟังก์ชัน middleware(‘ensuretoken’) ต่อท้ายฟังก์ชัน get()
Code 3. routes/web.php
Route::get('/', function (Request $request) {
return view('welcome');
})->middleware('ensuretoken');
ฟังก์ชัน get( ) เป็นการขอบริการของ HTTP ผ่าน URL: / ซึ่งถ้าเราทดลองใช้
- URL: http://localhost:8000/ จะไม่ตัวแปรใดไปกับ get( ) ด้วย ทำให้ หน้าเว็บเปิดที่หน้าเว็บ home
- ถ้าใส่ตัวแปรไปกับ URL: http://localhost:8000/?token=my-secret-token จะทำให้เปิดเว็บที่หน้า welcome ตามคำสั่ง return $next($request)
การสร้างเส้นทางรองรับสื่อตัวกลางที่มีหลายสื่อตัวกลาง สามารถใส่พร้อมกันหลายตัวได้ในรูปอาร์เรย์ เช่น
Route::get('/', function(){
})->middleware('ensuretoken', 'auth');
หรือจะใส่ชื่อคลาสแทนชื่อที่ลงทะเบียนก็ได้ และด้วยวิธีการนี้ก็ไม่จำเป็นต้องลงทะเบียนในไฟล์ Kernel.php เช่น
Route::get('/'', function(){
})->middleware(EnsureTokenIsValid::class);
สร้างสื่อตัวกลางรองรับ CORS
CORS ย่อมาจา Cross Origin Resource Sharing ประกอบด้วย โดเมน และเลขที่พอร์ทสำหรับสื่อสาร เช่น http://127.0.0.1:8000 การสื่อสารกันได้ภายใต้ CORS จะต้อง จำกัดภายใต้โดเมน และเลขที่พอร์ท นี้เท่านั้น เช่น ถ้าใช้ http://127.0.0.1:4200 ก็จะถือว่า ผิดนโยบาย CORS ที่ทำให้สื่อสารกันไม่ได้ และยิ่ง เป็นโดเมนอื่น ก็ยิ่งผิดนโยบายไปมาก
ในการพัฒนาเว็บแอปพลิเคชัน บางครั้งก็ จำเป็นต้อง ทดลองการทำงานร่วมกัน ที่ฝืนนโยบายของ CORS บ้าง การฝืนนโยบาย ทำได้ทั้งในฝั่งไคลเอ็นท์ และเซิร์บเวอร์ การแก้ที่เซิร์บเวอร์เป็นวิธีการทำได้ง่าย และแน่นอนที่สุด ดังนั้นเรามาสร้างการฝืนนโยบายนี้ โดยสร้างเป็น สื่อตัวกลาง Cors
> php artisan make:middleware Cors
สร้าง header ที่รองรับการสื่อสารข้ามโดเมนและพอร์ท โดยให้ยอมรับ localhost:4200ซึ่งมาจากเว็บที่พัฒนาจาก Angularดังแก้ไขคลาส Cors ดังนี้
Code 4. app/Http/Middleware/Cors.php
class Cors{
public function handle(Request $request, Closure $next){
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
}
}
และลงทะเบียนกับ Kernel ในระดับ Global คือใช้ได้กับทุกเส้นทาง
Code 5. app/Http/Kernel.php
protected $middleware = [
// สื่อตัวกลางอื่น ๆ ของระดับ Global
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\Cors::class, //ตัวที่เพิ่มไปใหม่
];
สร้างเส้นทาง products ทดสอบ การอ่านให้อ่านค่าออกเป็น JSON
Code 6. routes/web.php
Route::get('/products', function(){
$pro1 = array('id'=>1, 'name'=>'RAM');
$pro2 = array('id'=>2, 'name'=>'Harddisk');
return array($pro1, $pro2);
});
ทำสอบการอ่านข้าม Port โดยใช้ Port: 80 ของเซิร์บเวอร์ของ XAMPP เก็บไฟล์งานที่โฟลเดอร์ test_cors เมื่อทดสอบจะสามารถอ่านข้อมูลได้ แต่ถ้าทดลองเปลี่ยนโดยการเอาสื่อตัวกลาง Cors ออกจาก Kernel.php จะพบว่าไม่สามารถเข้าถึงข้อมูลได้
Code 6. xampp/htdocs/test_cors/index.html
<!DOCTYPE html>
<html lang="en">
<body>
<h2>Title</h2>
<ul></ul>
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js">
</script>
<script>
$.ajax({
method:"GET",
url:"http://127.0.0.1:8000/products",
dataType:"json",
success:function(data){
data.forEach(i=>{
str = i.id + ":"+ i.name;
$('<li>').text(str).appendTo('ul');
});
},
error:function(){
alert("Error");
}
});
</script>
</body>
สื่อตัวกลางแบบกลุ่ม
จากที่ผ่านมา การใช้งานสื่อตัวกลางต้องเขียนต่อท้ายการสร้างเส้นทาง แต่ถ้าต้องการให้ใช้ได้กับเส้นทางอื่นพร้อมกัน โดยไม่ต้องเขียนแต่ละเส้นทาง วิธีการคือต้องสร้างสื่อตัวกลางแบบกลุ่ม
การสร้างกลุ่มของสื่อตัวกลาง ใช้การลงทะเบียนในไฟล์ Kernel.php ในตัวแปร $middlewareGroups ซึ่งมี web และ api ได้มีลงทะเบียนไว้แล้ว
Code 8. app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
เมื่อลงทะเบียนไว้แล้ว ก็นำไปใช้กับระบบเส้นทางได้ ซึ่งใช้ในลักษณะเดียวกับสื่อตัวกลางเดี่ยว (ไม่เป็นกลุ่ม) และใช้ในลักษณะกลุ่มโดยตรงก็ได้ ดัง 2 ตัวอย่างต่อไปนี้
Route::get('/', function () {
//
})->middleware('web');
Route::middleware(['web'])->group(function () {
//
});
สื่อตัวกลางทำงานตามลำดับ
ในบางครั้งเราอาจต้องการให้สื่อตัวกลางทำงานตามลำดับ กรณีเช่นนี้ ให้ลงทะเบียนผ่านตัวแปรชื่อ $middlewarePriority ในไฟล์ Kernel.php ตัวแปรนี้ยังได้กำหนดมาตั้งแต่ต้น ดังนั้นหากเราต้องการทำงานตามลำดับ ก็ต้องสร้างขึ้นเอง
Code 9. app/Http/Kernel.php
protected $middlewarePriority = [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
ส่งตัวแปรไปกับสื่อตัวกลาง
การรับตัวแปรมากับสื่อตัวกลาง จะเป็นตัวแปรที่ สาม ต่อกับตัวแปร $next ของฟังก์ชัน handle เช่น ใช้ตัวแปรในชื่อ $role ในตัวอย่างต่อไปนี้ สมมุติเหตุการณ์เป็นการตรวจสอบตัวแปร $role ว่ามีค่าเป็น “manager” หรือไม่ ส่วนสองตัวแปรแรกค่าเป็นค่าตัวแปรที่ใส่มาให้ตั้งแต่สร้างฟังก์ชัน handle
Code 10. app/Http/Middleware/EnsureUserHasRole.php
<?php
namespace App\Http\Middleware;
use Closure;
class EnsureUserHasRole
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $role
* @return mixed
*/
public function handle($request, Closure $next, $role) {
if ($role !== 'manager') {
return redirect('/');
}
echo $role;
return $next($request);
}
}
และเช่นเคยลงทะเบียนสื่อตัวกลาง
Code 11. app/Http/Kernel.php
protected $routeMiddleware = [
// สื่อตัวกลางอื่น ๆ
'role' => \App\Http\Middleware\EnsureUserHasRole::class,
];
สำหรับการส่งตัวแปรไปกับเส้นทาง ใช้เครื่องหมาย : นำหน้าตัวแปรที่จะไปกับสื่อตัวกลาง ตามตัวอย่างต่อไปนี้
Code 12. routes/web.php
Route::get('/admin', function () {
return view('admin');
})->middleware('role:manager');
สำหรับหน้า view: admin ก็ให้สร้างรองรับไว้ ในชื่อไฟล์ admin.blade.php และทำการทดสอบตาม URI : /admin
หยุดการทำงานสื่อตัวกลาง
สื่อตัวกลางจะหยุดทำงานอัตโนมัติเมื่อ มีการตอบสนอง (response) กลับมายังเบราเซอร์ แต่เราสามารถจัดการในขั้นตอนที่สื่อตัวกลางจะหยุดทำงาน โดยการใช้ฟังก์ชัน terminate($request, $response) ซึ่งฟังก์ชันนี้รองรับตัวแปรสองตัว
Code 13. app/Http/Middleware/TerminateMiddlewar.php
<?php
namespace App\Http\Middleware;
use Closure;
use \Illuminate\Http\Request;
use \Illuminate\Http\Response;
class TerminateMiddlewar
{
public function handle($request, Closure $next)
{
echo "Handle method worked.";
return $next($request);
}
public function terminate($request, $response)
{
echo "Terminate method worked.";
}
}
ต่อมาทำการลงทะเบียนกับไฟล์ Kernel.php ในชื่อ “terminate”
Code 14. app/Http/Kernel.php
protected $routeMiddleware = [
// สื่อตัวกลางอื่น ๆ
'role' => \App\Http\Middleware\EnsureUserHasRole::class,
'terminate' => \App\Http\Middleware\TerminateMiddlewar::class,
];
ทดสอบกับเส้นทางเดิม แต่เพิ่มสื่อตัวกลางไปกับ role ในรูปอาร์เรย์ ทำให้เส้นทาง /admin ทำงานสองสื่อตัวกลาง ซึ่งจะได้ผลการพิมพ์ค่า "Terminate method worked" เป็นลำดับสุดท้าย
Code 15. routes/web.php
Route::get('/admin', function () {
return view('admin');
})->middleware(['role:manager', 'terminate']);