Atualizado: 02/01/2025

Enviando Arquivos - Node.js

O envio de arquivos estáticos é uma tarefa bastante comum na construção e operação de uma aplicação web. Vamos analisar como enviar arquivos em uma aplicação Node.js.

Suponha que temos três arquivos no diretório do projeto:

  • app.js

  • about.html

  • index.html

O arquivo app.js é o arquivo principal da aplicação. No arquivo index.html, definimos o seguinte código:

<!DOCTYPE html>
<html>
<head>
    <title>Principal</title>
    <meta charset="utf-8" />
</head>
<body>
    <h1>Principal</h1>
</body>
<html>

De forma semelhante, definimos o código no arquivo about.html:

<!DOCTYPE html>
<html>
<head>
    <title>Sobre</title>
    <meta charset="utf-8" />
</head>
<body>
    <h1>Sobre</h1>
</body>
<html>

Nossa tarefa será enviar o conteúdo desses arquivos para o usuário.

Primeiro Método

Para ler um arquivo, podemos usar o método fs.createReadStream(), que lê o arquivo em um fluxo. Em seguida, com o método pipe(), podemos ligar os arquivos lidos ao fluxo de gravação, ou seja, ao objeto response. Então, colocamos no arquivo app.js o seguinte código:

const http = require("http");
const fs = require("fs");

http.createServer(function(request, response) {
    console.log(`Endereço solicitado: ${request.url}`);
    // obtém o caminho após a barra
    const filePath = request.url.substring(1);
    // verifica se o arquivo existe
    fs.access(filePath, fs.constants.R_OK, err => {
        // se ocorrer um erro, envia o código de status 404
        if(err) {
            response.statusCode = 404;
            response.end("Recurso não encontrado!");
        } else {
            fs.createReadStream(filePath).pipe(response);
        }
    });
}).listen(3000, function() {
    console.log("Servidor iniciado na porta 3000");
});

Primeiramente, obtemos o endereço solicitado. Suponha que o endereço solicitado corresponda diretamente ao caminho do arquivo no servidor. Em seguida, com a função assíncrona fs.access, verificamos a disponibilidade do arquivo para leitura. O primeiro parâmetro da função é o caminho do arquivo. O segundo parâmetro é uma opção que define o tipo de verificação de acesso. Nesse caso, o valor fs.constants.R_OK indica que estamos verificando os direitos de leitura do arquivo. O terceiro parâmetro é uma função de callback que recebe um objeto de erro. Se ocorrer um erro (o arquivo não está disponível para leitura ou não foi encontrado), enviamos o código de status 404.

Para enviar o arquivo, aplicamos a cadeia de métodos:

fs.createReadStream(filePath).pipe(response);

O método fs.createReadStream(filePath) cria um fluxo de leitura, um objeto fs.ReadStream. Para obter os dados do fluxo, chamamos o método pipe(), que recebe um objeto da interface stream.Writable ou um fluxo de gravação. Esse é exatamente o caso do objeto http.ServerResponse, que implementa essa interface.

Vamos iniciar a aplicação e acessar no navegador o endereço http://localhost:3000/index.html:

index.html em Node.js

De forma semelhante, podemos acessar o endereço http://localhost:3000/about.html:

about.html em Node.js

Nesse caso, enviamos arquivos HTML, mas de maneira similar podemos enviar diferentes tipos de arquivos. Por exemplo, definimos no projeto a pasta public e nela criamos um novo arquivo styles.css com o seguinte conteúdo:

body { 
    font-family: Verdana; 
    color: navy;
}

Aplicamos esses estilos na página index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Principal</title>
    <meta charset="utf-8" />
    <link href="public/styles.css" rel="stylesheet" type="text/css">
</head>
<body>
    <h1>Principal</h1>
</body>
<html>

E então acessamos index.html:

Arquivos estáticos em Node.js

Segundo Método

O segundo método envolve a leitura dos dados usando a função fs.readFile() e o envio com o método response.end():

const http = require("http");
const fs = require("fs");

http.createServer(function(request, response) {
    console.log(`Endereço solicitado: ${request.url}`);
    // obtém o caminho após a barra
    const filePath = request.url.substring(1);
    fs.readFile(filePath, function(error, data) {
        if(error) {
            response.statusCode = 404;
            response.end("Recurso não encontrado!");
        } else {
            response.end(data);
        }
    });
}).listen(3000, function(){
    console.log("Servidor iniciado na porta 3000");
});

O resultado será similar, porém existem algumas diferenças:

fs.createReadStream

  • Fluxo de Dados (Streaming): fs.createReadStream cria um fluxo de leitura para o arquivo, permitindo que ele seja lido em partes (chunks) e transmitido diretamente para o cliente. Isso é útil para arquivos grandes, pois não há necessidade de carregar o arquivo inteiro na memória antes de enviá-lo.

  • Uso de Memória: Como os dados são lidos e enviados em partes, o uso de memória é mais eficiente, o que é ideal para servidores que lidam com muitos usuários ou arquivos grandes.

  • Desempenho: A leitura em fluxo pode ser mais rápida para grandes arquivos, pois começa a enviar os dados imediatamente, sem esperar que todo o arquivo seja lido.

fs.readFile

  • Leitura Completa: fs.readFile lê o arquivo inteiro na memória antes de enviar os dados para o cliente. Isso pode ser mais simples de implementar para arquivos pequenos, mas se torna ineficiente para arquivos grandes.

  • Uso de Memória: Requer que o arquivo inteiro seja carregado na memória. Para arquivos grandes ou em sistemas com recursos limitados, isso pode levar a problemas de memória.

  • Desempenho: Pode ser mais lento para arquivos grandes, pois precisa ler todo o arquivo antes de começar a enviá-lo. No entanto, para arquivos pequenos, a diferença de desempenho pode ser insignificante.

Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com