Как обслуживать изображение с помощью nodejs

У меня есть логотип, который находится у публики /images/logo.gif. Вот мой код nodejs.

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

Он работает, но когда я запрашиваю localhost: 8080/logo.gif, то я, очевидно, не получаю логотип.

Какие изменения мне нужно сделать для обслуживания изображения.

Ответ 1

Обновление 2016

Примеры с Express и без Express, которые фактически работают

Этот вопрос старше 5 лет, но каждый ответ имеет некоторые проблемы.

TL; DR

Прокрутите вниз, чтобы примеры отображались с помощью:

  • express.static
  • express
  • connect
  • http
  • net

Все примеры также находятся на GitHub: https://github.com/rsp/node-static-http-servers

Результаты теста доступны на Travis: https://travis-ci.org/rsp/node-static-http-servers

Введение

Спустя более 5 лет с момента появления этого вопроса существует один правильный ответ generalhenry, но даже хотя у этого ответа нет проблем с кодом, у него, похоже, есть проблемы с приемом. Было прокомментировано, что он "не объясняет многое, кроме как полагаться на кого-то другого, чтобы выполнить свою работу", и тот факт, сколько людей проголосовали за этот комментарий, ясно показывает, что многие вещи нуждаются в разъяснении.

Прежде всего, хороший ответ на вопрос "Как использовать изображения с помощью Node.js" не реализует статический файловый сервер с нуля и делает это плохо. Хороший ответ с использованием модуля, например Express, который правильно выполняет работу.

Ответы на комментарии, которые говорят, что использование Express "не объясняет многое, кроме как полагаться на кого-то другого, чтобы выполнить работу", следует отметить, что с использованием http модуля уже полагается на кого-то другого, чтобы выполнить свою работу. Если кто-то не хочет полагаться на кого-либо, чтобы выполнить задание, вместо этого следует использовать как минимум необработанные сокеты TCP, которые я делаю в одном из приведенных ниже примеров.

Более серьезная проблема заключается в том, что все ответы здесь, в которых используется модуль http, сломанный. Они вносят условия гонки, небезопасного разрешения пути, что приведет к уязвимости, блокировке ввода/вывода, которая будет полностью не могут выполнять любые параллельные запросы вообще и другие тонкие проблемы - они полностью разбиты как примеры того, о чем спрашивает вопрос, и тем не менее они уже используют абстракцию, предоставляемую модулем http вместо использования сокетов TCP, чтобы они даже не делали все с нуля, как они утверждают.

Если возник вопрос: "Как реализовать статический файловый сервер с нуля, как упражнение для обучения", то, во всяком случае, ответы, как это сделать, должны быть опубликованы, но даже тогда мы должны ожидать, что они будут по крайней мере правильными. Кроме того, небезосновательно предположить, что кто-то, кто хочет обслуживать изображение, может захотеть показывать больше изображений в будущем, поэтому можно утверждать, что запись определенного пользовательского статического файлового сервера, который может обслуживать только один файл с жестко закодированным путем, несколько близоруких. Кажется, трудно представить, что любой, кто ищет ответ о том, как обслуживать изображение, будет удовлетворен решением, которое обслуживает только одно изображение вместо общего решения для обслуживания любого изображения.

Вкратце, вопрос заключается в том, как обслуживать изображение, и ответ на этот вопрос заключается в использовании соответствующего модуля для этого безопасного, предварительного и надежного способа, который доступен для чтения, поддерживаемый и перспективный при использовании лучшей практики профессиональной разработки Node. Но я согласен с тем, что отличное дополнение к такому ответу будет показывать способ реализовать одну и ту же функциональность вручную, но, к сожалению, каждая попытка сделать это пока не удалась. Вот почему я написал несколько новых примеров.

После этого краткого введения, вот мои пять примеров, выполняющих работу на 5 разных уровнях абстракции.

Минимальная функциональность

Каждый пример служит для файлов из каталога public и поддерживает минимальную функциональность:

  • Типы MIME для большинства распространенных файлов
  • служит HTML, JS, CSS, обычный текст и изображения
  • служит index.html как индекс каталога по умолчанию
  • отвечает кодами ошибок для отсутствующих файлов
  • Отсутствие уязвимостей, связанных с обходом пути
  • Отсутствие условий гонки при чтении файлов

Я тестировал каждую версию на Node версиях 4, 5, 6 и 7.

express.static

В этой версии используется express.static встроенное промежуточное программное обеспечение express.

Этот пример имеет наибольшую функциональность и наименьший объем кода.

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

В этой версии используется express, но без промежуточного программного обеспечения express.static. Обслуживание статических файлов реализовано как один обработчик маршрута с использованием потоков.

В этом примере реализованы простейшие контрмеры с обходом пути и поддерживается ограниченный набор наиболее распространенных типов MIME.

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

В этой версии используется модуль connect, который является одним уровнем абстракции ниже express.

Этот пример имеет сходную функциональность с версией express, но с использованием немного более низких API-интерфейсов.

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

В этой версии используется http модуль, который является самым низким уровнем API для HTTP в Node.

Этот пример имеет сходную функциональность с версией connect, но использует еще более API более низкого уровня.

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

В этой версии используется net модуль, который является API нижнего уровня для сокетов TCP в Node.

В этом примере есть некоторые функции версии http, но минимальный и неполный протокол HTTP был реализован с нуля. Поскольку он не поддерживает кодирование с чередованием, он загружает файлы в память, прежде чем обслуживать их, чтобы узнать размер перед отправкой ответа, потому что статистика файлов, а затем загрузка приведет к состоянию гонки.

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/');
});

Примеры загрузки

Я привел все примеры на GitHub с дополнительными пояснениями.

Примеры с express.static, express, connect, http и net:

Другой проект, используя только express.static:

Испытания

Результаты теста доступны на Travis:

Все тестируется на Node версиях 4, 5, 6 и 7.

См. также

Другие связанные ответы:

Ответ 2

Я согласен с другими плакатами, что в конечном итоге вы должны использовать фреймворк, такой как Express.. но сначала вы также должны понимать, как сделать что-то фундаментальное, подобное этому, без библиотеки, чтобы действительно понять, что библиотека абстрагирует для вас.. Шаги

  • Разберите входящий HTTP-запрос, чтобы узнать, к какому пути пользователь запрашивает
  • Добавить путь в условном выражении для ответа сервера на
  • Если изображение запрашивается, прочитайте файл с диска.
  • Подавать содержимое содержимого изображения в заголовке
  • Служить содержимому изображения в теле

Код будет выглядеть примерно так (не проверен)

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');

Ответ 3

Вы должны использовать структуру express.

npm install express

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

а затем url localhost: 8080/images/logo.gif должен работать

Ответ 4

Версия Vanilla node по запросу:

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');

Ответ 5

Слишком поздно, но помогает кому-то, я использую node version v7.9.0 и express version 4.15.0

если ваша структура каталогов выглядит примерно так:

your-project
   uploads
   package.json
   server.js

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);

Ответ 6

Мне нравится использовать Restify для служб REST. В моем случае я создал службу REST для обслуживания изображений, а затем, если источник изображения вернулся 404/403, я хотел вернуть альтернативное изображение. Вот что я придумал, объединив некоторые вещи здесь:

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();
}

Ответ 7

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');

Ответ 8

Это может быть немного не по теме, так как вы спрашиваете об обслуживании статических файлов через Node.js (где на самом деле хорошая идея fs.createReadStream('./image/demo.jpg').pipe(res)), но в работе вы можете захотеть, чтобы ваше приложение Node обрабатывало задачи, которые не могут решаться иначе и снимать статическую нагрузку, например, с Nginx. Это означает меньшее количество кода в вашем приложении и лучшую эффективность, поскольку обратные прокси-сервера идеально подходят для этого.

Ответ 9

Вам нужно использовать библиотеку, которая знает URL-адреса и статические файлы. Я рекомендую использовать Express. Он имеет возможности для настройки маршрутов и статический модуль для работы с файлами.

Ответ 10

Этот метод работает для меня, он не динамический, но прямо в точку:

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 );