Mở rộng express application



  • Ờ thì express đã sang version 4, ờ thì 1 lũ SailJS, Pomelo, TotalJS, Loopback v.v… sẽ làm bạn nhức đầu khi gõ từ khoá “nodejs framework”. Cơ mà, cho dù là cái gì đi nữa, thì căn bản vẫn là căn bản:

    Mô hình chung của một HTTP framework.

    Mình thích gọi là HTTP framework hơn là web framework, vì nếu là web thì có 1 tỷ thứ xung quanh nó. Ở đây, mình chỉ muốn đề cập mô hình đơn giản nhất của một hệ thống – ĐÓN – một request – TRẢ – một response.

    Tất cả chỉ gói gọn trong 1 từ: Phân lớp!

    ---- request  --->|
                      |
                      v   <---Lớp xử lý
                      | 
    <-- response------|
    

    Ví dụ kinh điển luôn nhé:

    var http = require('http');
    
    var handler = function(request, response) {
        response.writeHead(200, {'Content-Type': 'text/plain'});
        response.write('Hello World!');
        response.end();
    };
    
    http.createServer(handler).listen(8000);
    

    Okay! Bây giờ bạn sẽ tự hỏi: Cái kia liên quan gì đến phân lớp?
    Và mình sẽ trả lời:

    ----- request --->|------>--------|
                      |               |
                      | <-- Lớp 1     v <-- Lớp 2
                      |               |
    <---- response ---|-----<-------- |
    

    Bản chất của việc xử lý 1 request và trả lại 1 response luôn tuân theo mô hình này. Một lớp (Layer) là một tầng xử lý của hệ thống.

    Có thể, nó sẽ đón 1 request, tiến hành xử lý, và trả về response (Mô hình đầu tiên).
    Cũng có thể, nó sẽ đón 1 request, đính thêm các thông tin cần thiết vào request đó, gửi tiếp request đó cho tầng tiếp theo xử lý, đón lại response do tầng tiếp theo trả về, và trả lại tiếp request đó. (Mô hình thứ 2).
    Và mô hình tổng quát sẽ là:

    --->--|-->--|-->--|      |-->|
          |     |     |  ... |   |
    --<---|--<--|--<--|      |<--|
    

    Nhờ mô hình này, bạn có thể đính thêm – BAO – NHIÊU – LỚP – XỬ – LÝ – CŨNG – ĐƯỢC!
    Điều này đồng nghĩa với việc ta có thể mở rộng mô hình xử lý HTTP tuỳ ý! Cool!

    Nhưng, để đạt được điều này, bạn bắt buộc phải có:

    Một hệ thống HTTP Foundation (Request, Response) vững chắc – Nôm na ra là định nghĩa request, response đầy đủ và nhất quán xuyên suốt hệ thống
    Một hệ thống HTTP Kernel uyển chuyển – Nôm na ra là chuyển tải được request – response từ tầng này sang tầng khác.
    Và hệ thống HTTP Foundation vững chắc mà NodeJS mang lại chính là module http (như trong code demo). Nếu không có module http, bạn sẽ không có NodeJS và ngược lại! module HTTP là linh hồn và là lý do để chúng ta có NodeJS ngày nay. (Chắc bạn nào đã có kinh nghiệm với Symfony/Laravel/Zend framework của PHP, MVC của ASP.NET hay Rails của Ruby đều không lạ lẫm với mô hình này)

    Nhưng, rất tiếc là NodeJS không có điều kiện thứ 2!

    Nhưng, rất may, là nếu không có, thì ta sẽ tự làm – đó chính là sự ra đời của connect, lõi của expressjs ngày nay.

    Và mỗi lớp xử lý, trong connect/express được gọi là một middleware. Thực chất, express framework gồm 2 thành phần chính:

    Lõi connect
    Tập hợp các middleware đã dựng sẵn.
    Chỉ có vậy thôi :)
    Nào, cùng quay về chủ đề của mình: Mở rộng express application

    Chính là cách để bạn xây dựng một middleware

    INTERFACE CỦA MIDDLEWARE

    function(request, response, next) {
        // Whatever I like 
    }
    

    Rất đơn giản, một middleware của connect/express là một hàm, gồm 3 tham số:

    request – response: Khỏi nói nhé
    next – Là 1 HÀM – (hàm nhé), bạn hãy call nó khi muốn thông báo middleware này đã xử lý xong, middleware tiếp theo hãy tiến hành xử lý.
    Ví dụ:

    var express = require('express');
    var app     = express();
    
    app.use(function(request, response, next) {
        response.write('Hello World ');
        next();
    });
    
    app.use(function(request, response, next) {
        response.write('from Rikky');
        response.send();
    });
    
    app.listen(8000);
    

    Bạn chỉ cần kết hợp các middleware khác nhau, và bạn sẽ có các http server khác nhau, từ RESTful API cho đến những http protocol đặc thù cho dự án của bạn.

    app.use(function(request, response, next){
         var protocol = request.header('My-Protocol');
    
         if ('Rikky-Handsome' == protocol) {
             // Switching protocol
             next();
         } else {
             response.status(400).send('Invalid Protocol').end();
         }
    });
    

    Và sau đây là một số use case rất phù hợp với mô hình middleware.

    Middleware use cases

    Auth

    Nào thì có người thắc mắc là

    sao trong express không có auth module?

    Đơn giản là express KHÔNG được xây dựng với mục đích là một fullstack framework, nó chỉ là hệ thống truyền tải request và response. Còn việc xử lý request, response như nào, nó chỉ cần cắm thêm các middleware có bên thứ 3.
    Express sinh ra không phải để sử dụng ngay! Mà nó sinh ra để có thể mở rộng và tương thích!

    Okay, lại hơi đi xa vấn đề :D

    // Authenticate:

    app.use(function(request, response) {
        // 
        someAuthAdapter
              .login(request.body.username, request.body.password)
              .then(function(userIdentity) {
                  // Process on login OK
                  // Bind user identity as an attribute to the request:
                  request.userIdentity = userIdentity;
                  next();
              }, function() {
                  response.status(401).send('UnAuthenticated').end();
              });
    });
    

    // Authorization:

    app.use(function(request, response) {
         var token = request.header('Authorization');
         if (token) {
              someAuthAdapter.hasPermission(token).then(next, function() {
                  response.status(403).send('UnAuthorized').end();
              });
         } else {
             response.status(403).send('UnAuthorized').end();
         }
    })
    

    Ngày nay đang có rất nhiều auth module được xây dựng, nhưng mình chắc chắn, chúng đều phải tiếp cận theo cách này!
    Nếu một ngày đẹp trời, bạn phải tích hợp một module auth đặc thù, hãy nhớ lấy đây là một option!

    Filtering

    Bộ lọc cũng rất fit với mô hình middleware:

    // IP Filtering:

    app.use(function(request, response, next) {
        //
        var ip = request.connection.remoteAddress;
        if (someBlackList.has(ip)) {
            response.status(400).send('IP Blocked').end();
        } else {
            next();
        }
    });
    

    Request & Response decoration

    app.use(function(request, response, next) {
         response.sayHello = function (name) {
             name = name || 'World',
             response.send('Hello ' + name);
         };
         next();
    });
    
    app.get('/greeting', function(request, response) {
        response.sayHello('Rikky');
    })
    

    Access Logging:

    app.use(function(request, response, next) {
        //
        console.log('A client has connected:   %s /%s', request.method, request.url);
        next();
    });
    

    Runtime Service Locator

    app.use('/foo', function(request, response, next) {
        app.set('myFooService', fooService);
        next();
    });
    
    app.use('/bar', function(request, response, next) {
        app.set('myBarService', barService);
        next();
    });
    

    Cuối cùng, và thú vị nhất: Modular Integration

    Bản thân một express application cũng là 1 middlware:

    var express = require('express');
    
    var apiApp = express(),
        backendApp = express(),
        frontendApp = express(),
    
    var server = express();
    
    // Do something awesome with apps
    
    server.use('/api', apiApp);
    server.use('/dashboard', backendApp);
    server.use('/', frontendApp);
    
    server.listen(8000);
    

    Hãy thử tưởng tượng, bạn có thể nhúng nguyên 1 app thành 1 module trong app của bạn sẽ fun như thế nào :D

    KẾT

    Hãy đừng mơ tưởng đến những điều cao xa, cũng đừng chờ đợi một cao thủ nào chỉ giáo bạn. Hãy tự mình trở thành cao thủ. Mật tịch võ công không ở đâu xa xôi, ở ngay đây :blush:

    mickey_ichi

    1


  • Cám ơn bạn rất nhiều!^^


Log in to reply