Đầu tiên phải nói là bài viết sẽ thuần kỹ thuật và mình sẽ lấy .Net làm ví dụ minh họa, tuy nhiên mình sẽ giải thích một cách tự nhiên nhất với các ví dụ để bạn có thể hình dung được với những ngôn ngữ và công nghệ khác sẽ có cơ chế tương tự.
Trước khi nói đến middlewares là gì mình đề cập qua một chút về kiến trúc client – server. Đây là một kiến trúc được bắt nguồn từ mạng máy tính sau lan rộng sang phần mềm, đặc biệt là web. Về bản chất nó là một máy client giao tiếp với một máy server để lấy thông tin. Website hiện này cũng dưới dạng hình thức như vậy, trình duyệt web gọi tới máy chủ để lấy dữ liệu và hiển thị dữ liệu đó cho người dùng. Để hiểu thêm về mô hình này bạn có thể đọc lại bài viết trước đó của mình, “Client Server Architecture – Kiến trúc client-server
Đối với mô hình client – server nói chung và một mô hình web nói riêng thì một request (thứ mà client gọi tới máy chủ) được đi qua các lớp thành phần cho đến khi lấy được dữ liệu trả về gọi là response. Đây chính là pipeline. Pipeline chỉ ra cách mà ứng dụng phản hồi với HTTP Request. Request đến từ trình duyệt đi qua pipeline và quay trở lại khi xử lý xong để trả về client.
Những thứ ở giữa chặn ngang dòng request pipeline này đều được gọi là middleware.
Định nghĩa cụ thể của Middleware sẽ là thành phần của phần mềm đóng vai trò tác động vào request pipeline để xử lý chúng và tạo ra response phản hồi lại client. 
Mỗi một tiến trình middleware thao tác với các request nhận được từ middleware trước đó và nó cũng có thể quyết định gọi middleware tiếp theo trong pipeline hay trả về response cho middleware ngay trước nhằm thực hiện hành vi dừng request pipeline lại.
Cụ thể trong Asp net core, middleware sẽ gồm nhiều tầng như sau:
Middleware đầu tiên sẽ nhận request, xử lý và gán nó cho middleware tiếp theo. Quá trình này tiếp diễn cho đến khi đi đến middleware cuối cùng. Tùy thuộc bạn muốn pipeline của bạn có bao nhiêu middleware.
Middleware cuối cùng sẽ trả request ngược lại cho middleware trước đó, và sẽ ngắt quá trình trong request pipeline.
Mỗi Middleware trong pipeline sẽ tuần tự có cơ hội thứ hai để kiểm tra lại request và điểm chỉnh response trước khi được trả lại.
Đối với từng công nghệ hay các version của một công nghệ nào đó sẽ có những thay đổi đôi chút trong request pipeline nên bạn có thể phải đọc đôi chút về tài liệu đặc thù thì mới có thể nắm được một vòng đời và thứ tự của các request sẽ đi đâu và về đâu từ đó tùy biến chúng theo yêu cầu bài toán của cá nhân bạn.
Ví dụ trong .Net Core 2.2 thì có chứa thêm Kestrel nhưng từ .Net Core 3.0 trở đi thì lại không có thành phần này, và cơ chế thay đổi đôi chút.
Khi bắt đầu, Kestrel web server nhặt lấy request và tạo một HttpContext và gán nó vào Middleware đầu tiên trong request pipeline. Và cuối cùng, response sẽ đến Kestrel nó sẽ trả response về cho client.
Đi sâu hơn một chút với Asp Net Core thì trong request pipeline sẽ đi qua những lớp cụ thể dưới đây.
Đối với Asp Net Core thì chúng ta cấu hình middleware trong hàm Configuration của lớp Startup bằng cách sử dụng interface IApplicationBuilder. Vậy tại sao nó lại được trigger từ đây. Bạn có thể hình dung Configure là một bể chứa lớn chứa tất cả các middlwares mà chúng được gọi tới application. Và chính vì nó là một bể chứa lớn nên nhiệm vụ chúng ta phải hiểu thứ tự gọi tới và phân loại các request đi vào pipeline này để tránh việc chồng chéo request hay sai logic mà bạn mong muốn. Mình nhắc lại, thứ tự ưu tiên rất rất quan trọng khi làm việc với middleware nhé.
Đến đây thì lại có một lưu ý nữa là thứ tự ưu tiên này, mỗi nền tảng hay mô hình lại có thứ tự khác nhau đôi chút, bạn cần lưu ý xem kỹ tài liệu trước khi implement nhé.
Ví dụ trong Asp Net Core, thứ tự middlewares mà bạn muốn tùy biến sẽ xếp sau những middlewares mặc định sau.
Dựa vào thứ tự này bạn cũng có thể tuy biến kế thừa và mở rộng những middleware có sẵn trước đó như Routing, Cors, Authentication hay lớp cuối cùng chính là Endpoint mà bạn vẫn hay làm khi filter và validation vậy.
Cụ thể hơn, để tùy biến các middleware thì bạn nên nắm một chút về kiến trúc trong Asp Net Core như Run > Use > Map > MapWhen.
Phương thức Run là một phương thức mở rộng của IApplicationBuilder và nó chấp nhận một kiểu RequestDelegate. RequestDelegate là một delegate tham chiếu tới phương thức xử lý yêu cầu. Bạn có thể hiểu đơn giản nó là một dạng ủy quyền xử lý một hành vi nào đó.
Phương thức app.Run để lấy thể hiện của HttpContext. Bạn có thể sử dụng đối tượng Response từ HttpContext để viết thêm các thông tin vào HttpResponse. Với phương thức này sẽ không trigger middleware kế tiếp sau đó nên phương thức Run thêm vào một middleware ngắt hay middleware cuối cùng trong chuối pipeline.
Hiểu được phương thức này bạn có thể dễ dàng viết những hàm delegate để can thiệp và xử lý các hành vi gọi tới Run đầu tiên.
Phương thức Use tương tự như phương thức Run nhưng nó có thêm tham số next để chuyển tới middlewares kế tiếp sau khi nó đã xử lý xong hành vi của mình. Các middaware mặc định có sẵn đều thuộc dạng phương thức này và bắt đầu bằng use như: UseCors, UseAuthentication, UseAuthorization, UseStaticFiles, UseResponseCaching, UseRequestLocalization,…
Phương thức app.Use có nhận hai tham số, một là HttpContext và hai là một RequestDelegate, về cơ bản nó là tham chiếu tới middleware tiếp theo.
Ngoài tùy biến kế thừa những middlware có sẵn bạn cũng có thể tự xây dựng những middleware riêng phục vụ nhu cầu của mình với từ khóa UseMiddleware để đăng ký tùy biến sau khi xây dựng.
Phương thức Map, có một số trường hợp bạn muốn điều hướng request đi theo một hướng đi khác thì bạn có thể dùng Map. Đây cũng là phương thức dùng phổ biến trong Routing. Map cũng chấp nhận Delegate như Run và sau khi điều hướng đón đầu request xong thì nó cũng có thể kết hợp với Run hay Use vì bên trong Map vẫn chứa một IApplicationBuilder giúp bạn tùy biến lồng nhau.
Phương thức MapWhen thì tương tự như Map nhưng có thêm điều kiện giúp bạn filter những request tới thay vì phải đón đầu tất cả các request như Map.
Hiểu được các thành phần và các lớp trong Request Pipeline bạn có thể tự tin viết những Middlewares cho riêng mình.
Về cơ bản các công nghệ khác cũng có những pipeline tương tự, nếu bạn nắm được thứ tự từng thành phần trong công nghệ đó thì bạn sẽ có thể dễ dàng tùy biến theo yêu cầu bài toán.
Tuy nhiên hãy nhớ một điều, sợi dây càng dài thì càng dễ đứt. Không phải cứ gắn càng nhiều middlewares càng tốt đâu nhé. Nó còn ảnh hưởng đến bảo mật và hiệu năng của mỗi request đó. Nên cân nhắc và ràng buộc chặt chẽ khi tự mình tùy biến cũng như sử dụng middleware.
Với bài viết này hi vọng bạn có thể hiểu được hơn về cách thức vận hành của một middleware và cách tùy biến chúng.
Chúc bạn thành công!