Pipeline Middleware

Pipeline request dan middleware kustom.

Daftar isi

Pipeline middleware adalah rantai komponen yang dilewati setiap request sebelum mencapai endpoint dan kembali sebagai response. Setiap middleware memperoleh kesempatan memproses request pada jalan menuju endpoint dan memproses response pada jalan pulangnya, sehingga pencatatan, autentikasi, dan penanganan error ditempatkan pada satu lapisan yang sama. Topik ini membahas urutan eksekusi pipeline, penulisan middleware kustom dengan app.Use, dan demo yang mencetak urutan tersebut dari eksekusi nyata.

Pipeline request dan urutan middleware#

ASP.NET Core menangani setiap request dengan meneruskannya melalui deretan delegate yang disusun saat aplikasi dibangun. Komponen pada deretan itu disebut middleware, dan komponen yang ditambahkan lebih dahulu menjadi yang pertama menerima request. Setiap middleware memutuskan apakah request diteruskan ke middleware berikutnya atau dihentikan di tempat. Middleware yang meneruskan request dapat menjalankan kode lagi setelah penerusan selesai, yaitu pada saat response dalam perjalanan kembali, sehingga satu middleware dapat mencatat kejadian sebelum dan sesudah endpoint dijalankan.

Banyak kemampuan bawaan ASP.NET Core dibangun dengan cara yang sama. Routing, berkas statis, autentikasi, dan penanganan error semuanya merupakan middleware yang dipasang oleh templat proyek atau dipasang sendiri oleh aplikasi. Karena urutan pemasangan menentukan urutan eksekusi, middleware yang memerlukan hasil kerja middleware lain harus dipasang setelahnya, sedangkan middleware pencatat biasanya dipasang paling awal agar melihat setiap request. Contoh kerjanya beragam, mulai dari mencatat durasi, memeriksa token, sampai menambahkan header keamanan pada response. Diagram berikut memperlihatkan jalur dua middleware kustom pada satu pipeline.

Alur request dan response melalui dua middlewarerequestresponseklienmiddleware 1pencatatmiddleware 2pengukurendpoint
Alur request melewati dua middleware secara berurutan menuju endpoint, kemudian response kembali melewati middleware yang sama dengan urutan terbalik

Middleware kustom dengan app.Use#

Metode app.Use menambahkan middleware ke pipeline berupa satu lambda yang menerima HttpContext dan delegate penerus. Delegate itu, yang pada contoh diberi nama lanjut, menjalankan sisa pipeline, sedangkan await pada pemanggilannya membuat middleware menunggu seluruh bagian di belakangnya selesai. Kode sebelum await lanjut() berjalan pada fase request, sedangkan kode sesudahnya berjalan pada fase response setelah endpoint dan middleware di belakangnya selesai.

csharp Contoh: middleware pencatat
// tidak-dijalankan
app.Use(async (context, lanjut) =>
{
    Console.WriteLine("request masuk");
    await lanjut();
    Console.WriteLine("response selesai dikirim");
});

Lambda di atas mencatat kejadian pada dua fase yang berbeda tanpa mengetahui endpoint yang ada di belakangnya. Middleware juga dapat menghentikan penyebaran request dengan tidak memanggil lanjut sama sekali, misalnya untuk menolak request tanpa token, dan pola ini disebut short-circuit karena sisa pipeline tidak pernah dijalankan. Selain Use, metode app.Run menambahkan middleware terminal yang tidak menerima penerus, sedangkan app.Map membuat cabang pipeline yang hanya dijalankan untuk awalan URL tertentu. Pembagian ini menjaga kebijakan cross-cutting, yaitu kebijakan yang berlaku lintas request, tetap terpisah dari kode endpoint. Middleware yang dipasang dengan Use juga tidak perlu mengetahui daftar endpoint yang ada, karena keputusan meneruskan atau menghentikan request sepenuhnya berada di tangan middleware yang sedang berjalan.

Demo mengamati urutan eksekusi#

Program berikut memasang dua middleware dengan app.Use lalu memanggil dua endpoint melalui HttpClient dari dalam proses yang sama. Middleware pertama diberi tanda [1] dan middleware kedua [2], sehingga urutan baris keluaran menunjukkan jalannya request dan response pada kedua endpoint. Server dimulai dengan StartAsync dan dihentikan dengan StopAsync, sehingga setiap eksekusi menghasilkan urutan yang sama. Aktifkan .NET pada sesi terminal dengan perintah berikut sebelum mencoba programnya.

bash
# aktifkan .NET yang terpasang di sandbox
export DOTNET_ROOT=/home/kal/.dotnet PATH=/home/kal/.dotnet:$PATH DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1

Simpan program berikut sebagai Program.cs di folder topik, lalu jalankan dengan perintah dotnet run --file Program.cs.

csharp Program.cs
#:sdk Microsoft.NET.Sdk.Web

var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();

var app = builder.Build();
app.Urls.Add("http://127.0.0.1:5021");

app.Use(async (context, lanjut) =>
{
    Console.WriteLine("[1] pencatat : request masuk");
    await lanjut();
    Console.WriteLine($"[1] pencatat : response kembali ({context.Response.StatusCode})");
});

app.Use(async (context, lanjut) =>
{
    Console.WriteLine("[2] pengukur : sebelum endpoint");
    await lanjut();
    Console.WriteLine("[2] pengukur : sesudah endpoint");
});

app.MapGet("/salam", () => Results.Text("Halo dari endpoint"));
app.MapGet("/tugas", () => Results.Text("Daftar tugas kosong"));

await app.StartAsync();
using var client = new HttpClient();
var jawab1 = await client.GetAsync("http://127.0.0.1:5021/salam");
Console.WriteLine($"GET /salam -> {(int)jawab1.StatusCode} {await jawab1.Content.ReadAsStringAsync()}");
var jawab2 = await client.GetAsync("http://127.0.0.1:5021/tugas");
Console.WriteLine($"GET /tugas -> {(int)jawab2.StatusCode} {await jawab2.Content.ReadAsStringAsync()}");
await app.StopAsync();
text Keluaran
[1] pencatat : request masuk
[2] pengukur : sebelum endpoint
[2] pengukur : sesudah endpoint
[1] pencatat : response kembali (200)
GET /salam -> 200 Halo dari endpoint
[1] pencatat : request masuk
[2] pengukur : sebelum endpoint
[2] pengukur : sesudah endpoint
[1] pencatat : response kembali (200)
GET /tugas -> 200 Daftar tugas kosong

Keluaran tersebut menegaskan tiga sifat pipeline. Pertama, eksekusinya berlapis, yaitu [1] membuka jalan, [2] mengikutinya, kemudian keduanya menutup dengan urutan terbalik [2] lalu [1]. Kedua, middleware yang sama berjalan untuk kedua endpoint, terbukti dari blok empat baris yang berulang persis untuk /salam dan /tugas, sehingga kebijakan seperti pencatatan cukup dipasang satu kali. Ketiga, kode sesudah await lanjut() melihat keadaan akhir pipeline, terlihat dari status 200 yang dibaca context.Response.StatusCode setelah endpoint selesai. Baris milik klien muncul setelah blok milik server karena HttpClient baru melanjutkan eksekusi setelah response selesai sepenuhnya, dan response baru selesai setelah seluruh middleware menutup jalannya.

Kesalahan umum#

  • Lupa await lanjut() pada middleware Use. Pipeline berhenti di middleware tersebut, sehingga endpoint tidak pernah dijalankan dan klien menerima response kosong. Middleware yang meneruskan request wajib menunggu delegate penerusnya.
  • Mengubah header atau status setelah await lanjut() padahal response sudah mulai dikirim. Perubahan semacam ini memicu InvalidOperationException, sehingga penulisan status dan header ditempatkan sebelum pemanggilan lanjut atau di dalam endpoint.
  • Mengharapkan middleware Use dijalankan hanya untuk rute tertentu. Middleware yang dipasang dengan Use memproses setiap request, sehingga pemilahan jalur dikerjakan app.Map atau app.UseWhen dengan kondisi yang dinyatakan secara eksplisit.

Ringkasan#

  • Pipeline middleware meneruskan setiap request melalui komponen yang berurutan, dan urutan pemanggilan Use menentukan urutan eksekusinya.
  • Middleware kustom memperoleh HttpContext dan delegate penerus, dengan kode sebelum await lanjut() berjalan pada fase request dan kode sesudahnya pada fase response.
  • Middleware dapat menghentikan penyebaran request dengan tidak memanggil penerusnya, sedangkan app.Run dan app.Map menyediakan bentuk terminal dan percabangan.
  • Middleware yang sama dipanggil untuk setiap request, sehingga kebijakan lintas request seperti pencatatan cukup ditulis satu kali.

Referensi#