Enviando Arquivos em Node.js
O envio de arquivos estáticos é uma tarefa bastante comum para 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
:
De forma semelhante, podemos acessar o endereço http://localhost:3000/about.html
:
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
:
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.