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:
- Crie um novo aplicativo expresso.
- Crie uma nova rota.
- Iniciando o servidor HTTP no número da porta especificado.
- 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
:
- Uma rota é criada no roteador do aplicativo (
this._router
). - 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. - 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 rotasOs 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 expressoSumá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?
