Як подати зображення за допомогою nodejs


147

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

Які зміни потрібно зробити, щоб подати зображення.

Відповіді:


224

Оновлення 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 років, з тих пір, як це запитання було задано, є лише одна правильна відповідь від Generalhenry, але, хоча ця відповідь не має проблем з кодом, схоже, є деякі проблеми з отриманням . Прокоментували, що це "не пояснює багато іншого, крім того, як покластися на когось іншого, щоб виконати роботу", а факт того, скільки людей проголосували за цей коментар, чітко свідчить про те, що багато речей потребує роз'яснення.

Перш за все, хороша відповідь на тему "Як обслуговувати зображення за допомогою Node.js" - це не реалізувати статичний файловий сервер з нуля і робити це погано. Гарна відповідь - використання модуля типу Express, який виконує завдання правильно .

Відповідаючи на коментарі, в яких говориться, що використання Express "не пояснює багато іншого, крім того, як покластися на когось іншого, щоб виконати роботу", слід зазначити, що використання httpмодуля вже покладається на когось іншого, щоб виконати роботу. Якщо хтось не хоче покладатися на когось, щоб виконати роботу, тоді слід використовувати принаймні сировинні розетки TCP - що я роблю в одному зі своїх прикладів нижче.

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

Якщо питання було "Як реалізувати статичний файловий сервер з нуля, як навчальну вправу", то якраз і відповіді, як це зробити, слід розмістити - але навіть тоді ми повинні очікувати, що вони принаймні будуть правильними . Крім того, нерозумно вважати, що той, хто хоче подати зображення, може захотіти подавати більше зображень у майбутньому, щоб можна було стверджувати, що написання конкретного користувальницького статичного файлового сервера, який може обслуговувати лише один файл із жорстко закодованим шляхом дещо короткозорий. Мабуть важко уявити, що кожен, хто шукає відповідь про те, як подати зображення, буде задоволений рішенням, яке обслуговує лише одне зображення замість загального рішення для обслуговування будь-якого зображення.

Коротше кажучи, питання полягає в тому, як подати зображення, а відповідь на це - використовувати відповідний модуль, щоб зробити це в a безпечним, заздалегідь формуваним та надійним способом, який можна читати, ремонтувати та захищати від майбутнього , використовуючи кращі практики професійного вузла розвиток. Але я погоджуюся, що чудовим доповненням до такої відповіді було б показати спосіб реалізувати ту саму функціональність вручну, але, на жаль, кожна спроба зробити це не вдалося досі. І тому я написав кілька нових прикладів.

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

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

У кожному прикладі подаються файли з public каталогу та підтримує функцію minumum:

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

Я перевіряв кожну версію на версії 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:

Все перевірено на версії 4, 5, 6 та 7 у вузлі.

Дивитися також

Інші відповідні відповіді:


8
Найкраща та повна відповідь на питання. Шкода, я можу висловити вас лише один раз.
— Кульвар

3
Повинен бути спосіб переробити старовинні питання, такі як цей! Я просто витратив годину або близько того, намагаючись отримати 110 голосових відповідей. Нарешті я прокрутився вниз просто для перевірки. Ваша відповідь могла (повинна) бути підручником з цієї теми.
— Таїланд

2
Найкращий відповідь та з усіма деталями. Повинна бути позначена прийнята відповідь. Дякую, друже !!!
— Prabodh M

Я не знаю, навіщо хтось використовуватиме Express. Я теж на старті, мабуть, тому, що це зробили всі інші. Тоді я зрозумів, що використання http модуля Node - це правильний підхід. Саме це і передбачено. Ви отримуєте велику гнучкість. Ви розумієте протокол HTTP і можете легко налагоджувати. Експрес забезпечує багато жаргону та тонкого шару над модулем 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
Я зустрічаюся з вами щодо версії синхронізації, але для версії async я вважав, що небезпека використання операцій, що не блокують файли, полягає в тому, що вона може надіслати відповідь до того, як буде прочитаний весь файл, і в кінцевому підсумку залишить вас з частковим файлом, який подається користувачеві? Чи потрібно використовувати чіткове кодування, якщо ви використовуєте файл зчитування async?
— noli

1
fs.readFileSync не повертає виклик, поки не завантажується весь файл, тому немає необхідності в обробці фрагментів. Поводження з фрагментами здебільшого стосується передачі мережевих файлів (оскільки це може зайняти більше часу, ніж очікувалося).
— Generalhenry

9
Лінія res.end(img);повинна бути res.end(img, 'binary');. Гарна робота!
— Honza Pokorny

3
+1 для "але спочатку ви також повинні зрозуміти, як зробити щось таке принципове без бібліотеки, щоб дійсно зрозуміти, що бібліотека відбирає для вас .."
— Сонячний

67

Вам слід використовувати експрес- рамку.

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

Я додав версію ванільного вузла (лише основні модулі).
— generalhenry

+ I Це єдина правильна відповідь, розміщена на даний момент. Я пояснюю це у своїй відповіді більш детально.
— rsp

15

Це вже пізно, але допомагає комусь, я використовую node version v7.9.0іexpress version 4.15.0

якщо структура вашого каталогу приблизно така:

your-project
   uploads
   package.json
   server.js

код сервера.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(стан перегонів), було б краще звикати помилку під час трубопроводу.
— Брендан

Хоча перевірка існує в даному випадку не з вузлових шляхів, все інше щодо цієї відповіді в мільйон разів краще, ніж прийнята відповідь.
— Ninjaxor

1
Я згоден з @BrendanAshworth. Умови гонки присутні майже у всіх відповідях тут. Про це я написав більше у своїй відповіді . Але кудо за те, що написав це потоками. Майже у всіх інших відповідях використовується readFileSync, який блокує, і не повинен використовуватися в будь-яких обробниках подій.
— rsp

1
var filePath = path.resolve ('public', '.' + parts.pathname); response.writeHead (200, {'Content-Type': mime.lookup (parts.pathname)}); mime - пакунок mime-type від 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');

простий і, до речі, спосіб кращий, ніж експрес-відповіді. Це заслуговує на 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.