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


148

У меня есть логотип, который находится в общедоступном / 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, тогда я, очевидно, не получаю логотип.

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

Ответы:


225

Обновление 2016

Примеры с Express и без Express, которые действительно работают

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

TL; DR

Прокрутите вниз, чтобы найти примеры изображений:

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

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

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

Введение

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

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

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

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

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

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

После этого краткого вступления, вот мои пять примеров, делающих работу на 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 был реализован с нуля. Поскольку он не поддерживает chunked-кодировку, он загружает файлы в память, а затем отправляет их, чтобы узнать их размер перед отправкой ответа, потому что проверка файлов и последующая загрузка приведут к состязанию.

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.

Смотрите также

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


8
Лучший и полный ответ на вопрос. Очень жаль, что я могу проголосовать только один раз.
— Кульвар

3
Должен быть способ переделать винтажные вопросы, такие как этот! Я просто потратил час или около того, пытаясь заставить ответ 110 голосов работать. Наконец я прокрутил вниз, чтобы проверить. Ваш ответ может (должен) быть учебником по теме.
— Тайланд

2
Лучший ответ и в деталях. Должен быть отмечен принятый ответ. Спасибо друг !!!
— Прабод М

Я не знаю, почему кто-то будет использовать Express. Я тоже сделал в начале, вероятно, потому что все остальные сделали. Затем я понял, что использование http-модуля Node - правильный подход. Вот для чего это предусмотрено. Вы получаете большую гибкость. Вы понимаете протокол HTTP, и вы можете легко отлаживать. Express предоставляет много жаргона и тонкого слоя над модулем http, который легко реализовать с помощью простого кодирования с модулем http. Я настоятельно рекомендую любому пользователю Express или любому другому такому модулю отойти от них и использовать модуль http напрямую.
— Солнечно

1
Примечание, которое я хотел бы упомянуть для новичков, таких как я: когда мы объявляем папку как статическую, используя ее, express.staticмы можем извлечь изображение, вызвав URL http://ip:port/path_after_the_static_folder, нам не нужно упоминать саму статическую папку для обслуживания изображения. Хотя мы можем добавить это, используя app.use('/static', express.static(imagePath))для удобства документацию по номиналу: expressjs.com/en/starter/static-files.html
— Ракибул Хак,

160

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

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

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

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
Вы не должны использовать readFileSync в середине ответа. Либо синхронизирующая нагрузка должна использоваться на первом тике, либо должен использоваться асинхронный метод. codr.cc/s/5d0b73d6/js
— generalhenry

1
Я согласен с вами по поводу версии для синхронизации, хотя для асинхронной версии я подумал, что опасность использования неблокирующих операций для файлов заключается в том, что он может отправить ответ до того, как весь файл будет прочитан, и в конечном итоге оставит вас с частичным файлом, обслуживаемым пользователем? Вам нужно использовать chunked-кодировку, если вы используете чтение асинхронного файла?
— Ноли

1
fs.readFileSync не вызывает до тех пор, пока не будет загружен весь файл, поэтому нет необходимости в обработке чанка. Обработка чанка в основном для передачи файлов по сети (поскольку все может занять больше времени, чем ожидалось).
— генерал Генри

9
Линия res.end(img);должна быть res.end(img, 'binary');. Хорошая работа!
— Хонза Покорный

3
+1 за «но сначала вы должны также понять, как сделать нечто фундаментальное, подобное этому, без библиотеки, чтобы действительно понять, что библиотека абстрагирует для вас…»
— Солнечный

67

Вы должны использовать Express Framework.

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 должен работать.


18
Безопасно, но ничего не объясняет, кроме как положиться на кого-то другого, чтобы выполнить работу.
— LeeGee

Я добавил версию ванильного узла (только основные модули).
— генералхенри

+ I Это единственный правильный ответ, опубликованный до сих пор. Я объясню это в моем ответе более подробно.
— rsp

15

Уже поздно но кому то помогает, пользуюсь 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);

14

Версия узла ванили по запросу:

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
Вы не хотите проверять fs.exists(состояние гонки), лучше было бы ловить ошибку при прокачке трубопровода.
— Брендан,

Хотя проверка в этом случае не является узловой, все остальное в этом ответе в 1 миллион раз лучше принятого ответа.
— Ниндзяксор

1
Я согласен с @BrendanAshworth. Условия гонки присутствуют почти во всех ответах здесь. Я написал об этом больше в своем ответе . Но слава за то, что написал это с потоками. Почти все другие ответы используют readFileSync, который блокирует и не должен использоваться ни в каких обработчиках событий.
— RSP

1
var filePath = path.resolve ('public', '.' + parts.pathname); response.writeHead (200, {'Content-Type': mime.lookup (parts.pathname)}); мим - пакет мим-тип от НОГО
— 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');

просто и точно, намного лучше, чем экспресс-ответы .. это заслуживает 1000 голосов. Просто совет: возможно, удалите фрагмент кода и замените его просто текстом javascript
— bluejayke

Можно ли передать параметр, чтобы './image/:jpg мог обслуживать любую картинку?
— Право

13

Мне нравится использовать 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();
}

Вы никогда не должны использовать readFileSync внутри обработчиков событий. Это синхронная операция, которая блокирует весь процесс, пока он читает файл. Я объяснил это в моем ответе более подробно.
— RSP

5

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

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


3

Позвольте мне добавить к ответам, приведенным выше, что оптимизация изображений и предоставление отзывчивых изображений значительно ускоряет загрузку страниц, поскольку> 90% веб-трафика составляют изображения. Возможно, вы захотите предварительно обработать изображения, используя модули JS / Node, такие как imagemin и связанные плагины, в идеале во время процесса сборки с Grunt или Gulp .

Оптимизация изображений означает обработку, чтобы найти идеальный тип изображения и выбрать оптимальное сжатие для достижения баланса между качеством изображения и размером файла.

Обслуживание адаптивных изображений приводит к автоматическому созданию нескольких размеров и форматов каждого изображения, а использование srcsetв вашем html позволяет вам предоставлять оптимальный набор изображений (т. Е. Идеальный формат и размеры, то есть оптимальный размер файла) для каждого отдельного браузера).

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

Некоторые отлично читают отзывчивые изображения , минификацию в целом, модуль узла imagemin и использование srcset .


1

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.