Recursos do trabalho e do dispositivo interno express.js

Se você estava desenvolvendo para a plataforma node.js, provavelmente já ouviu falar sobre o express.js . Essa é uma das estruturas leves mais populares usadas para criar aplicativos da web para o nó.



O autor do material, cuja tradução publicamos hoje, se propõe a estudar os recursos da estrutura interna da estrutura expressa através da análise de seu código-fonte e da consideração de um exemplo de seu uso. Ele acredita que o estudo dos mecanismos subjacentes às bibliotecas populares de código aberto contribui para uma compreensão mais profunda deles, remove a cortina do “mistério” deles e ajuda a criar aplicativos melhores baseados neles.

Você pode achar conveniente manter o código-fonte expresso à mão durante a leitura deste material. Esta versão é usada aqui. Você pode ler este artigo sem abrir o código expresso, pois aqui, sempre que apropriado, são fornecidos fragmentos de código dessa biblioteca. Nos locais em que o código é abreviado, comentários do formulário // ...

Um exemplo básico de uso expresso


Para começar, vamos dar uma olhada no "Hello World!" Tradicional no desenvolvimento de novas tecnologias de computador - um exemplo. Ele pode ser encontrado no site oficial da estrutura, servirá como ponto de partida em nossa pesquisa.

 const express = require('express') const app = express() app.get('/', (req, res) => res.send('Hello World!')) app.listen(3000, () => console.log('Example app listening on port 3000!')) 

Esse código inicia um novo servidor HTTP na porta 3000 e envia um Hello World! às solicitações recebidas na rota GET / . Se você não entrar em detalhes, podemos distinguir quatro estágios do que está acontecendo, que podemos analisar:

  1. Crie um novo aplicativo expresso.
  2. Crie uma nova rota.
  3. Iniciando o servidor HTTP no número da porta especificado.
  4. Processando solicitações de entrada para o servidor.

Criando um novo aplicativo expresso


O comando var app = express() permite criar um novo aplicativo expresso. A função createApplication do arquivo lib / express.js é a função exportada padrão; somos nós que a acessamos chamando a função express() . Aqui estão algumas coisas importantes que você deve prestar atenção:

 // ... var mixin = require('merge-descriptors'); var proto = require('./application'); // ... function createApplication() { //    ,     . //     : `function(req, res, next)` var app = function(req, res, next) {   app.handle(req, res, next); }; // ... //  `mixin`    `proto`  `app` //     -  `get`,     . mixin(app, proto, false); // ... return app; } 

O objeto de app retornado dessa função é um dos objetos usados ​​em nosso código de aplicativo. O método app.get adicionado usando a função mixin da biblioteca merge-descriptors , responsável por atribuir os métodos de app declarados em proto . O próprio objeto proto é importado da lib / application.js .

Crie uma nova rota


Agora, vamos dar uma olhada no código responsável pela criação do método app.get partir do nosso exemplo.

 var slice = Array.prototype.slice; // ... /** *   `.VERB(...)` `router.VERB(...)`. */ // `methods`    HTTP, (  ['get','post',...]) methods.forEach(function(method){ //    app.get app[method] = function(path){   //     //          var route = this._router.route(path);   //        route[method].apply(route, slice.call(arguments, 1));   //   `app`,          return this; }; }); 

É interessante notar que, além dos recursos semânticos, todos os métodos que implementam ações HTTP, como app.get , app.post , app.put e similares, em termos de funcionalidade, podem ser considerados os mesmos. Se você simplificar o código acima, reduzindo-o à implementação de apenas um método get , obtém algo como o seguinte:

 app.get = function(path, handler){ // ... var route = this._router.route(path); route.get(handler) return this } 

Embora a função acima tenha 2 argumentos, ela é semelhante à função app[method] = function(path){...} . O segundo argumento, handler , é obtido chamando slice.call(arguments, 1) .

Em poucas palavras, o app.<method> salva a rota no roteador do aplicativo usando seu método de route e passa o handler para a route.<method> .

O método do roteador route() é declarado em lib / router / index.js :

 // proto -     `_router` proto.route = function route(path) { var route = new Route(path); var layer = new Layer(path, {   sensitive: this.caseSensitive,   strict: this.strict,   end: true }, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; }; 

Sem surpresa, a declaração do método route.get em lib / router / route.js é semelhante à declaração de app.get :

 methods.forEach(function (method) { Route.prototype[method] = function () {   // `flatten`   ,  [1,[2,3]],      var handles = flatten(slice.call(arguments));   for (var i = 0; i < handles.length; i++) {     var handle = handles[i];         // ...     //   ,  ,    Layer,     //            var layer = Layer('/', {}, handle);     // ...     this.stack.push(layer);   }   return this; }; }); 

Cada rota pode ter vários manipuladores; com base em cada manipulador, Layer construída uma variável do tipo Layer , que é uma camada de processamento de dados, que entra na pilha.

Objetos de camada


_router e route usam objetos do tipo Layer . Para entender a essência de um objeto, vejamos seu construtor :

 function Layer(path, options, fn) { // ... this.handle = fn; this.regexp = pathRegexp(path, this.keys = [], opts); // ... } 

Ao criar objetos do tipo Layer eles recebem um caminho, determinados parâmetros e uma função. No caso do nosso roteador, essa função é route.dispatch (falaremos mais sobre isso abaixo, em termos gerais, ele foi projetado para transmitir uma solicitação a uma rota separada). No caso da própria rota, essa função é uma função de manipulador declarada no código do nosso exemplo.

Cada objeto do tipo Layer possui um método handle_request , responsável pela execução da função passada quando o objeto foi inicializado.

Lembre-se do que acontece ao criar uma rota usando o método app.get :

  1. Uma rota é criada no roteador do aplicativo ( this._router ).
  2. O método de rota de dispatch é atribuído como o método manipulador do objeto Layer correspondente, e esse objeto é enviado para a pilha do roteador.
  3. O manipulador de solicitações é passado para o objeto Layer como um método manipulador, e esse objeto é enviado para a pilha de rotas.

Como resultado, todos os manipuladores são armazenados dentro da instância do app na forma de objetos do tipo Layer que estão dentro da pilha de rotas, cujos métodos de dispatch são atribuídos aos objetos Layer que estão na pilha do roteador:


Objetos de camada na pilha do roteador e na pilha de rotas

Os pedidos HTTP recebidos são processados ​​de acordo com esta lógica. Falaremos sobre eles abaixo.

Inicialização do servidor HTTP


Depois de configurar as rotas, você precisa iniciar o servidor. Em nosso exemplo, recorremos ao método app.listen , passando o número da porta e a função de retorno de chamada como argumentos. Para entender os recursos desse método, podemos nos referir ao arquivo lib / application.js :

 app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); }; 

app.listen ser apenas um invólucro do http.createServer . Esse ponto de vista faz sentido, pois se você se lembra do que falamos desde o início, o app é apenas uma função com uma function(req, res, next) {...} assinatura function(req, res, next) {...} , compatível com os argumentos necessários para http.createServer (a assinatura deste método é function (req, res) {...} ).

Depois de perceber que, no final, tudo o que express.js nos dá pode ser reduzido a um manipulador de funções muito inteligente, a estrutura não parece mais tão complicada e misteriosa quanto antes.

Processamento de solicitação HTTP


Agora que sabemos que o app é apenas um manipulador de solicitações, seguiremos o caminho que uma solicitação HTTP passa dentro de um aplicativo expresso. Esse caminho leva ao manipulador declarado por nós.

Primeiro, a solicitação vai para a função createApplication ( lib / express.js ):

 var app = function(req, res, next) {   app.handle(req, res, next); }; 

Em seguida, ele segue para o método app.handle ( lib / application.js ):

 app.handle = function handle(req, res, callback) { // `this._router` -  ,    ,  `app.get` var router = this._router; // ... //     `handle` router.handle(req, res, done); }; 

O método router.handle declarado em lib / router / index.js :

 proto.handle = function handle(req, res, out) { var self = this; //... // self.stack -  ,      // Layer (  ) var stack = self.stack; // ... next(); function next(err) {   // ...   //        var path = getPathname(req);   // ...   var layer;   var match;   var route;   while (match !== true && idx < stack.length) {     layer = stack[idx++];     match = matchLayer(layer, path);     route = layer.route;     // ...     if (match !== true) {       continue;     }     // ...      HTTP,       }  // ...      // process_params    ,          self.process_params(layer, paramcalled, req, res, function (err) {     // ...     if (route) {       //       `layer.handle_request`       //         `next`       //  ,   `next`     ,              //  ,   `next`   ,            return layer.handle_request(req, res, next);     }     // ...   }); } }; 

Se você descrever o que está acontecendo em poucas palavras, a função router.handle por todas as camadas da pilha até encontrar a que corresponde ao caminho especificado na solicitação. Em seguida, o método da camada handle_request será chamado, o que executará a função manipuladora predefinida. Essa função do manipulador é um método de rota de dispatch , declarado em lib / route / route.js :

 Route.prototype.dispatch = function dispatch(req, res, done) { var stack = this.stack; // ... next(); function next(err) {   // ...   var layer = stack[idx++];   // ...    layer.handle_request(req, res, next);   // ... } }; 

Assim como no roteador, durante o processamento de cada rota, as camadas que essa rota possui são enumeradas e seus métodos handle_request que executam os métodos do manipulador de camada são handle_request . No nosso caso, este é um manipulador de solicitações, declarado no código do aplicativo.

Aqui, finalmente, a solicitação HTTP cai na área de código do nosso aplicativo.


Caminho de solicitação no aplicativo expresso

Sumário


Aqui examinamos apenas os mecanismos básicos da biblioteca express.js, os responsáveis ​​pela operação do servidor web, mas essa biblioteca também possui muitos outros recursos. Não paramos nas verificações pelas quais as solicitações passam antes que eles atinjam os manipuladores; não falamos sobre os métodos auxiliares disponíveis ao trabalhar com as variáveis res e req . E, finalmente, não tocamos em um dos recursos mais poderosos do Express. Consiste no uso de middleware, que pode ter como objetivo solucionar quase qualquer problema - desde a análise de solicitações até a implementação de um sistema de autenticação completo.

Esperamos que este material o tenha ajudado a entender os principais recursos do dispositivo expresso e, agora, se necessário, você possa entender tudo o mais analisando independentemente as partes do código fonte desta biblioteca que lhe interessam.

Caros leitores! Você usa express.js?

Source: https://habr.com/ru/post/pt414079/


All Articles