Servidor de arquivo estático básico em NodeJS


85

Estou tentando criar um servidor de arquivos estático em nodejs mais como um exercício para entender o node do que como um servidor perfeito. Estou bem ciente de projetos como Connect e node-static e pretendo usar essas bibliotecas para obter mais código pronto para produção, mas também gosto de entender o básico com o que estou trabalhando. Com isso em mente, codifiquei um pequeno server.js:

var http = require('http'),
    url = require('url'),
    path = require('path'),
    fs = require('fs');
var mimeTypes = {
    "html": "text/html",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "png": "image/png",
    "js": "text/javascript",
    "css": "text/css"};

http.createServer(function(req, res) {
    var uri = url.parse(req.url).pathname;
    var filename = path.join(process.cwd(), uri);
    path.exists(filename, function(exists) {
        if(!exists) {
            console.log("not exists: " + filename);
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.write('404 Not Found\n');
            res.end();
        }
        var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
        res.writeHead(200, mimeType);

        var fileStream = fs.createReadStream(filename);
        fileStream.pipe(res);

    }); //end path.exists
}).listen(1337);

Minha pergunta é dupla

  1. É esta a maneira "certa" de criar e transmitir html básico etc no node ou existe um método melhor / mais elegante / mais robusto?

  2. O .pipe () no nó basicamente está fazendo o seguinte?

.

var fileStream = fs.createReadStream(filename);
fileStream.on('data', function (data) {
    res.write(data);
});
fileStream.on('end', function() {
    res.end();
});

Obrigado a todos!


2
Escrevi um módulo que permite fazer isso sem comprometer a flexibilidade. Ele também armazena em cache automaticamente todos os seus recursos. Confira: github.com/topcloud/cachemere
Jon

2
Um pouco engraçado que você escolheu (?) Para retornar '404 Not Found' com o código de status HTTP '200 OK'. Se não houver recurso a ser encontrado na URL, o código apropriado deve ser 404 (e o que você escreve no corpo do documento é geralmente de importância secundária). Caso contrário, você confundirá muitos agentes de usuário (incluindo rastreadores da web e outros bots), fornecendo-lhes documentos sem valor real (que também podem armazenar em cache).
amn

1
Obrigado. Ainda trabalhando bem muitos anos depois.
statosdotcom

1
Obrigado! este código está funcionando perfeitamente. Mas agora use em fs.exists()vez do path.exists()código acima. Felicidades! e sim! não se esqueça return:
Kaushal28

NOTA : 1) fs.exists() está obsoleto . Use fs.access()ou ainda melhor como no caso de uso acima fs.stat(),. 2) url.parse está obsoleto ; use a new URLinterface mais recente .
rags2riches

Respostas:


44
  • Seu servidor básico parece bom, exceto:

    Falta uma returndeclaração.

    res.write('404 Not Found\n');
    res.end();
    return; // <- Don't forget to return here !!
    

    E:

    res.writeHead(200, mimeType);

    deveria estar:

    res.writeHead(200, {'Content-Type':mimeType});

  • Sim pipe(), basicamente faz isso, ele também pausa / retoma o fluxo de origem (no caso do receptor ser mais lento). Aqui está o código-fonte da pipe()função: https://github.com/joyent/node/blob/master/lib/stream.js


2
o que acontecerá se o nome do arquivo for como blah.blah.css?
ShrekOverflow

2
mimeType deve ser blá nesse caso xP
ShrekOverflow

5
Não é esse o problema? se você escrever o seu próprio, estará solicitando esses tipos de bugs. Bom exercício de aprendizagem, mas estou aprendendo a apreciar "conectar" em vez de rolar por conta própria. O problema com esta página é que as pessoas estão procurando apenas descobrir como fazer um servidor de arquivos simples e o estouro de pilha surge primeiro. Essa resposta está certa, mas as pessoas não estão procurando por ela, apenas uma resposta simples. Eu tive que descobrir o mais simples sozinho, então coloque-o aqui.
Jason Sebring

1
+1 por não colar um link para uma solução na forma de uma biblioteca, mas realmente escrever uma resposta à pergunta.
Shawn Whinnery

56

Menos é mais

Basta ir ao prompt de comando primeiro em seu projeto e usar

$ npm install express

Em seguida, escreva o código app.js assim:

var express = require('express'),
app = express(),
port = process.env.PORT || 4000;

app.use(express.static(__dirname + '/public'));
app.listen(port);

Em seguida, você criaria uma pasta "pública" onde colocaria seus arquivos. Eu tentei da maneira mais difícil primeiro, mas você precisa se preocupar com os tipos MIME, que é apenas mapear coisas que consomem muito tempo e depois se preocupar com os tipos de resposta, etc. etc. etc ... não, obrigado.


2
+1 Há muito a ser dito sobre o uso de código testado em vez de lançar o seu próprio.
jcollum de

1
Eu tentei olhar a documentação, mas não consigo encontrar muito, você pode explicar o que seu snippet está fazendo? Tentei usar esta variação particular e não sei o que pode ser substituído pelo quê.
onaclov2000

3
Se você quiser a lista de diretórios, simplesmente adicione .use (connect.directory ('public')) logo após a linha connect.static, substituindo public, pelo seu caminho. Desculpe pelo sequestro, mas acho que isso esclarece as coisas para mim.
onaclov2000

1
Você também pode 'Usar jQuery'! Esta não é uma resposta à pergunta do OP, mas uma solução para um problema que nem existe. OP afirmou que o objetivo deste experimento era aprender Node.
Shawn Whinnery

1
@JasonSebring Por que require('http')na segunda linha?
Peng em ZenUML.com

20

Também gosto de entender o que está acontecendo nos bastidores.

Percebi algumas coisas em seu código que você provavelmente deseja limpar:

  • Ele trava quando o nome do arquivo aponta para um diretório, porque existe é verdadeiro e tenta ler um fluxo de arquivo. Usei fs.lstatSync para determinar a existência do diretório.

  • Ele não está usando os códigos de resposta HTTP corretamente (200, 404 etc.)

  • Enquanto MimeType está sendo determinado (da extensão do arquivo), ele não está sendo definido corretamente em res.writeHead (como stewe apontou)

  • Para lidar com caracteres especiais, você provavelmente deseja desfazer o escape do uri

  • Segue cegamente links simbólicos (pode ser uma preocupação de segurança)

Diante disso, algumas das opções do apache (FollowSymLinks, ShowIndexes, etc) começam a fazer mais sentido. Atualizei o código do seu servidor de arquivos simples da seguinte maneira:

var http = require('http'),
    url = require('url'),
    path = require('path'),
    fs = require('fs');
var mimeTypes = {
    "html": "text/html",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "png": "image/png",
    "js": "text/javascript",
    "css": "text/css"};

http.createServer(function(req, res) {
  var uri = url.parse(req.url).pathname;
  var filename = path.join(process.cwd(), unescape(uri));
  var stats;

  try {
    stats = fs.lstatSync(filename); // throws if path doesn't exist
  } catch (e) {
    res.writeHead(404, {'Content-Type': 'text/plain'});
    res.write('404 Not Found\n');
    res.end();
    return;
  }


  if (stats.isFile()) {
    // path exists, is a file
    var mimeType = mimeTypes[path.extname(filename).split(".").reverse()[0]];
    res.writeHead(200, {'Content-Type': mimeType} );

    var fileStream = fs.createReadStream(filename);
    fileStream.pipe(res);
  } else if (stats.isDirectory()) {
    // path exists, is a directory
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('Index of '+uri+'\n');
    res.write('TODO, show index?\n');
    res.end();
  } else {
    // Symbolic link, other?
    // TODO: follow symlinks?  security?
    res.writeHead(500, {'Content-Type': 'text/plain'});
    res.write('500 Internal server error\n');
    res.end();
  }

}).listen(1337);

4
posso sugerir "var mimeType = mimeTypes [path.extname (filename) .split (". "). reverse () [0]];" em vez de? alguns nomes de arquivo têm mais de um "." por exemplo, "my.cool.video.mp4" ou "download.tar.gz"
não sincronizado de

Isso, de alguma forma, impede alguém de usar um url como a pasta /../../../ home / user / jackpot.privatekey? Eu vejo a junção para garantir que o caminho seja downstream, mas estou me perguntando se usar o tipo de notação ../../../ vai contornar isso ou não. Talvez eu mesmo teste.
Reynard

Não funciona. Não sei por que, mas é bom saber.
Reynard

bom, uma correspondência RegEx também pode coletar a extensão; var mimeType = mimeTypes[path.extname(filename).match(/\.([^\.]+)$/)[1]];
John Mutuma

4
var http = require('http')
var fs = require('fs')

var server = http.createServer(function (req, res) {
  res.writeHead(200, { 'content-type': 'text/plain' })

  fs.createReadStream(process.argv[3]).pipe(res)
})

server.listen(Number(process.argv[2]))

4
Pode querer explicar isso um pouco mais.
Nathan Tuggy de

3

Que tal este padrão, que evita verificar separadamente se o arquivo existe

        var fileStream = fs.createReadStream(filename);
        fileStream.on('error', function (error) {
            response.writeHead(404, { "Content-Type": "text/plain"});
            response.end("file not found");
        });
        fileStream.on('open', function() {
            var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
            response.writeHead(200, {'Content-Type': mimeType});
        });
        fileStream.on('end', function() {
            console.log('sent file ' + filename);
        });
        fileStream.pipe(response);

1
você esqueceu o tipo MIME em caso de sucesso. Estou usando este design, mas em vez de canalizar imediatamente os streams, estou canalizando-os no evento 'open' do filestream: writeHead para o tipo MIME e, em seguida, cano. O fim não é necessário: readable.pipe .
GeH de

Modificado de acordo com o comentário de @GeH.
Brett Zamir

Deveria serfileStream.on('open', ...
Petah


0

o módulo st facilita o serviço de arquivos estáticos. Aqui está um extrato de README.md:

var mount = st({ path: __dirname + '/static', url: '/static' })
http.createServer(function(req, res) {
  var stHandled = mount(req, res);
  if (stHandled)
    return
  else
    res.end('this is not a static file')
}).listen(1338)

0

A resposta de @JasonSebring me indicou a direção certa, porém seu código está desatualizado. Aqui está como você faz isso com a connectversão mais recente.

var connect = require('connect'),
    serveStatic = require('serve-static'),
    serveIndex = require('serve-index');

var app = connect()
    .use(serveStatic('public'))
    .use(serveIndex('public', {'icons': true, 'view': 'details'}))
    .listen(3000);

No connect Repositório GitHub, existem outros middlewares que você pode usar.


Usei apenas expresso para uma resposta mais simples. A versão expressa mais recente tem a estática incorporada, mas não muito mais. Obrigado!
Jason Sebring

Olhando para a connectdocumentação, é apenas um wrapperpara middleware. Todos os outros itens interessantes middlewaresão do expressrepositório, portanto, tecnicamente, você pode usar essas APIs usando o express.use().
ffleandro
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.