Como criar um servidor da Web simples usando apenas instruções nodejs padrão
Geralmente, é necessário um servidor Web simples para desenvolver aplicativos MPA / SPA / PWA. Uma vez, em uma grande manifestação em resposta à pergunta: "O que você estava fazendo?", Eu disse que estava criando um servidor da Web para hospedar um aplicativo PWA. Todos nós rimos por um longo tempo e sim, a propósito, o PWA não é cola. Como o SPA, não é um salão de beleza. Esses são todos os tipos de aplicativos da web. E SSR não é um país :-). Se você iniciar um aplicativo simplesmente abrindo a página inicial do index.html por meio de um navegador, ele não funcionará como deveria; no melhor dos casos, obteremos uma versão offline. Adoro JavaScript e resolverá o problema usando apenas os meios disponíveis, por assim dizer, fora da caixa.
Vamos começar com o plano:
- Se não houver NodeJS, faça o download do LTS, instale, não altere as configurações, clique em Avançar
- Em nosso local isolado, onde todos os projetos são coletados, crie a pasta simple-web-server
- Na pasta do projeto, execute o comando npm init --yes // sem --yes, o inicializador fará muitas perguntas
- No arquivo package.json na seção de scripts , adicione a propriedade e seu valor - "main": "index.js" - para que possamos iniciar rapidamente nosso servidor usando o comando npm run
- Criar uma pasta lib É recomendável colocar todo o seu código nele, o que não requer montagem e etapas adicionais para sua operação
- Crie um arquivo index.js na pasta lib.Este é o nosso futuro servidor.
- Criar uma pasta dist - esta será a pasta na qual haverá arquivos acessíveis ao público, incluindo index.html, ou seja, a estática que o servidor distribuirá
- Abra o arquivo /index.js
- Escreva algum código
Então, o que sabemos sobre o que nosso servidor deve fazer?
- Manipular solicitações
- Ler arquivos
- Responder à solicitação com o conteúdo do arquivo
Primeiro, crie nosso servidor, importe-o para o arquivo idex.js
const {createServer} = require('http');
Esta instrução destrói o objeto do módulo http e atribui uma expressão, a função createServer , ao identificador de variável createServer .
Crie um novo servidor usando a seguinte instrução
const server = createServer();
Quando você primeiro acessa o host do servidor, o navegador envia uma solicitação para receber o documento. Portanto, para processar este evento, precisamos ouvir essas solicitações. O servidor que criamos tem um método listen , como parâmetro, passamos o número da porta 3000.
const eventsEmitter = server.listen(3000);
A expressão desse método será um objeto EventEmitter que será armazenado em uma variável com o identificador eventsEmitter . Este objeto é observável. Assinamos seus eventos usando a chamada do método on / addEventListener com dois parâmetros de função de string necessários. O primeiro parâmetro indica quais eventos nos interessam; solicitar o segundo é uma função que processará esse evento.
eventsEmitter.on('request', (req, res) => { debugger; });
Abra o link no navegador

Então, decidimos pelas instruções do depurador. Vemos que, como parâmetros, obtemos dois objetos req , res.Estes objetos são instâncias de objetos do tipo fluxo, portanto , req é o fluxo de leitura e res é o fluxo de gravação.
Processamos a solicitação e podemos dizer que "o problema está no chapéu". Tudo o que resta é ler e devolver o arquivo. Primeiro, você precisa entender que tipo de arquivo precisamos. No depurador, tendo estudado todas as propriedades do parâmetro req , vi que ele possui a propriedade url . Mas apenas não há nada como index.html nele .
Vamos olhar novamente para o nosso navegador: vemos que não indicamos isso explicitamente. Vamos tentar novamente, mas já especificaremos explicitamente index.html .

Agora está claro que o nome do arquivo vem na solicitação na propriedade url e nada além disso, não precisamos ler o arquivo da solicitação. Nós a decompomos usando um par de chaves, especificamos o nome da propriedade url e, através do operador :, definimos um nome arbitrário usando um identificador de variável válido, no meu caso requestUrl .
eventsEmitter.addListener('request', ({url: requestUrl}, res) => { debugger });
Ótimo, o que vem a seguir? Na verdade, eu realmente não gosto do fato de que index.html sempre precisará ser especificado explicitamente, então vamos resolver esse problema imediatamente. Decidi que a maneira mais fácil de fazer isso é usar a função extname padrão ; ela está incluída no pacote padrão
NodeJS do módulo path , nós o importamos usando a seguinte instrução.
const {extname} = require('path');
Agora você pode chamá-lo passando a expressão identificadora requestUrl como um parâmetro e obter a expressão para a sequência do formato aproximado '.extension'
. Se a solicitação não especificar explicitamente um arquivo, uma string vazia será retornada. Usando esse princípio, adicionaremos o valor padrão de 'index.html' . Escrevemos a seguinte instrução
const url = extname(requestUrl) === '' ? DEFAULT_FILE_NAME : requestUrl;
Estou certo de que o usuário do servidor desejará substituir esse nome e também configurar uma variável de ambiente usando a seguinte instrução
const {env: {DEFAULT_FILE_NAME = '/index.html'}} = process;`
No processo de variável global , há muitas informações úteis, apenas enterrarei parte delas, em particular, a propriedade env que contém todas as propriedades do ambiente do usuário e já procuraremos por DEFAULT_FILE_NAME se o usuário não especificar - usamos index.html por padrão.
IMPORTANTE: se o valor da propriedade de ambiente DEFAULT_FILE_NAME não for definido, a atribuição de um valor padrão não funcionará. Vale lembrar, mas agora não, estamos fazendo tudo ao mínimo :-)

Agora que temos um link relativo para o arquivo, precisamos obter o caminho absoluto para o arquivo no sistema de arquivos do nosso servidor. Decidimos que todos os arquivos públicos serão armazenados na pasta dist . Portanto, para obter o caminho absoluto para o arquivo , usaremos outra função de resolução do módulo que já conhecemos
caminho simplesmente indique-o na instrução criada anteriormente na linha 5
const {resolve, extname} = require('path');
Em seguida, na linha 10, escrevemos uma instrução que receberá e salvará o caminho absoluto da variável filePath.Eu também disse que o nome dessa pasta pode ser redefinido para maior flexibilidade. Portanto, expanda a instrução na linha 6, adicionando o nome
variável de ambiente DIST_FOLDER !

Agora tudo está pronto para ler o arquivo. Você pode ler um arquivo de maneiras diferentes de maneira assíncrona, síncrona ou usar fluxos . Vou usar fluxos :-) é bonito e mais eficaz, do ponto de vista dos recursos gastos. Primeiro, crie um arquivo de teste na pasta dist para que haja algo para ler :-)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> TESTING 1,2,3... </body> </html>
Agora precisamos de uma função que crie um fluxo de leitura de arquivo, que também esteja incluído na distribuição padrão do NodeJS e que a extraia do módulo fs usando a seguinte instrução
const {createReadStream} = require('fs');
e na linha 12 no corpo do processador de solicitações, usamos a seguinte declaração
createReadStream(filePath)
como resultado, a instância do objeto de fluxo de leitura será retornada usando-o
podemos mudar os fluxos de leitura para o fluxo de gravação, também podemos transformar os fluxos e muitas outras coisas úteis. Então o parâmetro res é um fluxo de leitura, certo?
Vamos tentar mudar imediatamente o fluxo de leitura de arquivo que criamos para o fluxo de gravação res para
disso, na linha 12, continuamos a instrução chamando o método pipe e, como parâmetro, passamos nosso res de fluxo de gravação
createReadStream(filePath).pipe(res);

Isso é tudo? Nããão. E quem irá lidar com os erros? Que tipo de erros? Vamos tentar fazer upload do arquivo css para o arquivo index.html, mas não o criaremos e veremos o que acontece :-)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="index1.css"> </head> <body> TESTING 1,2,3... </body> </html>

Esperado, mas o servidor travou! Este não é o caso. O fato é que, por padrão, os erros não são capturados nos fluxos e você precisa fazer isso você mesmo :-) createReadStream retorna o fluxo no qual o erro ocorre. Portanto, adicionamos um manipulador de erros. Utilizando a Chamada para o Método On . Especificando o Nome do Evento de Erro e o Manipulador de uma Função. Ele encerrará o fluxo de leitura e leitura com um código de resposta 404.
createReadStream(filePath) .on('error', error => res.writeHead(404).end()) .pipe(res);
verifique!

Outra coisa. A propósito, o servidor ainda não está pronto e, se tentarmos abri-lo em outro navegador, a página não funcionará corretamente :-) quem a adivinhou, escreva nos comentários, o que esquecemos de fazer? O fato é que, quando um servidor responde à solicitação de um servidor com um arquivo, uma extensão não é suficiente para o navegador entender de que tipo esse arquivo e outros navegadores são: nem o Chrome nem as versões mais antigas funcionarão com arquivos baixados sem especificar o cabeçalho de resposta Tipo de Conteúdo. Para processar o arquivo, entre outras coisas, nosso servidor deve especificar o tipo MIME. Para isso, criaremos uma variável separada com todos os tipos comuns de maio. Também oferecemos a oportunidade de expandi-los passando como uma variável de ambiente
const {env: {DEFAULT_FILE_NAME = '/index.html', DIST_FOLDER = 'dist', DEFAULT_MIME_TYPES = '{}'}} = process; const {text} = mimeTypes = { 'html': 'text/html', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'png': 'image/png', 'js': 'text/javascript', 'css': 'text/css', 'text': 'plain/text', 'json': 'application/json', ...JSON.parse(DEFAULT_MIME_TYPES) };
Bem, agora você precisa especificar de alguma forma o tipo MIME antes de alternar o fluxo de leitura para o fluxo de gravação. Eu usei os nomes de extensão de arquivo como chaves, então obteremos a extensão com a familiar função extname
const fileExtension = extname(url).split('.').pop();
e com a ajuda do manipulador de eventos de pipe , definimos o tipo MIME desejado
res.on('pipe', () => res.setHeader(contentType, mimeTypes[fileExtension] || text));
Verifique

Isso é tudo - o servidor está pronto. É claro que não é perfeito, mas para começar rapidamente, é isso. Se você estiver interessado em desenvolver essa ideia, escreva nos comentários :-)
Código completo do projeto