Como servir uma imagem usando nodejs


147

Eu tenho um logotipo que reside no public / images / logo.gif. Aqui está o meu código nodejs.

http.createServer(function(req, res){
  res.writeHead(200, {'Content-Type': 'text/plain' });
  res.end('Hello World \n');
}).listen(8080, '127.0.0.1');

Funciona, mas quando solicito localhost: 8080 / logo.gif, obviamente não recebo o logotipo.

Que mudanças eu preciso fazer para exibir uma imagem.

Respostas:


224

Atualização de 2016

Exemplos com Express e sem Express que realmente funcionam

Esta pergunta tem mais de 5 anos, mas todas as respostas têm alguns problemas .

TL; DR

Role para baixo para obter exemplos de imagem com:

  1. express.static
  2. express
  3. connect
  4. http
  5. net

Todos os exemplos também estão no GitHub: https://github.com/rsp/node-static-http-servers

Os resultados dos testes estão disponíveis no Travis: https://travis-ci.org/rsp/node-static-http-servers

Introdução

Depois de mais de 5 anos desde que essa pergunta foi feita, existe apenas uma resposta correta pelo generalhenry, mas mesmo que essa resposta não tenha problemas com o código, ela parece ter alguns problemas com a recepção . Foi comentado que "não explica muita coisa além de como confiar em outra pessoa para fazer o trabalho" e o fato de quantas pessoas votaram nesse comentário mostra claramente que muitas coisas precisam de esclarecimentos.

Antes de tudo, uma boa resposta para "Como veicular imagens usando o Node.js" não está implementando um servidor de arquivos estático do zero e fazendo mal. Uma boa resposta é usar um módulo como o Express que faça o trabalho corretamente .

Os comentários de resposta que dizem que o uso do Express "não explica muito além de como confiar em alguém para fazer o trabalho" deve ser observado, que o uso do httpmódulo conta com alguém para fazer o trabalho. Se alguém não quiser confiar em ninguém para fazer o trabalho, pelo menos os soquetes TCP não processados ​​devem ser usados ​​- o que eu faço em um dos meus exemplos abaixo.

Um problema mais sério é que todas as respostas aqui que usam o httpmódulo estão quebradas . Eles introduzem condições de corrida , resolução de caminho insegura que levará a vulnerabilidade de percurso , bloqueando a E / S que falhará completamente em atender a solicitações simultâneas de todos e outros problemas sutis - elas são completamente quebradas como exemplos do que a pergunta faz e no entanto, eles já usam a abstração fornecida pelo httpmódulo em vez de usar soquetes TCP, para que nem façam tudo do zero, como afirmam.

Se a pergunta era "Como implementar um servidor de arquivos estático a partir do zero, como um exercício de aprendizado", deve-se postar todas as respostas sobre como fazer isso - mas mesmo assim, devemos esperar que pelo menos estejam corretas . Além disso, não é razoável supor que alguém que queira servir uma imagem possa querer servir mais imagens no futuro, portanto, alguém poderia argumentar que escrever um servidor de arquivos estático personalizado específico que pode servir apenas um único arquivo com caminho codificado é um pouco míope. Parece difícil imaginar que qualquer pessoa que procure uma resposta sobre como exibir uma imagem se contentaria com uma solução que serve apenas uma imagem em vez de uma solução geral para servir qualquer imagem.

Em suma, a questão é como servir uma imagem e uma resposta a isso é usar um módulo apropriado para fazê-lo de maneira segura, pré-executável e confiável, que seja legível, sustentável e à prova de futuro enquanto utiliza as melhores práticas do Nó profissional. desenvolvimento. Mas concordo que um ótimo complemento para essa resposta seria mostrar uma maneira de implementar a mesma funcionalidade manualmente, mas infelizmente todas as tentativas de fazer isso falharam até agora. E foi por isso que escrevi alguns novos exemplos.

Após esta breve introdução, aqui estão meus cinco exemplos fazendo o trabalho em 5 níveis diferentes de abstração.

Funcionalidade mínima

Todo exemplo serve arquivos do publicdiretório e suporta a funcionalidade mínima de:

  • Tipos MIME para os arquivos mais comuns
  • serve HTML, JS, CSS, texto sem formatação e imagens
  • serve index.htmlcomo um índice de diretório padrão
  • responde com códigos de erro para arquivos ausentes
  • sem vulnerabilidades transversais ao caminho
  • sem condições de corrida ao ler arquivos

Testei todas as versões nas versões 4, 5, 6 e 7 do Node.

express.static

Esta versão usa o express.staticmiddleware embutido do expressmódulo.

Este exemplo tem mais funcionalidade e menos quantidade de código.

var path = require('path');
var express = require('express');
var app = express();

var dir = path.join(__dirname, 'public');

app.use(express.static(dir));

app.listen(3000, function () {
    console.log('Listening on http://localhost:3000/');
});

express

Esta versão usa o expressmódulo, mas sem o express.staticmiddleware. A veiculação de arquivos estáticos é implementada como um manipulador de rota única usando fluxos.

Este exemplo possui contramedidas simples de deslocamento de caminho e suporta um conjunto limitado dos tipos MIME mais comuns.

var path = require('path');
var express = require('express');
var app = express();
var fs = require('fs');

var dir = path.join(__dirname, 'public');

var mime = {
    html: 'text/html',
    txt: 'text/plain',
    css: 'text/css',
    gif: 'image/gif',
    jpg: 'image/jpeg',
    png: 'image/png',
    svg: 'image/svg+xml',
    js: 'application/javascript'
};

app.get('*', function (req, res) {
    var file = path.join(dir, req.path.replace(/\/$/, '/index.html'));
    if (file.indexOf(dir + path.sep) !== 0) {
        return res.status(403).end('Forbidden');
    }
    var type = mime[path.extname(file).slice(1)] || 'text/plain';
    var s = fs.createReadStream(file);
    s.on('open', function () {
        res.set('Content-Type', type);
        s.pipe(res);
    });
    s.on('error', function () {
        res.set('Content-Type', 'text/plain');
        res.status(404).end('Not found');
    });
});

app.listen(3000, function () {
    console.log('Listening on http://localhost:3000/');
});

connect

Esta versão usa o connectmódulo que é um nível de abstração menor que express.

Este exemplo tem funcionalidade semelhante à expressversão, mas usando APIs de alavanca um pouco mais baixa.

var path = require('path');
var connect = require('connect');
var app = connect();
var fs = require('fs');

var dir = path.join(__dirname, 'public');

var mime = {
    html: 'text/html',
    txt: 'text/plain',
    css: 'text/css',
    gif: 'image/gif',
    jpg: 'image/jpeg',
    png: 'image/png',
    svg: 'image/svg+xml',
    js: 'application/javascript'
};

app.use(function (req, res) {
    var reqpath = req.url.toString().split('?')[0];
    if (req.method !== 'GET') {
        res.statusCode = 501;
        res.setHeader('Content-Type', 'text/plain');
        return res.end('Method not implemented');
    }
    var file = path.join(dir, reqpath.replace(/\/$/, '/index.html'));
    if (file.indexOf(dir + path.sep) !== 0) {
        res.statusCode = 403;
        res.setHeader('Content-Type', 'text/plain');
        return res.end('Forbidden');
    }
    var type = mime[path.extname(file).slice(1)] || 'text/plain';
    var s = fs.createReadStream(file);
    s.on('open', function () {
        res.setHeader('Content-Type', type);
        s.pipe(res);
    });
    s.on('error', function () {
        res.setHeader('Content-Type', 'text/plain');
        res.statusCode = 404;
        res.end('Not found');
    });
});

app.listen(3000, function () {
    console.log('Listening on http://localhost:3000/');
});

http

Esta versão usa o httpmódulo que é a API de nível mais baixo para HTTP no Nó.

Este exemplo tem funcionalidade semelhante à connectversão, mas usando APIs de nível ainda mais baixo.

var path = require('path');
var http = require('http');
var fs = require('fs');

var dir = path.join(__dirname, 'public');

var mime = {
    html: 'text/html',
    txt: 'text/plain',
    css: 'text/css',
    gif: 'image/gif',
    jpg: 'image/jpeg',
    png: 'image/png',
    svg: 'image/svg+xml',
    js: 'application/javascript'
};

var server = http.createServer(function (req, res) {
    var reqpath = req.url.toString().split('?')[0];
    if (req.method !== 'GET') {
        res.statusCode = 501;
        res.setHeader('Content-Type', 'text/plain');
        return res.end('Method not implemented');
    }
    var file = path.join(dir, reqpath.replace(/\/$/, '/index.html'));
    if (file.indexOf(dir + path.sep) !== 0) {
        res.statusCode = 403;
        res.setHeader('Content-Type', 'text/plain');
        return res.end('Forbidden');
    }
    var type = mime[path.extname(file).slice(1)] || 'text/plain';
    var s = fs.createReadStream(file);
    s.on('open', function () {
        res.setHeader('Content-Type', type);
        s.pipe(res);
    });
    s.on('error', function () {
        res.setHeader('Content-Type', 'text/plain');
        res.statusCode = 404;
        res.end('Not found');
    });
});

server.listen(3000, function () {
    console.log('Listening on http://localhost:3000/');
});

net

Esta versão usa o netmódulo que é a API de nível mais baixo para soquetes TCP no Nó.

Este exemplo possui algumas das funcionalidades da httpversão, mas o protocolo HTTP mínimo e incompleto foi implementado do zero. Como ele não suporta codificação em partes, ele carrega os arquivos na memória antes de servi-los para saber o tamanho antes de enviar uma resposta, pois a definição dos arquivos e o carregamento carregariam uma condição de corrida.

var path = require('path');
var net = require('net');
var fs = require('fs');

var dir = path.join(__dirname, 'public');

var mime = {
    html: 'text/html',
    txt: 'text/plain',
    css: 'text/css',
    gif: 'image/gif',
    jpg: 'image/jpeg',
    png: 'image/png',
    svg: 'image/svg+xml',
    js: 'application/javascript'
};

var server = net.createServer(function (con) {
    var input = '';
    con.on('data', function (data) {
        input += data;
        if (input.match(/\n\r?\n\r?/)) {
            var line = input.split(/\n/)[0].split(' ');
            var method = line[0], url = line[1], pro = line[2];
            var reqpath = url.toString().split('?')[0];
            if (method !== 'GET') {
                var body = 'Method not implemented';
                con.write('HTTP/1.1 501 Not Implemented\n');
                con.write('Content-Type: text/plain\n');
                con.write('Content-Length: '+body.length+'\n\n');
                con.write(body);
                con.destroy();
                return;
            }
            var file = path.join(dir, reqpath.replace(/\/$/, '/index.html'));
            if (file.indexOf(dir + path.sep) !== 0) {
                var body = 'Forbidden';
                con.write('HTTP/1.1 403 Forbidden\n');
                con.write('Content-Type: text/plain\n');
                con.write('Content-Length: '+body.length+'\n\n');
                con.write(body);
                con.destroy();
                return;
            }
            var type = mime[path.extname(file).slice(1)] || 'text/plain';
            var s = fs.readFile(file, function (err, data) {
                if (err) {
                    var body = 'Not Found';
                    con.write('HTTP/1.1 404 Not Found\n');
                    con.write('Content-Type: text/plain\n');
                    con.write('Content-Length: '+body.length+'\n\n');
                    con.write(body);
                    con.destroy();
                } else {
                    con.write('HTTP/1.1 200 OK\n');
                    con.write('Content-Type: '+type+'\n');
                    con.write('Content-Length: '+data.byteLength+'\n\n');
                    con.write(data);
                    con.destroy();
                }
            });
        }
    });
});

server.listen(3000, function () {
    console.log('Listening on http://localhost:3000/');
});

Exemplos de download

Postei todos os exemplos no GitHub com mais explicações.

Exemplos com express.static, express, connect, httpe net:

Outro projeto usando apenas express.static:

Testes

Os resultados dos testes estão disponíveis no Travis:

Tudo é testado nas versões 4, 5, 6 e 7 do Node.

Veja também

Outras respostas relacionadas:


8
Melhor e resposta completa para a pergunta. Que pena, eu posso te votar apenas uma vez.
Kulvar

3
Deve haver uma maneira de reformular questões antigas como essa! Só perdi uma hora tentando fazer com que a resposta de 110 votos funcionasse. Finalmente rolei para baixo apenas para verificar. Sua resposta poderia (deveria) ser um livro sobre o assunto.
Tailândia

2
Melhor resposta e com todos os detalhes. Deve ser marcado como a resposta aceita. Obrigado companheiro !!!
Prabodh M

Não sei por que alguém usaria o Express. Eu também fiz no início, provavelmente porque todo mundo fez. Então percebi que usar o módulo http do Node é a abordagem correta. É para isso que está previsto. Você tem muita flexibilidade. Você entende o protocolo HTTP e pode depurar facilmente. O Express fornece muito jargão e uma camada fina sobre o módulo http, que é fácil de implementar com a codificação bruta com o módulo http. Eu recomendo fortemente que qualquer usuário do Express ou qualquer outro módulo afaste-se deles e use o módulo http diretamente.
Sunny

1
uma observação que eu gostaria de mencionar para iniciantes como eu: quando declaramos uma pasta como estática express.static, podemos buscar a imagem chamando o URL http://ip:port/path_after_the_static_folder, não precisamos mencionar a própria pasta estática para veicular a imagem. Embora possamos acrescentar isso usando app.use('/static', express.static(imagePath))por conveniência como documentação par: expressjs.com/en/starter/static-files.html
Rakibul Haq

160

Concordo com os outros pôsteres que, eventualmente, você deve usar uma estrutura, como o Express .. mas primeiro você também deve entender como fazer algo fundamental como este sem uma biblioteca, para realmente entender o que a biblioteca abstrai para você. etapas são

  1. Analise a solicitação HTTP recebida para ver qual caminho o usuário está solicitando
  2. Adicione um caminho na declaração condicional para o servidor responder
  3. Se a imagem for solicitada, leia o arquivo de imagem do disco.
  4. Servir o tipo de conteúdo da imagem em um cabeçalho
  5. Servir o conteúdo da imagem no corpo

O código ficaria assim (não testado)

fs = require('fs');
http = require('http');
url = require('url');


http.createServer(function(req, res){
  var request = url.parse(req.url, true);
  var action = request.pathname;

  if (action == '/logo.gif') {
     var img = fs.readFileSync('./logo.gif');
     res.writeHead(200, {'Content-Type': 'image/gif' });
     res.end(img, 'binary');
  } else { 
     res.writeHead(200, {'Content-Type': 'text/plain' });
     res.end('Hello World \n');
  }
}).listen(8080, '127.0.0.1');

27
Você não deve usar o readFileSync no meio de uma resposta. Uma carga de sincronização deve ser usada no primeiro tique ou o método assíncrono. codr.cc/s/5d0b73d6/js
generalhenry

1
Estou a bordo com você na versão de sincronização; no entanto, para a versão assíncrona, pensei no perigo de usar operações sem bloqueio de arquivos, era que ele podia enviar a resposta antes que o arquivo inteiro fosse lido e acabava deixando você com um arquivo parcial sendo veiculado para o usuário? Você precisa usar a codificação em blocos se usar um arquivo assíncrono lido?
Noli 29/04

1
O fs.readFileSync não chama de retorno até que o arquivo inteiro seja carregado, portanto não há necessidade de manipulação de partes. A manipulação de partes é principalmente para transferência de arquivos em rede (já que as coisas podem demorar mais que o esperado).
generalhenry

9
A linha res.end(img);deve ser res.end(img, 'binary');. Bom trabalho!
Honza Pokorny

3
+1 para "mas primeiro você também deve entender como fazer algo fundamental como esse sem uma biblioteca, para realmente entender o que a biblioteca abstrai para você .."
Sunny

67

Você deve usar a estrutura expressa .

npm install express

e depois

var express = require('express');
var app = express();
app.use(express.static(__dirname + '/public'));
app.listen(8080);

e o URL localhost: 8080 / images / logo.gif deve funcionar.


18
Seguro, mas não explica muito além de como confiar em outra pessoa para fazer o trabalho.
precisa saber é o seguinte

Eu adicionei uma versão de nó de baunilha (apenas módulos principais).
generalhenry 24/09

+ I Esta é a única resposta correta postada até agora. Eu explico na minha resposta em mais detalhes.
Rsp #

15

É tarde demais, mas ajuda alguém, estou usando node version v7.9.0eexpress version 4.15.0

se sua estrutura de diretórios é algo como isto:

your-project
   uploads
   package.json
   server.js

código server.js:

var express         = require('express');
var app             = express();
app.use(express.static(__dirname + '/uploads'));// you can access image 
 //using this url: http://localhost:7000/abc.jpg
//make sure `abc.jpg` is present in `uploads` dir.

//Or you can change the directory for hiding real directory name:

`app.use('/images', express.static(__dirname+'/uploads/'));// you can access image using this url: http://localhost:7000/images/abc.jpg


app.listen(7000);

14

Versão do nó de baunilha, conforme solicitado:

var http = require('http');
var url = require('url');
var path = require('path');
var fs = require('fs');

http.createServer(function(req, res) {
  // parse url
  var request = url.parse(req.url, true);
  var action = request.pathname;
  // disallow non get requests
  if (req.method !== 'GET') {
    res.writeHead(405, {'Content-Type': 'text/plain' });
    res.end('405 Method Not Allowed');
    return;
  }
  // routes
  if (action === '/') {
    res.writeHead(200, {'Content-Type': 'text/plain' });
    res.end('Hello World \n');
    return;
  }
  // static (note not safe, use a module for anything serious)
  var filePath = path.join(__dirname, action).split('%20').join(' ');
  fs.exists(filePath, function (exists) {
    if (!exists) {
       // 404 missing files
       res.writeHead(404, {'Content-Type': 'text/plain' });
       res.end('404 Not Found');
       return;
    }
    // set the content type
    var ext = path.extname(action);
    var contentType = 'text/plain';
    if (ext === '.gif') {
       contentType = 'image/gif'
    }
    res.writeHead(200, {'Content-Type': contentType });
    // stream the file
    fs.createReadStream(filePath, 'utf-8').pipe(res);
  });
}).listen(8080, '127.0.0.1');

2
Você não deseja verificar se há fs.exists(condição de corrida), seria um hábito melhor detectar um erro durante a tubulação.
Brendan

Embora a verificação exista neste caso não seja a maneira do nó, tudo o resto nesta resposta é 1 milhão de vezes melhor que a resposta aceita.
Ninjaxor 27/05

1
Eu concordo com @BrendanAshworth. As condições da corrida estão presentes em quase todas as respostas aqui. Eu escrevi mais sobre isso na minha resposta . Mas parabéns por escrevê-lo com fluxos. Quase todas as outras respostas usam readFileSync, que está bloqueando e não deve ser usado em nenhum manipulador de eventos.
Rsp

1
var filePath = path.resolve ('public', '.' + parts.pathname); response.writeHead (200, {'Tipo de conteúdo': mime.lookup (parts.pathname)}); mime - pacote mime-type a partir de npm
Rijen

14

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

http.createServer(function(req, res) {
  res.writeHead(200,{'content-type':'image/jpg'});
  fs.createReadStream('./image/demo.jpg').pipe(res);
}).listen(3000);
console.log('server running at 3000');

simples e direto ao ponto, muito melhor do que as respostas expressas. isso merece 1000 votos positivos. Apenas uma dica: talvez remover o trecho de código e substituí-lo com o texto apenas javascript
bluejayke

seria possível passar um parâmetro para que './image/:jpg possa servir qualquer imagem?
Prav 29/01

13

Eu gosto de usar os serviços Restify for REST. No meu caso, eu criei um serviço REST para exibir imagens e, se uma fonte de imagens retornasse 404/403, eu desejaria retornar uma imagem alternativa. Aqui está o que eu criei combinando algumas das coisas aqui:

function processRequest(req, res, next, url) {
    var httpOptions = {
        hostname: host,
        path: url,
        port: port,
        method: 'GET'
    };

    var reqGet = http.request(httpOptions, function (response) {
        var statusCode = response.statusCode;

        // Many images come back as 404/403 so check explicitly
        if (statusCode === 404 || statusCode === 403) {
            // Send default image if error
            var file = 'img/user.png';
            fs.stat(file, function (err, stat) {
                var img = fs.readFileSync(file);
                res.contentType = 'image/png';
                res.contentLength = stat.size;
                res.end(img, 'binary');
            });

        } else {
            var idx = 0;
            var len = parseInt(response.header("Content-Length"));
            var body = new Buffer(len);

            response.setEncoding('binary');

            response.on('data', function (chunk) {
                body.write(chunk, idx, "binary");
                idx += chunk.length;
            });

            response.on('end', function () {
                res.contentType = 'image/jpg';
                res.send(body);
            });

        }
    });

    reqGet.on('error', function (e) {
        // Send default image if error
        var file = 'img/user.png';
        fs.stat(file, function (err, stat) {
            var img = fs.readFileSync(file);
            res.contentType = 'image/png';
            res.contentLength = stat.size;
            res.end(img, 'binary');
        });
    });

    reqGet.end();

    return next();
}

Você nunca deve usar readFileSync dentro de manipuladores de eventos. Esta é uma operação síncrona que bloqueia todo o seu processo enquanto ele lê o arquivo. Expliquei isso em minha resposta com mais detalhes.
Rsp

5

Isso pode ser um pouco estranho, pois você está perguntando sobre a veiculação de arquivos estáticos via Node.js especificamente (onde fs.createReadStream('./image/demo.jpg').pipe(res)é realmente uma boa ideia), mas na produção você pode querer que o aplicativo Node lide com tarefas, que não podem ser resolvidas de outra forma , e veiculação estática sem carga para, por exemplo, o Nginx.

Isso significa menos codificação dentro do aplicativo e melhor eficiência, pois os proxies reversos são projetados, ideais para isso.


3

Deixe-me acrescentar às respostas acima que a otimização de imagens e a veiculação de imagens responsivas ajudam a reduzir drasticamente o tempo de carregamento da página, pois mais de 90% do tráfego da Web são imagens. Convém pré-processar imagens usando módulos JS / Node , como imagemin e plug-ins relacionados, idealmente durante o processo de construção com Grunt ou Gulp .

Otimizar imagens significa processar encontrar um tipo de imagem ideal e selecionar a compactação ideal para obter um equilíbrio entre a qualidade da imagem e o tamanho do arquivo.

A exibição de imagens responsivas se traduz na criação de vários tamanhos e formatos de cada imagem automaticamente e o uso srcsetem seu html permite que você ative o conjunto ideal de imagens (ou seja, o formato e as dimensões ideais, portanto, o tamanho ideal do arquivo) para cada navegador).

A automação do processamento de imagens durante o processo de construção significa incorporá-la uma vez e apresentar imagens otimizadas posteriormente, exigindo um tempo extra mínimo.

Algumas ótimas leituras sobre imagens responsivas , minificação em geral, imagem no módulo do nó e uso do srcset .


1

Este método funciona para mim, não é dinâmico, mas direto ao ponto:

const fs      = require('fs');
const express = require('express');
const app     = express();

app.get( '/logo.gif', function( req, res ) {

  fs.readFile( 'logo.gif', function( err, data ) {

    if ( err ) {

      console.log( err );
      return;
    }

    res.write( data );
    return res.end();
  });

});

app.listen( 80 );

0

//This method involves directly integrating HTML Code in the res.write
//first time posting to stack ...pls be kind

const express = require('express');
const app = express();
const https = require('https');

app.get("/",function(res,res){
    res.write("<img src="+image url / src +">");
    res.send();
});

app.listen(3000, function(req, res) {
  console.log("the server is onnnn");
});

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.