如何使用nodejs提供图像

Nic*_*ilt 133 node.js

我有一个位于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');
Run Code Online (Sandbox Code Playgroud)

它工作,但当我要求localhost:8080/logo.gif然后我显然我没有得到徽标.

我需要做些什么来改变图像.

rsp*_*rsp 179

2016年更新

Express和without 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模块的所有答案都被打破了.它们引入了竞争条件,不安全的路径解析将导致路径遍历漏洞,阻止完全无法满足任何并发请求的I/O以及其他微妙的问题 - 它们完全被打破,作为问题所要求的示例,以及然而,他们已经使用http模块提供的抽象而不是使用TCP套接字,因此他们甚至不会像他们声称的那样从头做任何事情.

如果问题是"如何从头开始实现静态文件服务器,作为一种学习练习"那么通过各种方式回答如何做到这一点应该被发布 - 但即便如此,我们也应该期望它们至少是正确的.此外,假设想要提供图像的人可能希望将来提供更多图像是不合理的,因此有人可能会认为编写一个特定的自定义静态文件服务器只能为一个具有硬编码路径的文件提供服务.有些短视.似乎很难想象任何搜索如何提供图像的答案的人都会满足于只提供单个图像的解决方案,而不是为任何图像提供服务的通用解决方案.

简而言之,问题是如何提供图像,并且答案是使用适当的模块以安全,预先形成且可靠的方式执行此操作,这种方式在使用专业节点的最佳实践时是可读的,可维护的且面向未来的发展.但是我同意这样一个答案的一个很好的补充就是显示了一种手动实现相同功能的方法,但遗憾的是,到目前为止,每次尝试这样做都失败了.这就是为什么我写了一些新的例子.

在这个简短的介绍之后,这里是我在五个不同抽象层次上完成工作的五个例子.

最低功能

每个示例都提供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/');
});
Run Code Online (Sandbox Code Playgroud)

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/');
});
Run Code Online (Sandbox Code Playgroud)

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/');
});
Run Code Online (Sandbox Code Playgroud)

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/');
});
Run Code Online (Sandbox Code Playgroud)

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/');
});
Run Code Online (Sandbox Code Playgroud)

下载示例

我在GitHub上发布了所有的例子,并附有更多解释.

有例子express.static,express,connect,httpnet:

其他项目仅使用express.static:

测试

测试结果可在Travis上获得:

一切都在Node版本4,5,6和7上进行了测试.

也可以看看

其他相关答案:

  • 这个问题的最佳和完整答案.太糟糕了,我只能投票一次. (5认同)
  • 应该有一种方法可以修改这样的老式问题!我只是在浪费一个小时左右的时间来尝试获得110票的投票响应率。最后,我向下滚动只是为了检查。您的答案可能(应该)是关于该主题的教科书。 (2认同)
  • 我不知道为什么会有人使用 Express。一开始我也这样做了,可能是因为其他人都这样做了。然后我意识到使用 Node 的 http 模块是正确的方法。这就是它所提供的。您将获得很大的灵活性。您了解 HTTP 协议,并且可以轻松调试。Express 在 http 模块上提供了大量术语和一个薄层,这很容易通过 http 模块的原始编码实现。**我强烈建议任何 Express 或任何其他此类模块的用户远离它们并直接使用 http 模块。** (2认同)
  • 对于像我这样的新手,我想提一下:当我们使用“express.static”将文件夹声明为静态时,我们可以通过调用 url“http://ip:port/path_after_the_static_folder”来获取图像,我们不需要提及静态文件夹本身来提供图像。尽管为了方便起见,我们可以通过使用 `app.use('/static',express.static(imagePath))` 来添加它,作为标准文档:https://expressjs.com/en/starter/static-files.html (2认同)
  • 人们要求不涉及express的答案的原因是他们想在没有框架的Node级别上了解node是如何工作的。这些人可能也会很乐意了解 TCP 套接字的工作原理。那些喜欢编写代码、喜欢理解和学习框架的人通常会提供完成工作所需的信息,但不会提供了解工作如何完成所需的信息。它不仅仅是理解“如何”,更是理解“为什么”。 (2认同)

nol*_*oli 152

我同意其他海报,最终你应该使用一个框架,比如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');
Run Code Online (Sandbox Code Playgroud)

  • 您不应该在响应中使用readFileSync.应该在第一个tic上使用同步加载,或者应该使用异步方法.codr.cc/s/5d0b73d6/js (27认同)
  • 行`res.end(img);`应该是`res.end(img,'binary');`.干得好! (9认同)
  • +1"for"但首先你应该理解如何在没有图书馆的情况下做一些基本的事情,真正理解图书馆为你抽象的东西.." (3认同)

gen*_*nry 61

您应该使用快速框架.

npm install express

var express = require('express');
var app = express();
app.use(express.static(__dirname + '/public'));
app.listen(8080);
Run Code Online (Sandbox Code Playgroud)

然后url localhost:8080/images/logo.gif应该可以工作

  • 安全,但除了如何依赖别人完成工作之外,没有其他解释. (17认同)

gen*_*nry 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');
Run Code Online (Sandbox Code Playgroud)

  • 您不想检查“ fs.exists”(竞赛条件),这是在管道传输时发现错误的更好习惯。 (2认同)

occ*_*asl 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();
}
Run Code Online (Sandbox Code Playgroud)


Muh*_*zad 13

现在为时已晚,但有人帮忙,我正在使用node version v7.9.0express version 4.15.0

如果您的目录结构是这样的:

your-project
   uploads
   package.json
   server.js
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)


小智 8

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');
Run Code Online (Sandbox Code Playgroud)