nodejsを使用して画像を提供する方法


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を要求すると、ロゴが表示されません。

画像を配信するために必要な変更

回答:


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の正解は1つだけですが、その答えはコードに問題はありませんが、受信に問題があるようです。それは「仕事を成し遂げるために他の誰かに頼る方法以外はあまり説明しない」とコメントされ、多くの人々がこのコメントに投票したという事実は多くのことが明確化を必要とすることを明らかに示しています。

まず、「Node.jsを使用して画像を提供する方法」に対する適切な答えは、静的ファイルサーバーを最初から実装するのではなく、ひどいことです。良い答えは、正しく機能する Expressのようなモジュールを使用することです。

Expressを使用すると「他の人に頼って仕事を終わらせる方法以外はあまり説明されない」というコメントに答えると、httpモジュールの使用はすでに他の人に頼って仕事を終えていることに注意してください。誰かが誰かに頼って仕事をやりたくない場合は、代わりに少なくとも生のTCPソケットを使用する必要があります。これは、以下の私の例の1つで行います。

より深刻な問題は、httpモジュールを使用するここでのすべての回答が壊れていることです。彼らは、紹介、レース条件、安全でないパスの解決につながるパストラバーサルの脆弱性、I / Oをブロックし、完全になります任意の同時要求提供するために失敗し、彼らが完全に質問がについて尋ねるものの例として壊れている、と-すべておよびその他の微妙な問題ではしかし、httpTCPソケットを使用する代わりに、モジュールが提供する抽象化をすでに使用しているため、要求どおりに最初からすべてを実行することすらありません。

質問が「静的ファイルサーバーを最初から実装する方法、学習課題として」である場合、それを実行する方法は必ず回答されますが、それでも、少なくとも正しいことが期待されます。また、画像を提供したい人が将来さらに多くの画像を提供したいと思うので、ハードコードされたパスを持つ単一のファイルのみを提供できる特定のカスタム静的ファイルサーバーを書くことは、やや近視。画像を提供する方法についての回答を検索する人が、画像を提供する一般的なソリューションではなく、単一の画像のみを提供するソリューションで満足することは想像に難く思えます。

要するに、問題は、画像をどのように提供するかであり、その答えは、適切なモジュールを使用して、プロフェッショナルノードのベストプラクティスを使用しながら、読み取り可能で、保守可能で、将来も保証された、安全で予備的かつ信頼できる方法でそれを実行することです。開発。しかし、私はそのような答えに大きな追加が同じ機能を手動で実装する方法を示すことになることに同意しますが、残念ながらそれを行うすべての試みはこれまで失敗しました。そしてそれが私がいくつかの新しい例を書いた理由です。

この短い紹介の後、5つの異なる抽象化レベルで作業を行う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は、1つ下のレベルの抽象化であるモジュールを使用します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

このバージョンでは、httpNodeの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

このバージョンは、netNodeの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で入手できます。

すべてがNodeバージョン4、5、6、7でテストされています。

こちらもご覧ください

その他の関連する回答:


8
質問に対する最良で完全な答え。残念ながら、私は1度だけ賛成票を投じることができます。
— Kulvar 2017

3
このようなヴィンテージの質問を修正する方法があるはずです!私は1時間ほど無駄にして、110票の回答を得ようとしました。最後に、確認のために下にスクロールしました。あなたの答えは、そのトピックに関する教科書であるはずです(すべきです)。
— タイの

2
ベストアンサーと詳細。マークされた承認済みの回答である必要があります。どうも !!!
— Prabodh M 2017

なぜ誰もが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))便宜上、parjの
— Rakibul Haq

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
私は同期バージョンであなたと一緒にいますが、非同期バージョンでは、ファイルに対して非ブロッキング操作を使用することの危険性は、ファイル全体が読み取られる前に応答を送信し、最終的にあなたを離れることになると考えました部分的なファイルがユーザーに提供されていますか?非同期ファイル読み取りを使用する場合、チャンクエンコーディングを使用する必要がありますか?
— Noliの

1
fs.readFileSyncは、ファイル全体が読み込まれるまでコールバックしないため、チャンクを処理する必要はありません。チャンク処理は、主にネットワークファイル転送用です(予想よりも時間がかかる場合があるため)。
— ジェネラルヘンリー

9
行res.end(img);はである必要がありますres.end(img, 'binary');。よくできました!
— Honza Pokorny

3
+1「しかし、まずライブラリなしでこのような基本的なことを行う方法を理解し、ライブラリが抽象化するものを本当に理解する必要があります。」
— Sunny

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 2014

バニラノード(コアモジュールのみ)バージョンを追加しました。
— Generalhenry 2014

+ Iこれがこれまでに投稿された唯一の正解です。私はそれを私の回答でより詳細に説明します。
— rsp-2016年

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(競合状態)を確認したくない場合は、配管時にエラーをキャッチする方がよいでしょう。
— ブレンダン

この場合のチェックはノードの方法ではありませんが、この回答に関する他のすべては、受け入れられた回答よりも100万倍優れています。
— Ninjaxor 2016年

1
@BrendanAshworthに同意します。ここでのほぼすべての回答に競合状態が存在します。私はそれについてもっと答えを書いた。しかし、ストリームでそれを書いたことに対する称賛。他のほとんどすべての回答は、ブロッキングである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');

簡単かつ要点として、明白な答えよりもはるかに優れています。これは、1000回の賛成投票に値します。ヒント:コードスニペットを削除し、JavaScriptテキストだけに置き換える
— bluejayke

'./image/:jpgが任意の画像を提供できるようにパラメーターを渡すことは可能でしょうか?
— Prav

13

RESTify for 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

上記の回答に加えて、画像を最適化し、レスポンシブな画像を提供することで、Webトラフィックの90%以上が画像であるため、ページの読み込み時間が劇的に向上します。あなたはしたいかもしれませんJS /ノードモジュールを使用して画像のプリプロセスなどimagemin理想的なうなり声やとビルドプロセスの間に、関連するプラグインをがぶ飲み。

画像の最適化とは、理想的な画像タイプを見つけて処理し、最適な圧縮を選択して、画質とファイルサイズのバランスをとることを意味します。

レスポンシブ画像を配信すると、各画像の複数のサイズとフォーマットが自動的に作成さ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");
});

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.