Middleware mendalam

Middleware kustom, grup middleware, dan parameter middleware.

Daftar isi

Middleware adalah lapisan penyaring permintaan HTTP yang dijalankan sebelum dan sesudah rute aplikasi, sehingga pekerjaan lintas rute seperti pemeriksaan sesi, verifikasi akun, dan pencatatan dapat ditempatkan di satu tempat. Topik ini membahas cara pipeline middleware bekerja, cara membuat dan mendaftarkan middleware kustom pada aplikasi catatan, serta cara memberi parameter pada middleware. Setelah topik ini dibaca, urutan eksekusi setiap lapisan tidak lagi berupa kotak hitam, melainkan dapat disimulasikan dan diuji secara langsung.

Pipeline permintaan#

Sekumpulan middleware membentuk pipeline, yaitu rangkaian lapisan yang dilalui permintaan sebelum mencapai rute dan dilalui lagi dalam urutan terbalik setelah respons dibuat. Setiap middleware menerima dua argumen, yaitu objek permintaan dan closure bernama $next yang mewakili sisa pipeline. Kode sebelum pemanggilan $next($request) berjalan pada permintaan masuk, sedangkan kode setelahnya berjalan pada respons keluar.

Alur dua lapisan tersebut digambarkan pada diagram berikut.

Permintaan masukRespons keluarTandaAwalTandaAkhirRute aplikasirespons dibuat1. sebelum (masuk)TandaAwalTandaAkhir2. rute dijalankan3. sesudah (keluar)TandaAkhir, TandaAwal
Urutan eksekusi dua middleware pada satu permintaan
Urutannya dapat dibuktikan dengan Illuminate\Pipeline\Pipeline, yaitu kelas yang membangun pipeline yang sama seperti pipeline yang digunakan framework.

bash
# membuktikan urutan eksekusi pipeline dengan dua lapisan
cd ~/aplikasi-catatan
php artisan tinker --execute='
use Illuminate\Pipeline\Pipeline;

class TandaAwal
{
    public function handle($permintaan, Closure $next)
    {
        echo "TandaAwal: sebelum" . PHP_EOL;
        $respons = $next($permintaan);
        echo "TandaAwal: sesudah" . PHP_EOL;
        return $respons;
    }
}
class TandaAkhir
{
    public function handle($permintaan, Closure $next)
    {
        echo "TandaAkhir: sebelum" . PHP_EOL;
        $respons = $next($permintaan);
        echo "TandaAkhir: sesudah" . PHP_EOL;
        return $respons;
    }
}
$hasil = app(Pipeline::class)
    ->send("permintaan catatan")
    ->through([TandaAwal::class, TandaAkhir::class])
    ->then(fn ($permintaan) => "sampai ke tujuan");
echo "akhir: " . $hasil . PHP_EOL;
'
text Keluaran
TandaAwal: sebelum
TandaAkhir: sebelum
TandaAkhir: sesudah
TandaAwal: sesudah
akhir: sampai ke tujuan

Keluaran tersebut memperlihatkan pola yang konsisten, yaitu lapisan pertama mencetak keluarannya lebih dahulu, sedangkan keluaran penutupnya justru muncul paling akhir. Simulasi berikut memperlihatkan pola yang sama secara interaktif, termasuk apa yang terjadi ketika salah satu lapisan menghentikan permintaan sebelum mencapai rute. Jalankan simulasi, ubah pilihan setiap lapisan, kemudian amati urutan keluaran yang dihasilkan.

Membuat middleware kustom#

Middleware kustom dibuat dengan perintah make:middleware. Pada aplikasi catatan, sebuah middleware akan digunakan untuk memastikan pengguna yang mengakses catatan sudah memverifikasi emailnya.

bash
# tidak-dijalankan
# membuat middleware bernama PastikanTerverifikasi
cd ~/aplikasi-catatan
php artisan make:middleware PastikanTerverifikasi

Perintah tersebut menghasilkan berkas app/Http/Middleware/PastikanTerverifikasi.php beserta direktorinya apabila belum ada. Keluaran aslinya berbentuk berikut.

text Hasil perintah make:middleware

   INFO  Middleware [app/Http/Middleware/PastikanTerverifikasi.php] created successfully.  

Kerangka yang dihasilkan masih murni meneruskan permintaan tanpa melakukan apa pun. Isi berkasnya berbentuk berikut.

php PastikanTerverifikasi.php
// tidak-dijalankan
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class PastikanTerverifikasi
{
    /**
     * Handle an incoming request.
     *
     * @param  Closure(Request): (Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        return $next($request);
    }
}

Satu-satunya pekerjaan yang dilakukan kerangka tersebut adalah memanggil $next($request), sehingga permintaan lolos tanpa pemeriksaan. Logika pemeriksaan dituliskan dengan menggantikan baris tersebut, misalnya sebagai berikut.

php Contoh: isi handle() untuk memeriksa verifikasi email
// tidak-dijalankan
public function handle(Request $request, Closure $next): Response
{
    if ($request->user()?->email_verified_at === null) {
        return redirect("/email/verify");
    }

    return $next($request);
}

Apabila pengguna belum memverifikasi email, middleware mengembalikan respons pengalihan dan pipeline berhenti di lapisan itu. Apabila email sudah terverifikasi, permintaan diteruskan ke lapisan berikutnya.

Pendaftaran: alias, grup, dan global#

Middleware yang sudah dibuat perlu didaftarkan sebelum dapat digunakan pada rute. Pendaftaran dilakukan pada metode withMiddleware() di berkas bootstrap/app.php.

php Contoh: pendaftaran alias middleware pada bootstrap/app.php
// tidak-dijalankan
->withMiddleware(function (Middleware $middleware): void {
    $middleware->alias([
        "terverifikasi" => \App\Http\Middleware\PastikanTerverifikasi::class,
    ]);
})

Setelah alias terdaftar, rute dapat menggunakannya dengan nama pendek, misalnya Route::get(...)->middleware("terverifikasi"). Laravel mengenali tiga cara pemasangan middleware yang masing-masing memiliki cakupan berbeda.

Cara pemasanganCakupanCara menulisnya
GlobalSemua permintaanDipasang melalui $middleware->append() pada bootstrap/app.php.
GrupSeluruh rute dalam satu grup, misalnya web atau apiDitambahkan melalui $middleware->group() atau appendToGroup().
AliasRute tertentu yang memintanyaDidaftarkan melalui $middleware->alias(), lalu dipasang pada rute.

Grup web bawaan sudah memuat beberapa middleware penting, antara lain EncryptCookies, StartSession, dan SubstituteBindings yang mengisi parameter model pada rute. Karena itu, rute pada routes/web.php secara bawaan mengenal sesi dan binding model tanpa konfigurasi tambahan.

Parameter middleware#

Middleware dapat menerima parameter yang ditulis pada rute setelah tanda titik dua, misalnya batas:kerja. Parameter berguna ketika satu middleware digunakan untuk banyak keperluan dengan aturan yang sedikit berbeda. Contoh berikut membatasi akses rute berdasarkan kelompok yang dikirim melalui header permintaan.

php Contoh: middleware dengan parameter grup
// tidak-dijalankan
class BatasKategoriMiddleware
{
    public function handle($request, Closure $next, string $grup)
    {
        if ($request->header("X-Grup") !== $grup) {
            return response("akses ditolak untuk grup " . $grup, 403);
        }
        return $next($request);
    }
}

Perilakunya dapat diuji langsung dengan mendaftarkan alias saat aplikasi berjalan, kemudian mengirim dua permintaan yang berbeda ke rute yang sama.

bash
# menguji middleware berparameter dengan dua permintaan
cd ~/aplikasi-catatan
php artisan tinker --execute='
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

class BatasKategoriMiddleware
{
    public function handle($request, Closure $next, string $grup)
    {
        if ($request->header("X-Grup") !== $grup) {
            return response("akses ditolak untuk grup " . $grup, 403);
        }
        return $next($request);
    }
}

Route::aliasMiddleware("batas", BatasKategoriMiddleware::class);
Route::middleware("batas:kerja")->get("/demo-batas", function () {
    return "selamat datang di kategori kerja";
});

$dengan = app()->handle(Request::create("/demo-batas", "GET", server: ["HTTP_X_GRUP" => "kerja"]));
echo $dengan->getStatusCode() . ": " . $dengan->getContent() . PHP_EOL;
$tanpa = app()->handle(Request::create("/demo-batas", "GET"));
echo $tanpa->getStatusCode() . ": " . $tanpa->getContent() . PHP_EOL;
'
text Keluaran
200: selamat datang di kategori kerja
403: akses ditolak untuk grup kerja

Permintaan dengan header X-Grup: kerja lolos dengan status 200, sedangkan permintaan tanpa header ditolak dengan status 403. Lebih dari satu parameter dituliskan pada rute dengan pemisah koma, misalnya batas:kerja,editor, dan setiap parameter menjadi argumen tambahan setelah $next.

Kesalahan umum#

Kesalahan pertama adalah lupa mengembalikan hasil $next($request). Middleware yang tidak mengembalikan respons membuat permintaan berakhir dengan respons kosong tanpa jejak error yang jelas karena pipeline menunggu nilai kembalian dari setiap lapisan. Pastikan setiap cabang handle() mengembalikan hasil $next() atau respons yang dibuat sendiri.

Kesalahan kedua adalah menggunakan alias yang belum didaftarkan pada bootstrap/app.php. Rute yang menggunakannya gagal dengan respons 500 saat diakses, dan penyebabnya hanya terlihat pada log aplikasi. Periksa daftar alias setiap kali alias baru dituliskan pada rute.

Kesalahan ketiga adalah menuliskan kode penting setelah return $next($request) tanpa menampung nilainya. Kode tersebut tidak pernah dijalankan karena baris return langsung mengakhiri metode. Apabila ada pekerjaan yang perlu dilakukan pada respons keluar, tampung hasil $next($request) terlebih dahulu, terapkan pekerjaan tersebut pada objek responsnya, kemudian kembalikan.

Kesalahan keempat adalah menempatkan middleware global pada setiap rute tanpa menimbang beban. Middleware global dieksekusi pada seluruh permintaan, termasuk permintaan yang nantinya ditolak rutenya, sehingga pekerjaan berat semacam pembacaan database yang hanya dibutuhkan satu kelompok rute menjadi beban bagi seluruh aplikasi. Middleware semacam itu lebih tepat dipasang pada grup atau rute tertentu, dan pemeriksaan php artisan route:list yang menampilkan middleware setiap rute membantu menilai apakah penempatannya sudah tepat.

Ringkasan#

  • Middleware membentuk pipeline dua arah, yaitu kode sebelum $next($request) berjalan pada permintaan masuk dan kode setelahnya pada respons keluar.
  • Middleware kustom dibuat dengan make:middleware, diisi pada metode handle(), lalu didaftarkan sebagai alias, anggota grup, atau middleware global pada bootstrap/app.php.
  • Parameter middleware dituliskan pada rute setelah tanda titik dua dan diterima sebagai argumen tambahan setelah $next.
  • Middleware yang menghentikan permintaan cukup mengembalikan responsnya sendiri tanpa memanggil $next, dan lapisan-lapisan di luarnya tetap menjalankan kode pada sisi respons.

Referensi#