【问题标题】:How to serve an image using nodejs如何使用 nodejs 提供图像
【发布时间】:2025-12-18 01:55:01
【问题描述】:

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

我需要进行哪些更改才能投放图片。

【问题讨论】:

    标签: node.js


    【解决方案1】:

    您应该使用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 应该可以工作了。

    【讨论】:

    • 安全,但除了如何依靠别人来完成工作之外,并没有解释太多。
    • 我添加了一个香草节点(只是核心模块)版本。
    • +I 这是迄今为止发布的唯一正确答案。我会在my answer 中详细解释。
    【解决方案2】:

    我同意其他发帖者的观点,最终你应该使用一个框架,比如 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');
    

    【讨论】:

    • 您不应该在响应中间使用 readFileSync。应该在第一个 tic 上使用同步负载,或者应该使用异步方法。 codr.cc/s/5d0b73d6/js
    • 我支持同步版本,但对于异步版本,我认为对文件使用非阻塞操作的危险在于它可能会在整个文件之前发送响应被读取,并最终让您将部分文件提供给用户?如果使用异步文件读取,是否需要使用分块编码?
    • fs.readFileSync 在加载整个文件之前不会回调,因此不需要块处理。块处理主要用于网络文件传输(因为事情可能需要比预期更长的时间)。
    • res.end(img); 应该是res.end(img, 'binary');。干得好!
    • +1 表示“但首先,您还应该了解如何在没有库的情况下执行类似这样的基本操作,才能真正了解库为您抽象出什么......”
    【解决方案3】:

    我喜欢将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。这是一个同步操作,它会在读取文件时阻止您的整个进程。我在my answer 中进行了更详细的解释。
    【解决方案4】:

    原版节点版本要求:

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

    【讨论】:

    • 您不想检查fs.exists(竞争条件),最好在管道时发现错误。
    • 虽然在这种情况下存在检查不是节点方式,但关于这个答案的其他一切都比接受的答案好 100 万倍。
    • 我同意@BrendanAshworth 的观点。这里几乎所有的答案都存在竞争条件。我在my answer 中写了更多关于它的内容。但是感谢使用流编写它。几乎所有其他答案都使用阻塞的 readFileSync,不应在任何事件处理程序中使用。
    • var filePath = path.resolve('public', '.' + parts.pathname); response.writeHead(200, {'Content-Type': mime.lookup(parts.pathname)}); mime - 来自 npm 的包 mime-type
    【解决方案5】:

    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 只有 one correct answer,但即使该答案的代码没有问题,接收似乎也有一些问题。有人评论说它“除了如何依靠别人来完成工作之外没有解释太多”,有多少人投票赞成这条评论清楚地表明很多事情需要澄清。

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

    回答 cmets 说使用 Express “除了如何依赖别人来完成工作之外并没有解释太多” 应该注意的是,使用 http 模块 已经依赖其他人来完成工作。如果有人不想依赖任何人来完成工作,那么应该至少使用原始 TCP 套接字——我在下面的一个示例中就是这样做的。

    更严重的问题是这里所有使用http 模块的答案都损坏了。它们引入了竞争条件不安全的路径解析会导致路径遍历漏洞阻塞I/O会导致完全无法为任何并发请求提供服务以及其他微妙的问题 - 作为问题所问问题的示例,它们完全被破坏了,但它们已经使用了 http 模块提供的抽象而不是使用 TCP 套接字,因此他们甚至不会像他们声称的那样从头开始做所有事情。

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

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

    在这个简短的介绍之后,下面是我在 5 个不同抽象级别上完成这项工作的五个示例。

    最低限度的功能

    每个示例都提供来自public 目录的文件,并支持以下功能的最低限度:

    • 最常见文件的 MIME 类型
    • 提供 HTML、JS、CSS、纯文本和图像
    • index.html 用作默认目录索引
    • 以错误代码响应丢失文件
    • 没有路径遍历漏洞
    • 读取文件时没有竞争条件

    我在 Node 版本 4、5、6 和 7 上测试了每个版本。

    express.static

    此版本使用express模块的express.static内置中间件。

    这个例子功能最多,代码最少。

    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 模块,它是 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.staticexpressconnecthttpnet 的示例:

    仅使用express.static的其他项目:

    测试

    测试结果可在 Travis 上获得:

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

    另见

    其他相关答案:

    【讨论】:

    • 对该问题的最佳和完整答案。太糟糕了,我只能给你投票一次。
    • 应该有办法修改诸如此类的老式问题!我只是浪费了一个小时左右试图让 110 票对工作做出回应。最后我向下滚动只是为了检查。您的答案可能(应该)是有关该主题的教科书。
    • 我不知道为什么有人会使用 Express。一开始我也这样做了,可能是因为其他人都这样做了。然后我意识到使用 Node 的 http 模块是正确的方法。这就是它的用途。你得到了很大的灵活性。您了解 HTTP 协议,并且可以轻松调试。 Express 在 http 模块上提供了很多行话和一个薄层,这很容易通过 http 模块的原始编码实现。 我强烈建议 Express 或任何其他此类模块的任何用户远离它们并直接使用 http 模块。
    • 我想为像我这样的新手提一个注意事项:当我们使用 express.static 将文件夹声明为静态时,我们可以通过调用 url http://ip:port/path_after_the_static_folder 来获取图像,我们不需要提及静态文件夹本身来提供图像。虽然我们可以通过使用app.use('/static', express.static(imagePath)) 来添加它以方便作为标准文档:expressjs.com/en/starter/static-files.html
    • 这个答案的覆盖面很好,逐渐降低抽象是一个好方法。这对我来说是一个很好的资源;如果你想知道为什么人们会阅读这类东西,我个人是从 node.js 开始的,但我最终可能会在非常低规格、甚至无法运行 javascript 的深奥硬件上实现一些 html 状态页面。客户端和服务器都将是 LAN 上的弱计算机(无法访问 Internet)。
    【解决方案6】:

    为时已晚,但对某人有所帮助,我正在使用 node version v7.9.0express 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);
    

    【讨论】:

    【解决方案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');
    

    【讨论】:

    • 简单明了,比明确的答案要好得多.. 这值得 1000 次投票。只是一个提示:也许删除代码 sn-p 并仅用 javascript 文本替换它
    • 是否可以传递一个参数,以便 './image/:jpg 可以提供任何图片?
    • 正确/简单的答案..这就是我想要的。谢谢
    【解决方案8】:

    这可能有点跑题了,因为您正在询问通过 Node.js 提供静态文件的具体情况(fs.createReadStream('./image/demo.jpg').pipe(res) 实际上是一个好主意),但在生产中您可能希望让您的 Node 应用程序处理任务,否则无法解决,并将静态服务卸载到例如 Nginx。

    这意味着您的应用程序中的代码更少,效率更高,因为反向代理在设计上是理想的。

    【讨论】:

      【解决方案9】:

      这个方法对我有用,它不是动态的,而是直截了当的:

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

      【讨论】:

        【解决方案10】:

        让我补充一下上面的答案,优化图像和提供响应式图像有助于显着缩短页面加载时间,因为超过 90% 的网络流量是图像。您可能希望使用 JS / Node 模块预处理图像,例如 imagemin 和相关插件,最好是在构建过程中使用 Grunt 或 Gulp

        优化图像意味着处理寻找理想的图像类型,并选择最佳压缩以实现图像质量和文件大小之间的平衡。

        提供响应式图片意味着自动为每个图片创建多种尺寸和格式,在 HTML 中使用 srcset 可以让您提供最佳图片集(即理想的格式和尺寸,因此每个浏览器的最佳文件大小)。

        图像处理自动化在构建过程中意味着将其合并一次,然后进一步呈现优化的图像,需要最少的额外时间。

        关于responsive imagesminificationimagemin 节点模块和 using srcset 的一些精彩阅读。

        【讨论】:

          【解决方案11】:

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

          【讨论】: