如何使用Node.js提供图片


147

我有一个位于public / 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时,我显然没有得到徽标。

服务图像需要做些什么更改。

Answers:


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年多之后,通俗的回答只有一个正确的答案,但是即使该答案在代码上没有问题,也似乎在接收方面存在一些问题。有人评论说,“除了如何依靠别人来完成工作外,它没有其他解释”,而且有多少人投票赞成这一评论,这一事实清楚地表明,很多事情需要澄清。

首先,“如何使用Node.js提供图像”的一个很好的答案不是从头开始实现静态文件服务器,而是做得不好。一个很好的答案是使用 Express这样的模块来正确地完成工作。

回答说使用Express “除了如何依靠别人来完成工作外,没有什么其他解释”,应该指出,使用http模块已经依靠别人来完成工作。如果某人不想依靠任何人来完成工作,那么至少应该使用原始的TCP套接字代替-我在下面的示例之一中这样做。

一个更严重的问题是,此处使用该http模块的所有答案都被破坏了。它们引入了竞争条件,不安全的路径解析(将导致路径穿越漏洞),阻止将完全无法满足所有并发请求以及其他细微问题的I / O-它们被完全破坏为问题的示例,并且但是他们已经使用了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版本相似的功能,但使用的杠杆级别稍低。

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模块是Node中HTTP的最低级别的API。

该示例与该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模块是Node中TCP套接字的最低级别的API。

此示例具有该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
最佳和完整的答案。太可惜了,我只能给你投票一次。
— Kulvar '17

3
应该有一种方法可以修改这样的老式问题!我只是在浪费一个小时左右的时间来尝试获得110票的回应。最后,我向下滚动只是为了检查。您的答案可能(应该)是关于该主题的教科书。
— 泰国人

2
最佳答案和详细信息。应该标记为接受的答案。谢了哥们 !!!
— Prabodh M

我不知道为什么会有人使用Express。一开始我也这样做了,可能是因为其他所有人也这样做了。然后我意识到使用Node的http模块是正确的方法。这就是它所提供的。您将获得很大的灵活性。您了解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
— Rakibul Haq

159

我同意其他张贴者的观点,最终,您应该使用诸如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。应该在第一个tic上使用同步负载,或者应该使用async方法。codr.cc/s/5d0b73d6/js
— generalhenry 2011年

1
我支持同步版本,但对于异步版本,我认为对文件使用非阻塞操作的危险在于,它可能会在读取整个文件之前发送响应,并最终让您离开向用户提供部分文件?如果使用异步文件读取,是否需要使用分块编码?
— noli 2011年

1
fs.readFileSync在整个文件加载之前不会回调,因此不需要块处理。块处理主要用于网络文件传输(因为操作可能比预期花费的时间更长)。
— generalhenry 2011年

9
该行res.end(img);应为res.end(img, 'binary');。干得好!
— Honza Pokorny 2011年

3
为“ +1”表示“但首先,您还应该了解如何在没有库的情况下进行类似的基本操作,以真正了解库为您提取的内容。”
— 2015年

67

您应该使用快速框架。

npm install express

然后

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

然后网址localhost:8080 / images / logo.gif应该可以使用。


18
是安全的,但除了如何依靠别人来完成工作外,别无其他解释。
— LeeGee 2014年

我添加了一个香草节点(仅核心模块)版本。
— generalhenry 2014年

+ 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

要求的Vanilla节点版本:

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(竞赛条件),这是在管道传输时发现错误的更好习惯。
— 布伦丹2014年

尽管在这种情况下存在检查不是结点方式,但有关此答案的其他所有内容都比公认的答案好一百万倍。
— 忍者

1
我同意@BrendanAshworth。比赛条件几乎存在于此处。我在回答中写了更多有关它的内容。但是Kudos使用流来编写它。几乎所有其他答案都使用readFileSync,它阻塞了,不应在任何事件处理程序中使用。
— rsp

1
var filePath = path.resolve('public','。'+ parts.pathname); response.writeHead(200,{'Content-Type':mime.lookup(parts.pathname)}); mime-从npm打包mime-type
— 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');

简单明了,比表达答案更好。.这值得一千个投票。提示:也许删除代码片段,并仅用javascript文本替换
— bluejayke

是否可以传递参数,以使'./image/:jpg可以提供任何图片?
— Prav

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.