1.ストリームモジュール
stream は Node.js が提供するサーバー側でのみ利用できるモジュールで、その目的は「stream」のデータ構造をサポートすることです。
ストリームとは何ですか? ストリームは抽象的なデータ構造です。水の流れを想像してみてください。パイプの中を流れる水は、ある場所 (水道など) から別の場所 (流し台など) まで継続的に流れることができます。データをデータ ストリームと考えることもでき、たとえば、キーボードで入力するときに、各文字を順番に接続して文字ストリームとして見ることができます。このストリームはキーボードからアプリケーションに入力されるもので、実際には標準入力ストリーム (stdin) という名前にも対応します。
アプリケーションが文字を 1 つずつディスプレイに出力する場合、これもストリームとみなすことができ、このストリームには標準出力ストリーム (stdout) という名前も付けられます。ストリームの特徴は、データが順序付けられており、順次読み取りまたは書き込みを行う必要があり、配列のようにランダムに配置できないことです。
一部のストリームはデータの読み取りに使用されます。たとえば、ファイルからデータを読み取る場合、ファイル ストリームを開いてから、ファイル ストリームからデータを継続的に読み取ることができます。一部のストリームはデータの書き込みに使用されます。たとえば、データをファイルに書き込む場合、ファイル ストリームにデータを継続的に書き込むだけで済みます。
Node.js では、ストリームもオブジェクトです。ストリーム イベントに応答するだけで済みます。データ イベントはストリームのデータが読み取れることを示し、終了イベントはストリームが最後に到達し、ストリームが存在することを示します。読み取るデータがありません。エラー イベントはエラーを示します。
1.1 読み取りストリーム
const fs = require('fs');
//创建读取流
let rs = fs.createReadStream('hello.txt', 'utf-8');
rs.on('open', function () {
console.log('读取的文件已打开');
}).on('close', function () {
console.log('读取流结束');
}).on('error', err => {
console.log(err);
}).on('data', function (chunk) {
//每一批数据流入完成
console.log('单批数据流入:' + chunk.length);
console.log(chunk);
});
データ イベントは複数回発生する可能性があり、毎回渡されるチャンクはストリーム データの一部であることに注意してください。
ビデオを読む
const fs = require('fs');
//创建读取流
let rs = fs.createReadStream('video.mp4');
//每一批数据流入完成
rs.on('data', function (chunk) {
console.log('单批数据流入:' + chunk.length);
console.log(chunk);
});
1.2 書き込みストリーム
ファイルにストリームとして書き込むには、write() メソッドを呼び出し続けて end() で終了します。
const fs = require('fs');
//创建写入流
let ws = fs.createWriteStream('hello.txt', 'utf-8');
//监听文件打开事件
ws.on('open', function () {
console.log('文件打开');
});
//监听文件关闭事件
ws.on('close', function () {
console.log('文件写入完成,关闭');
});
//文件流式写入
ws.write('helloworld1!', function (err) {
if (err) {
console.log(err);
} else {
console.log('内容1流入完成');
}
});
ws.write('helloworld2!', function (err) {
if (err) {
console.log(err);
} else {
console.log('内容2流入完成');
}
});
//文件写入完成
ws.end(function () {
console.log('文件写入关闭');
});
pipe
2 本の水道管を鎖でつないでより長いパイプを作ることができるのと同じように、2 つの水流も鎖でつなぐことができます。ストリームReadable
がWritable
ストリームに接続されると、すべてのデータがストリームから自動的にストリームにReadable
入ります。この操作は と呼ばれます。Writable
pipe
Node.js では、Readable
ストリームにpipe()
まさにそれを行うメソッドがあります。
ファイル ストリームを別のファイル ストリームに連結してpipe()
、ソース ファイルのすべてのデータが宛先ファイルに自動的に書き込まれるようにしましょう。これは実際にはファイルをコピーするプログラムです。
const fs = require('fs');
//创建读取流
let rs = fs.createReadStream('video.mp4');
let ws = fs.createWriteStream('b.mp4');
rs.on('close', function () {
console.log('读取流结束');
});
rs.pipe(ws);
パイプの原理
const fs = require('fs');
//创建读取流
let rs = fs.createReadStream('video.mp4');
let ws = fs.createWriteStream('b.mp4');
rs.on('close', function () {
ws.end();
console.log('读取流结束');
});
//每一批数据流入完成
rs.on('data', function (chunk) {
console.log('单批数据流入:' + chunk.length);
ws.write(chunk, () => {
console.log('单批输入流入完成');
});
});
2. リソース圧縮モジュール zib
2.1 概要
Web パフォーマンスの最適化を行った学生は、パフォーマンス最適化キラー gzip についてよく知っているはずです。ブラウザは、js ファイルのダウンロードなど、サーバーへのリソース リクエストを開始します。サーバーは、トラフィックを節約してアクセスを高速化するために、まずリソースを圧縮してからブラウザに返します。
ブラウザは HTTP リクエスト ヘッダーに Accept-Encoding を追加して、サーバーに「gzip または defalte アルゴリズムを使用してリソースを圧縮できます」と伝えます。
Accept-Encoding:gzip, deflate
では、nodejs でリソースを圧縮するにはどうすればよいでしょうか? 答えは Zlib モジュールです。=
2.2 圧縮例
非常に単純な数行のコードで、ローカル ファイルの gzip 圧縮が完了します。
var fs = require('fs');
var zlib = require('zlib');
var gzip = zlib.createGzip();
var readstream = fs.createReadStream('./extra/fileForCompress.txt');
var writestream = fs.createWriteStream('./extra/fileForCompress.txt.gz');
readstream.pipe(gzip).pipe(writestream);
2.3 解凍例
これも非常に簡単で、逆の操作です。
var fs = require('fs');
var zlib = require('zlib');
var gunzip = zlib.createGunzip();
var readstream = fs.createReadStream('./extra/fileForCompress.txt.gz');
var writestream = fs.createWriteStream('./extra/fileForCompress1.txt');
readstream.pipe(gunzip).pipe(writestream);
2.4 サーバー側の gzip 圧縮
まず、accept-encoding ヘッダーが含まれているかどうか、値が gzip であるかどうかを確認します。
- いいえ: 非圧縮ファイルを返します。
- はい: gzip 圧縮ファイルを返します。
var http = require('http');
var zlib = require('zlib');
var fs = require('fs');
var filepath = './extra/fileForGzip.html';
var server = http.createServer(function(req, res){
var acceptEncoding = req.headers['accept-encoding'];
var gzip;
if(acceptEncoding.indexOf('gzip')!=-1){
// 判断是否需要gzip压缩
gzip = zlib.createGzip();
// 记得响应 Content-Encoding,告诉浏览器:文件被 gzip 压缩过
res.writeHead(200, {
'Content-Encoding': 'gzip'
});
fs.createReadStream(filepath).pipe(gzip).pipe(res);
}else{
fs.createReadStream(filepath).pipe(res);
}
});
server.listen('3000');
jsの大きなファイルを返す
const fs = require('fs');
const zlib = require('zlib');//这两个要写在fs模块后面
const gzip = zlib.createGzip();
const http = require('http');
http
.createServer((req, res) => {
let rs = fs.createReadStream('hello.js');
res.writeHead(200, {
'Content-Type': 'application/x-javascript;charset=utf-8',
'Content-Encoding': 'gzip',
});
rs.pipe(gzip).pipe(res);
})
.listen(3000, () => {
console.log('server start');
});
2.5 サーバー文字列の gzip 圧縮
コードは前の例と似ています。ここでは、zlib.gzipSync(str) を使用して文字列を gzip で圧縮します。
var http = require('http');
var zlib = require('zlib');
var responseText = 'hello world';
var server = http.createServer(function(req, res){
var acceptEncoding = req.headers['accept-encoding'];
if(acceptEncoding.indexOf('gzip')!=-1){
res.writeHead(200, {
'content-encoding': 'gzip'
});
res.end(zlib.gzipSync(responseText) );
}else{
res.end(responseText);
}
});
server.listen('3000');
3. データ暗号化モジュールcrypto
crypto
このモジュールの目的は、共通の暗号化およびハッシュ アルゴリズムを提供することです。これらの関数を純粋な JavaScript コードで実装することは不可能ではありませんが、非常に時間がかかります。Nodejs は C/C++ を使用してこれらのアルゴリズムを実装した後、このモジュールを通じてcypto
JavaScript インターフェイスとして公開されるため、使いやすく、高速に実行できます。
3.1 ハッシュの例
hash.digest([encoding])
: 概要を計算します。、または のいずれかencoding
です。指定した場合、文字列を返します。それ以外の場合は、インスタンスを返します。呼び出し後はオブジェクトが無効化され、再度呼び出すとエラーが発生するので注意してください。hex
latin1
base64
encoding
Buffer
hash.digest()
hash
hash.update(data[, input_encoding])
: 、または のいずれかinput_encoding
です。データが文字列で指定されていない場合、デフォルトの utf8 が使用されます。メソッドは複数回呼び出すことができることに注意してください。utf8
ascii
latin1
input_encoding
hash.update()
var crypto = require('crypto');
var fs = require('fs');
var content = fs.readFileSync('./test.txt', {
encoding: 'utf8'});
var hash = crypto.createHash('sha256');
var output;
hash.update(content);
output = hash.digest('hex');
console.log(output);
// 输出内容为:
// b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
次のようにすることもできます。
var crypto = require('crypto');
var fs = require('fs');
var input = fs.createReadStream('./test.txt', {
encoding: 'utf8'});
var hash = crypto.createHash('sha256');
hash.setEncoding('hex');
input.pipe(hash).pipe(process.stdout)
// 输出内容为:
// b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
hash.digest()后,再次调用digest()或者update()
var crypto = require('crypto');
var fs = require('fs');
var content = fs.readFileSync('./test.txt', {
encoding: 'utf8'});
var hash = crypto.createHash('sha256');
var output;
hash.update(content);
hash.digest('hex');
// 报错:Error: Digest already called
hash.update(content);
// 报错:Error: Digest already called
hash.digest('hex');
3.2 HMAC の例
HMAC の正式名は Hash-based Message Authentication Code で、ハッシュでのソルト操作です。
使用方法はハッシュモジュールと似ており、ハッシュアルゴリズムを選択して「salt」を指定するだけです。
例 1:
var crypto = require('crypto');
var fs = require('fs');
var secret = 'secret';
var hmac = crypto.createHmac('sha256', secret);
var input = fs.readFileSync('./test.txt', {
encoding: 'utf8'});
hmac.update(input);
console.log( hmac.digest('hex') );
// 输出:
// 734cc62f32841568f45715aeb9f4d7891324e6d948e4c6c60c0621cdac48623a
例 2:
var crypto = require('crypto');
var fs = require('fs');
var secret = 'secret';
var hmac = crypto.createHmac('sha256', secret);
var input = fs.createReadStream('./test.txt', {
encoding: 'utf8'});
hmac.setEncoding('hex');
input.pipe(hmac).pipe(process.stdout)
// 输出:
// 734cc62f32841568f45715aeb9f4d7891324e6d948e4c6c60c0621cdac48623a
3.3 MD5 の例
MD5 (メッセージ ダイジェスト アルゴリズム) は、コンピューター セキュリティの分野で広く使用されているハッシュ関数 (ハッシュ アルゴリズム、ダイジェスト アルゴリズムとも呼ばれます) であり、主にメッセージの整合性と一貫性を保証するために使用されます。一般的なアプリケーション シナリオには、パスワード保護、ダウンロード ファイルの検証などが含まれます。
特徴
- 高速な操作速度: jquery.js が md5 値を見つけるのに 57,254 文字、所要時間は 1.907 ミリ秒
- 固定出力長: 入力長は固定されず、出力長は固定 (128 ビット) です。
- 演算は不可逆です。演算結果がわかっている場合、逆演算によって元の文字列を取得することはできません。
- 高度に離散的: 入力の小さな変化が、演算結果に大きな違いをもたらす可能性があります。
- 弱い衝突: 異なる入力が同じハッシュ値を持つ可能性があります。
アプリケーションシナリオ
- ファイル整合性検証: たとえば、インターネットからソフトウェアをダウンロードする場合、一般的な Web サイトではソフトウェアの md5 値が Web ページに添付され、ユーザーはソフトウェアをダウンロードした後、ダウンロードしたソフトウェアで md5 計算を実行できます。 Web サイト上の md5 値と比較し、比較して、ダウンロードしたソフトウェアが完全である (または正しい) ことを確認します。
- パスワード保護: データベースのドラッグなどのイベント後の平文パスワードの漏洩を避けるために、平文パスワードを保存するのではなく、md5 の後のパスワードをデータベースに保存します。
- 改ざん防止: たとえば、デジタル証明書の改ざん防止にはダイジェスト アルゴリズムが使用されます。(もちろん、デジタル署名や他の手段と組み合わせて)。
var crypto = require('crypto');
var md5 = crypto.createHash('md5');
var result = md5.update('a').digest('hex');
// 输出:0cc175b9c0f1b6a831c399e269772661
console.log(result);
3.4 例: パスワード保護
前述したように、プレーンテキストのパスワードをデータベースに保存するのは非常に安全ではなく、最悪の場合、パスワードを保存するには md5 を実行する必要があります。たとえば、ユーザー パスワードが 123456 の場合、md5 を実行すると、出力は e10adc3949ba59abbe56e057f20f883e になります。
これには少なくとも 2 つの利点があります。
- 内部攻撃対策: Web サイト所有者はユーザーの平文パスワードを知らないため、Web サイト所有者がユーザーの平文パスワードを使用して不正行為を行うことを防ぎます。
- 外部攻撃対策: Web サイトがハッキングされた場合、ハッカーはユーザーの平文パスワードではなく、md5 以降のパスワードしか取得できません。
サンプルコードは次のとおりです。
var crypto = require('crypto');
function cryptPwd(password) {
var md5 = crypto.createHash('md5');
return md5.update(password).digest('hex');
}
var password = '123456';
var cryptedPassword = cryptPwd(password);
console.log(cryptedPassword);
// 输出:e10adc3949ba59abbe56e057f20f883e
パスワードに対して md5 を実行するだけでは安全ではありません
。前述したように、ユーザー パスワードに対して md5 計算を実行することでセキュリティが向上します。しかし実際には、そのようなセキュリティは非常に貧弱です。なぜでしょうか?
上の例を少し修正してみると、理解できるかもしれません。同じ平文パスワードは同じ md5 値を持ちます。
var crypto = require('crypto');
function cryptPwd(password) {
var md5 = crypto.createHash('md5');
return md5.update(password).digest('hex');
}
var password = '123456';
console.log( cryptPwd(password) );
// 输出:e10adc3949ba59abbe56e057f20f883e
console.log( cryptPwd(password) );
// 输出:e10adc3949ba59abbe56e057f20f883e
つまり、攻撃者がアルゴリズムが md5 であり、データベースに保存されているパスワード値が であることを知っている場合、理論的には、e10adc3949ba59abbe56e057f20f883e
ユーザーの平文パスワードは であると推測できます123456
。
実際、これがレインボー テーブルのブルート フォース クラック方法です。一般的な平文パスワードの md5 値が事前に計算されて保存され、Web サイトのデータベースに保存されているパスワードと照合されると、ユーザーの平文パスワードがすぐに見つかります。(具体的な詳細についてはここでは説明しません)
では、セキュリティをさらに向上させる方法はあるのでしょうか? 答えは「パスワードにソルトを付ける」です。
パスワードにソルトを追加する
「ソルティング」という言葉は神秘的に思えますが、原理は実際には非常に単純です。つまり、パスワードの特定の位置に特定の文字列を挿入した後、変更された文字列に対して md5 操作を実行します。
例としては以下のようなものがあります。同じパスワードでも、「salt」値が異なると、md5 値の差が非常に大きくなります。パスワードにソルトを追加することで、最も基本的なブルートフォースクラッキングを防ぐことができますが、攻撃者が事前に「ソルト」の値を知らなければ、解読は非常に困難になります。
var crypto = require('crypto');
function cryptPwd(password, salt) {
// 密码“加盐”
var saltPassword = password + ':' + salt;
console.log('原始密码:%s', password);
console.log('加盐后的密码:%s', saltPassword);
// 加盐密码的md5值
var md5 = crypto.createHash('md5');
var result = md5.update(saltPassword).digest('hex');
console.log('加盐密码的md5值:%s', result);
}
cryptPwd('123456', 'abc');
// 输出:
// 原始密码:123456
// 加盐后的密码:123456:abc
// 加盐密码的md5值:51011af1892f59e74baf61f3d4389092
cryptPwd('123456', 'bcd');
// 输出:
// 原始密码:123456
// 加盐后的密码:123456:bcd
// 加盐密码的md5值:55a95bcb6bfbaef6906dbbd264ab4531