• Posts tagged "nodejs"

Blog Archives

图片延迟加载库Layzr

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript/Nodejs)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-layzr/

js-layzr

前言

延迟加载技术,普遍应用于现在大部分的互联网的Web应用上。通过对图像的延迟加载,等到用户浏览区域接近时才把图片进行加载,可以有效地提升网页打开的速度,进一步改进用户的体验。

Layzr.js库就可以完成这个功能,150多行代码,很小很快很方便。

目录

  1. layzr介绍
  2. layzr的基本使用
  3. Layzr的配置

1. layzr介绍

layzr.js 是一个很小、速度快、无依赖的,用于浏览器图片延迟加载的库。

我们找到Layzr.js官方的Github上面,dist目录发布的 layzr.min.js 仅有 2.2 KB。同时,发现 package.json 文件,没有任何的dependencies依赖。

用layzr.js进行图片延迟加载,是非常方便的。通过配置选项,实现最大化的加载速度。layzr.js对于滚动事件已去抖,以尽量减少对浏览器的压力,以确保最佳的渲染。

项目官方网站:http://callmecavs.github.io/layzr.js/

2. layzr的基本使用

layzr.js是在浏览器端运行的Javascript库,但是他是用于NPM管理的,还在没有bower中发布。关于NPM和Bower的介绍,请分别查看文章:快速创建基于npm的nodejs库bower解决js的依赖管理

下面我们用npm的方式,下载layzr库。

首先,创建项目目录。


~  D:\workspace\javascript> mkdir js-layzr && cd js-layzr 

新建NPM项目文件package.json。


~ D:\workspace\javascript\js-layzr> vi package.json
{
    "name": "js-layzr",
    "version": "0.0.1",
    "description": "a demo for layzr",
    "dependencies": {}
}

通过NPM下载layzr.js包


~ D:\workspace\javascript\js-layzr> npm install layzr.js --save
npm WARN package.json js-layzr@0.0.1 No repository field.
npm WARN package.json js-layzr@0.0.1 No README data
layzr.js@1.1.4 node_modules\layzr.js

layzr1

接下来,我们打开layzr.js项目提供的Demo的文件,在layzr.js/demo.html。


<!DOCTYPE html>

<!--[if IE 9]>         <html class="ie9" lang="en-US"> <![endif]-->
<!--[if gt IE 9]><!--> <html lang="en-US"> <!--<![endif]-->
  <head>
    <title></title>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <style>
      .demo-images {
        width: 300px;
        margin-right: auto;
        margin-left: auto;
        padding-top: 50vh;
      }

      .demo-image,
      iframe {
        display: block;
        margin-bottom: 100px;
      }
    </style>
  </head>
  <body>
    <div class="wrapper">

      <div class="demo-images">
        <img class="demo-image" src="http://placehold.it/300">
        <img class="demo-image" data-layzr="http://placehold.it/300">
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina" data-layzr-bg>
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">
        <iframe width="300" height="169" data-layzr="https://www.youtube.com/embed/2a01Rg2g2Z8"></iframe>
      </div>

    </div><!-- END wrapper -->

    <script src="dist/layzr.js"></script>
    <script>
      // create new instance, defaults shown below
      var layzr = new Layzr({
        selector: '[data-layzr]',
        attr: 'data-layzr',
        retinaAttr: 'data-layzr-retina',
        bgAttr: 'data-layzr-bg',
        threshold: 0,
        callback: null
      });
    </script>
  </body>
</html>

在浏览型预览效果,如下图所示。

layzr2

左边为浏览器页面,右边为Chrome的开发者工具的Elements界面,红色直线为显示和代码的对应关系。当页面向下滚动时候,图片会一张一张的加载。同时代码中,原代码为


<img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">

的部分,会被重新翻译为


<img class="demo-image" src="http://placehold.it/300">

从本Demo中,我们可以猜出layzr库应该有2个组成部分,监听页面事件 和 更新img标签的DOM元素。

3. Layzr的配置选择

在HTML中,图片是用img标签在控制的,要用到Layzr.js库,需要在img标签中增加属性。


<img src="optional/placeholder" data-layzr="normal/image" data-layzr-retina="optional/retina/image" data-layzr-bg >
  • src: 用于定义图像占位符,如果没有定义图像占位符,那么在图像载入前,可能会显示异常。
  • data-layzr: 用于显示的图像
  • data-layzr-retina: 用于图像的延迟加载,测试发现没效果。
  • data-layzr-bg: 图像用于做成背景,测试发现直接被隐藏了。

对应地,我们还需要定义Javascript的实例,用来启动layzr的事件。


var layzr = new Layzr({ 
  selector: '[data-layzr]', 
  attr: 'data-layzr', 
  retinaAttr: 'data-layzr-retina', 
  bgAttr: 'data-layzr-bg', 
  threshold: 0, 
  callback: null 
});

Javascript实例属性:

  • selector: 用于选定图像标签。
  • attr: 用于指定data-layzr的属性
  • retinaAttr: 用于指定data-layzr-retina属性
  • bgAttr: 用于指定data-layzr-bg的属性
  • threshold: 用于定义图像加载参数,通过屏幕高度来控制。
  • callback: 当加载完成,触发事件回调。

接下来,我们新建一个layzr.html的文件,用于测试 layzr 的库各种功能,会比官方提供的Demo更易于理解。

新建文件layzr.html。


<!DOCTYPE html>
<html>
<head>
    <title>Layzr Demo</title>
    <script src="node_modules/layzr.js/dist/layzr.js"></script>
    <style>
        .row {
            margin: 50px 0;
            height: 500px;
        }
        .border {
            padding: 10px;
            border-style: solid;
            border-width: 3px;
            border-color: blue;
        }
    </style>
</head>
<body>
<div class="wrapper">
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/02/marked.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/03/buffer.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/02/express4.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/03/child_process.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/03/crypto.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2014/02/architect.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr-retina="./bg2.png" data-layzr="http://blog.fens.me/wp-content/uploads/2013/08/seo-title.png">
        </div>
</div>

<script>
    var layzr = new Layzr({
        selector: '[data-layzr]',
        attr: 'data-layzr',
        retinaAttr: 'data-layzr-retina',
        threshold: 10,
        callback: function () {
            console.log(this);
            this.classList.add('border');
        }
    });
</script>

</body>
</html>

用浏览器找开页面,我们可以看到展示效果。

layzr3

当浏览器向下滑动的过程中,图片由原来黑色的占位图,会自动替换为我们需要显示的图片。

layzr4

本文测试的代码,已上传到github:https://github.com/bsspirit/js-layzr

这样,我们就实现图像的延迟加载技术。对于有大量图片的网站来说,尝试一下图像的延迟加载技术,肯定会给网站带来大幅度的性能改进的。

转载请注明出处:
http://blog.fens.me/nodejs-layzr/

打赏作者

Node.js进程通信模块child_process

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript/Nodejs)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-child-process/

child_process

前言

Node.js是一种单线程的编程模型,对Node.js的赞美和诟病的也都是因为它的单线程模型,所有的任务都在一个线程中完成(I/O等例外)。单线程模型,不仅让代码非常简洁,更是直接避免了线程调度的复杂性;同样也是因为单线程,让CPU密集型计算应用,完全不适用。

在Node.js的内核中,给了我们一种新的选择,通过child_process模块创建新进程,从而实现多核并行计算。

目录

  1. child_process介绍
  2. child_process的基本使用:spawn, exec, execFile, fork

1. child_process介绍

child_process是Node.js的一个十分重要的模块,通过它可以实现创建多进程,以利用单机的多核计算资源。虽然,Nodejs天生是单线程单进程的,但是有了child_process模块,可以在程序中直接创建子进程,并使用主进程和子进程之间实现通信,等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果。

本文仅从使用上对child_process模块进行介绍,对于深入的内容请参考官方文档,https://nodejs.org/api/child_process.html

关于Node.js线程不错的文章:https://cnodejs.org/topic/518b679763e9f8a5424406e9

2. child_process的基本使用

child_process模块,在v0.12.0版本主要包括4个异步进程函数(spawn,exec,execFile,fork)和3个同步进程函数(spawnSync,execFileSync,execSync)。以异步函数中spawn是最基本的创建子进程的函数,其他三个异步函数都是对spawn不同程度的封装。spawn只能运行指定的程序,参数需要在列表中给出,而exec可以直接运行复杂的命令。

比如要运行 du -sh /disk1 命令, 使用spawn函数需要写成spawn(‘du ‘, [‘-sh ‘, ‘/disk1’]),而使用exec函数时,可以直接写成exec(‘du -sh /disk1’)。exec是会先进行Shell语法解析,因此用exec函数可以更方便的使用复杂的Shell命令,包括管道、重定向等。下面我们就针对每个异步函数,进行测试一下。

系统环境

  • Linux Ubuntu 14.04.1 LTS 64bit
  • Nodejs:v0.13.0-pre
  • Npm:1.4.28

创建项目


~ cd /disk1/demo
~ mkdir nodejs-childprocess && cd nodejs-childprocess

2.1 spawn函数

spawn从定义来看,有3个参数。

child_process.spawn(command[, args][, options])
  • command: 只执行的命令
  • args: 参数列表,可输入多的参数
  • options: 环境变量对象

其中环境变量对象包括7个属性:

  • cwd: 子进程的当前工作目录
  • env: 环境变量键值对
  • stdio: 子进程 stdio 配置
  • customFds: 作为子进程 stdio 使用的文件标示符
  • detached: 进程组的主控制
  • uid: 用户进程的ID.
  • gid: 进程组的ID.

首先,我们运行一下,上文提到的du的命令。直接在命令行,运行的结果。


~ du -sh /disk1
582M    /disk1

新建文件spawn.js


~ vi spawn.js

var child = require('child_process');
var du = child.spawn('du', ['-sh', '/disk1']);
du.stdout.on('data', function (data) {
    console.log('stdout: ' + data);
});
du.stderr.on('data', function (data) {
    console.log('stderr: ' + data);
});
du.on('exit', function (code) {
    console.log('child process exited with code ' + code);
});

通过node运行的结果


~ node spawn.js

stdout: 582M    /disk1
child process exited with code 0

输出的结果是一样的,这样我们就可以很方便地以异步的方式调用系统命令了,spawn是不支持callback函数的,通过stream的方式发数据传给主进程,从而实现了多进程之间的数据交换。

这个功能的直接用应用场景就是“系统监控”。在Linux下,我们有很多命令行工具,可以实时监控CPU,内存,IO,网络等数据,那么用Node的child_process模块可以很容易地把这些数据采集的我们自己的应用中。

比如,我们用mpstat命令,监控用户CPU的使用情况。先看看mpstat命令直接使用的效果。


~ mpstat 1
Linux 3.13.0-32-generic (ape3)  03/20/2015      _x86_64_        (4 CPU)
11:45:56 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
11:45:57 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:45:58 AM  all   96.50    0.00    3.25    0.00    0.25    0.00    0.00    0.00    0.00    0.00
11:45:59 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:46:00 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:46:01 AM  all   96.26    0.00    3.24    0.00    0.25    0.00    0.25    0.00    0.00    0.00
11:46:02 AM  all   96.75    0.00    3.25    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:46:03 AM  all   96.51    0.00    3.24    0.00    0.25    0.00    0.00    0.00    0.00    0.00
^C
Average:     all   96.50    0.00    3.35    0.00    0.11    0.00    0.04    0.00    0.00    0.00

我们新建文件mpstat.js,读取mpstat命令的数据,然后只输出%usr的数据。


~ vi mpstat.js

var child = require('child_process');
var mpstat = child.spawn('mpstat', ['1']);

//Linux 3.13.0-32-generic (ape3)  03/20/2015      _x86_64_        (4 CPU)
//11:27:12 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
//11:27:13 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00

var line = 0;
var cols = ["time","day","CPU","%usr","%nice","%sys","%iowait","%irq","%soft","%steal","%guest","%gnice","%idle"];
mpstat.stdout.on('data', function (data) {
    var str = data.toString();
    if(line > 2) {
        var arr = str.split(/\s+/);
        console.log(arr[0]+" "+cols[3]+" "+arr[3]);
    }else{
       line++;
    }
});
mpstat.stderr.on('data', function (data) {
    console.log('stderr: ' + data);
});
mpstat.on('exit', function (code) {
    console.log('child process exited with code ' + code);
});

运行程序:


~ node mpstat.js
11:47:57 %usr 96.75
11:47:58 %usr 96.52
11:47:59 %usr 96.75
11:48:00 %usr 96.25
11:48:01 %usr 96.74
11:48:02 %usr 96.51
11:48:03 %usr 96.74
11:48:04 %usr 96.51

这样就完成系统数据的采集,我们可以把采集到的数据存储到数据库中,通过websocket等协议直接输出的浏览器前端进行数据的展示。监控系统的例子,可以参考我之前写过的一篇文章:websocket服务器监控

2.2 exec函数

exec函数是对spawn的一种友好封装,增加Shell命令解析,可以直接嵌入复杂的命令,比如,管道用法 cat spawn.js exec.js | wc。

我先试用wc命令来统计一下当前目录的文件字数,分别对应3列为 字节数、字数、行数。


~ wc *
   9   29  275 exec.js
  25   95  878 mpstat.js
  16   41  343 spawn.js
  50  165 1496 total

~ cat *.js | wc
     50     165    1496

接下来,我们使用exec函数,来使用Linux管道命令。


~ vi exec.js

var childProcess = require('child_process');

var ls = childProcess.exec('cat *.js | wc', function (error, stdout, stderr) {
   if (error) {
     console.log(error.stack);
     console.log('Error code: '+error.code);
   }
   console.log('Child Process STDOUT: '+stdout);
});

运行程序:


~ node exec.js
Child Process STDOUT:      50     165    1496

输出结果与Linux命令类似,而且用exec时,命令可以写成一个完整的字符串了,不用像spawn函数时分开写成多个参数数组的形式。最后通过一个callback函数来返回,更符合JavaScript的函数调用习惯,通常情况我们可以用exec来替换spawn的使用。

2.3 execFile函数

execFile函数会直接执行特定的程序,参数作为数组传入,不会被bash解释,因此具有较高的安全性。execFile与spawn的参数相似,也需要分别指定执行的命令和参数,但可以接受一个回调函数,与exec的回调函数相同。

我们看一个execFile函数的例子。


~ vi execFile.js

var childProcess = require('child_process');
var path = ".";
childProcess.execFile('/bin/ls', ['-l', path], function (err, result) {
    console.log(result)
});

运行程序


~ node execFile.js
total 16
-rw-r--r-- 1 root root 527 Mar 20 13:23 execFile.js
-rw-r--r-- 1 root root 275 Mar 20 13:11 exec.js
-rw-r--r-- 1 root root 878 Mar 20 11:53 mpstat.js
-rw-r--r-- 1 root root 343 Mar 20 11:11 spawn.js

那么,什么时候使用exec,什么时候使用execFile呢?

如果命令参数是由用户来输入的,对于exec函数来说是有安全性风险的,因为Shell会运行多行命令,比如’ls -l .;pwd,如逗号分隔,之后的命令也会被系统运行。但使用exeFile命令时,命令和参数分来,防止了参数注入的安全风险。

我们用程序测试一下。


~ vi execFile.js

// exec
var cmd = 'ls -l .;pwd'
var ls = childProcess.exec(cmd, function (error, stdout, stderr) {
   if (error) {
     console.log(error.stack);
     console.log('Error code: '+error.code);
   }
   console.log('Child Process STDOUT: '+stdout);
});

// execFile
var path = ".;pwd";
childProcess.execFile('/bin/ls', ['-l', path], function (err, result) {
    console.log(result)
});

运行程序


~  node execFile.js

{ [Error: Command failed: /bin/ls -l .;pwd
/bin/ls: cannot access .;pwd: No such file or directory
] killed: false, code: 2, signal: null, cmd: '/bin/ls -l .;pwd' }

Child Process STDOUT: total 16
-rw-r--r-- 1 root root 547 Mar 20 13:31 execFile.js
-rw-r--r-- 1 root root 275 Mar 20 13:11 exec.js
-rw-r--r-- 1 root root 878 Mar 20 11:53 mpstat.js
-rw-r--r-- 1 root root 343 Mar 20 11:11 spawn.js
/disk1/demo/nodejs-childprocess

从输出结果看到,exec函数被正常执行,而execFile函数,则提示参数错误。

2.4 fork

fork函数,用于在子进程中运行的模块,如 fork(‘./son.js’) 相当于 spawn(‘node’, [‘./son.js’]) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。

我们一个主进程和子进程通信的例子,主进程文件main.js和子进程文件son.js。

新建主进程文件。


~ main.js

var childProcess = require('child_process');
var n = childProcess.fork('./son.js');

n.on('message', function(m) {
  console.log('Main Listen: ', m);
});
n.send({ hello: 'son' });

新建子进程文件。


~ vi son.js

process.on('message', function(m) {
  console.log('Son Listen:', m);
});
process.send({ Hello: 'conan' });

运行程序:


~ node main.js
Main Listen:  { Hello: 'conan' }
Son Listen: { hello: 'son' }

通过main.js启动子进程son.js,通过process在两个进程之间传递数据。我们对系统进程进行检查,看看是否确实有两个进程。


~ ps -aux|grep node
root     22777  0.2  0.1 603240 13252 pts/3    Sl+  14:25   0:00 node main.js
root     22782  0.2  0.1 603540 13516 pts/3    Sl+  14:25   0:00 /usr/local/bin/node ./son.js

有2个node进程分别是node main.js和node ./son.js。

掌握了进程之间的通信,我们可以做的事情就比较多了,比如自己做一个Node.js多进程管理器,调度器之类。相对于Java的多线程管理或者进程调度来说,Node程序是如此简单明了的。我已经非常明显的感觉到了编程语言在进步!!

转载请注明出处:
http://blog.fens.me/nodejs-child-process/

打赏作者

Node.js缓冲模块Buffer

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript/Nodejs)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-buffer/

buffer

前言

Javascript是为浏览器而设计的,能很好的处理unicode编码的字符串,但对于二进制或非unicode编码的数据就显得无能为力。Node.js继承Javascript的语言特性,同时又扩展了Javascript语言,为二进制的数据处理提供了Buffer类,让Node.js可以像其他程序语言一样,能处理各种类型的数据了。

网上有很多讲Buffer的文章,大都讲的是原理,怎么使用几乎找不到,文章将重点介绍Buffer的使用。

目录

  1. Buffer介绍
  2. Buffer的基本使用
  3. Buffer的性能测试

1. Buffer介绍

在Node.js中,Buffer类是随Node内核一起发布的核心库。Buffer库为Node.js带来了一种存储原始数据的方法,可以让Nodejs处理二进制数据,每当需要在Nodejs中处理I/O操作中移动的数据时,就有可能使用Buffer库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。

Buffer 和 Javascript 字符串对象之间的转换需要显式地调用编码方法来完成。以下是几种不同的字符串编码:

  • ‘ascii’ – 仅用于 7 位 ASCII 字符。这种编码方法非常快,并且会丢弃高位数据。
  • ‘utf8’ – 多字节编码的 Unicode 字符。许多网页和其他文件格式使用 UTF-8。
  • ‘ucs2’ – 两个字节,以小尾字节序(little-endian)编码的 Unicode 字符。它只能对 BMP(基本多文种平面,U+0000 – U+FFFF) 范围内的字符编码。
  • ‘base64’ – Base64 字符串编码。
  • ‘binary’ – 一种将原始二进制数据转换成字符串的编码方式,仅使用每个字符的前 8 位。这种编码方法已经过时,应当尽可能地使用 Buffer 对象。Node 的后续版本将会删除这种编码。

Buffer官方文档:http://nodejs.org/api/buffer.html

2. Buffer的基本使用

Buffer的基本使用,主要就是API所提供的操作,主要包括3个部分 创建Buffer类、读Buffer、写Buffer。由于基本操作在官方文档中详细的使用介绍,我只是简单列举一下。

系统环境

  • Win7 64bit
  • Nodejs:v0.10.31
  • Npm:1.4.23

创建项目


~ cd D:\workspace\javascript>
~ D:\workspace\javascript>mkdir nodejs-buffer && cd nodejs-buffer

2.1 创建Buffer类

要创建一个Buffer的实例,我们要通过new Buffer来创建。新建文件buffer_new.js。


~ vi buffer_new.js

// 长度为0的Buffer实例
var a = new Buffer(0);
console.log(a);
> <Buffer >

// 长度为0的Buffer实例相同,a1,a2是一个实例
var a2 = new Buffer(0);
console.log(a2);
> <Buffer >

// 长度为10的Buffer实例
var a10 = new Buffer(10);
console.log(a10);
> <Buffer 22 37 02 00 00 00 00 04 00 00>

// 数组
var b = new Buffer(['a','b',12])
console.log(b);
> <Buffer 00 00 0c>

// 字符编码
var b2 = new Buffer('你好','utf-8');
console.log(b2);
> <Buffer e4 bd a0 e5 a5 bd>

Buffer类有5个类方法,用于Buffer类的辅助操作。

1) 编码检查,上文中提到Buffer和Javascript字符串转换时,需要显式的设置编码,那么这几种编码类型是Buffer所支持的。像中文处理只能使用utf-8编码,对于几年前常用的gbk,gb2312等编码是无法解析的。


// 支持的编码
console.log(Buffer.isEncoding('utf-8'))
console.log(Buffer.isEncoding('binary'))
console.log(Buffer.isEncoding('ascii'))
console.log(Buffer.isEncoding('ucs2'))
console.log(Buffer.isEncoding('base64'))
console.log(Buffer.isEncoding('hex'))  # 16制进
> true

//不支持的编码
console.log(Buffer.isEncoding('gbk'))
console.log(Buffer.isEncoding('gb2312'))
> false

2) Buffer检查,很多时候我们需要判断数据的类型,对应后续的操作。


// 是Buffer类
console.log(Buffer.isBuffer(new Buffer('a')))
> true

// 不是Buffer
console.log(Buffer.isBuffer('adfd'))
console.log(Buffer.isBuffer('\u00bd\u00bd'))
> false

3) 字符串的字节长度,由于字符串编码不同,所以字符串长度和字节长度有时是不一样的。比如,1个中文字符是3个字节,通过utf-8编码输出就是4个中文字符,占12个字节。


var str2 = '粉丝日志';
console.log(str2 + ": " + str2.length + " characters, " + Buffer.byteLength(str2, 'utf8') + " bytes");
> 粉丝日志: 4 characters, 12 bytes
console.log(str2 + ": " + str2.length + " characters, " + Buffer.byteLength(str2, 'ascii') + " bytes");
> 粉丝日志: 4 characters, 4 bytes

4) Buffer的连接,用于连接Buffer的数组。我们可以手动分配Buffer对象合并后的Buffer空间大小,如果Buffer空间不够了,则数据会被截断。


var b1 = new Buffer("abcd");
var b2 = new Buffer("1234");
var b3 = Buffer.concat([b1,b2],8);
console.log(b3.toString());
> abcd1234

var b4 = Buffer.concat([b1,b2],32);
console.log(b4.toString());
console.log(b4.toString('hex'));//16进制输出
> abcd1234 乱码....
> 616263643132333404000000000000000000000000000000082a330200000000

var b5 = Buffer.concat([b1,b2],4);
console.log(b5.toString());
> abcd

程序运行的截图
buffer_concat

5) Buffer的比较,用于Buffer的内容排序,按字符串的顺序。


var a1 = new Buffer('10');
var a2 = new Buffer('50');
var a3 = new Buffer('123');

// a1小于a2
console.log(Buffer.compare(a1,a2));
> -1

// a2小于a3
console.log(Buffer.compare(a2,a3));
> 1

// a1,a2,a3排序输出
console.log([a1,a2,a3].sort(Buffer.compare));
> [ <Buffer 31 30>, <Buffer 31 32 33>, <Buffer 35 30> ]

// a1,a2,a3排序输出,以utf-8的编码输出
console.log([a1,a2,a3].sort(Buffer.compare).toString());
> 10,123,50

2.2 写入Buffer

把数据写入到Buffer的操作,新建文件buffer_write.js。


~ vi buffer_write.js

//////////////////////////////
// Buffer写入
//////////////////////////////

// 创建空间大小为64字节的Buffer
var buf = new Buffer(64);

// 从开始写入Buffer,偏移0
var len1 = buf.write('从开始写入');

// 打印数据的长度,打印Buffer的0到len1位置的数据
console.log(len1 + " bytes: " + buf.toString('utf8', 0, len1));

// 重新写入Buffer,偏移0,将覆盖之前的Buffer内存
len1 = buf.write('重新写入');
console.log(len1 + " bytes: " + buf.toString('utf8', 0, len1));

// 继续写入Buffer,偏移len1,写入unicode的字符串
var len2 = buf.write('\u00bd + \u00bc = \u00be',len1);
console.log(len2 + " bytes: " + buf.toString('utf8', 0, len1+len2));

// 继续写入Buffer,偏移30
var len3 = buf.write('从第30位写入', 30);
console.log(len3 + " bytes: " + buf.toString('utf8', 0, 30+len3));

// Buffer总长度和数据
console.log(buf.length + " bytes: " + buf.toString('utf8', 0, buf.length));

// 继续写入Buffer,偏移30+len3
var len4 = buf.write('写入的数据长度超过Buffer的总长度!',30+len3);

// 超过Buffer空间的数据,没有被写入到Buffer中
console.log(buf.length + " bytes: " + buf.toString('utf8', 0, buf.length));

buffer_write

Node.js的节点的缓冲区,根据读写整数的范围,提供了不同宽度的支持,使从1到8个字节(8位、16位、32位)的整数、浮点数(float)、双精度浮点数(double)可以被访问,分别对应不同的writeXXX()函数,使用方法与buf.write()类似。


buf.write(string[, offset][, length][, encoding])
buf.writeUIntLE(value, offset, byteLength[, noAssert])
buf.writeUIntBE(value, offset, byteLength[, noAssert])
buf.writeIntLE(value, offset, byteLength[, noAssert])
buf.writeIntBE(value, offset, byteLength[, noAssert])
buf.writeUInt8(value, offset[, noAssert])
buf.writeUInt16LE(value, offset[, noAssert])
buf.writeUInt16BE(value, offset[, noAssert])
buf.writeUInt32LE(value, offset[, noAssert])
buf.writeUInt32BE(value, offset[, noAssert])
buf.writeInt8(value, offset[, noAssert])
buf.writeInt16LE(value, offset[, noAssert])
buf.writeInt16BE(value, offset[, noAssert])
buf.writeInt32LE(value, offset[, noAssert])
buf.writeInt32BE(value, offset[, noAssert])
buf.writeFloatLE(value, offset[, noAssert])
buf.writeFloatBE(value, offset[, noAssert])
buf.writeDoubleLE(value, offset[, noAssert])
buf.writeDoubleBE(value, offset[, noAssert])

另外,关于Buffer写入操作,还有一些Buffer类的原型函数可以操作。

Buffer复制函数 buf.copy(targetBuffer[, targetStart][, sourceStart][, sourceEnd])。


// 新建两个Buffer实例
var buf1 = new Buffer(26);
var buf2 = new Buffer(26);

// 分别向2个实例中写入数据
for (var i = 0 ; i < 26 ; i++) {
    buf1[i] = i + 97; // 97是ASCII的a
    buf2[i] = 50; // 50是ASCII的2
}

// 把buf1的内存复制给buf2
buf1.copy(buf2, 5, 0, 10); // 从buf2的第5个字节位置开始插入,复制buf1的从0-10字节的数据到buf2中
console.log(buf2.toString('ascii', 0, 25)); // 输入buf2的0-25字节
> 22222abcdefghij2222222222

Buffer填充函数 buf.fill(value[, offset][, end])。


// 新建Buffer实例,长度20节节
var buf = new Buffer(20);

// 向Buffer中填充数据
buf.fill("h");
console.log(buf)
> <Buffer 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68>
console.log("buf:"+buf.toString())
> buf:hhhhhhhhhhhhhhhhhhhh
// 清空Buffer中的数据
buf.fill();
console.log("buf:"+buf.toString())
> buf:

Buffer裁剪,buf.slice([start][, end])。返回一个新的缓冲区,它和旧缓冲区指向同一块内存,但是从索引 start 到 end 的位置剪裁。


var buf1 = new Buffer(26);
for (var i = 0 ; i < 26 ; i++) {
    buf1[i] = i + 97;
}

// 从剪切buf1中的0-3的位置的字节,新生成的buf2是buf1的一个切片。
var buf2 = buf1.slice(0, 3);
console.log(buf2.toString('ascii', 0, buf2.length));
> abc

// 当修改buf1时,buf2同时也会发生改变
buf1[0] = 33;
console.log(buf2.toString('ascii', 0, buf2.length));
> !bc

2.3 读取Buffer

我们把数据写入Buffer后,我们还需要把数据从Buffer中读出来,新建文件buffer_read.js。我们可以通过readXXX()函数获得对应该写入时编码的索引值,再转换原始值取出,有这种方法操作中文字符就会变得麻烦,最常用的读取Buffer的方法,其实就是toString()。


~ vi buffer_read.js

//////////////////////////////
// Buffer 读取
//////////////////////////////

var buf = new Buffer(10);
for (var i = 0 ; i < 10 ; i++) {
    buf[i] = i + 97;
}
console.log(buf.length + " bytes: " + buf.toString('utf-8'));
> 10 bytes: abcdefghij

// 读取数据
for (ii = 0; ii < buf.length; ii++) {
    var ch = buf.readUInt8(ii); // 获得ASCII索引
    console.log(ch + ":"+ String.fromCharCode(ch));
}
> 97:a
98:b
99:c
100:d
101:e
102:f
103:g
104:h
105:i
106:j

写入中文数据,以readXXX进行读取,会3个字节来表示一个中文字。


var buf = new Buffer(10);
buf.write('abcd')
buf.write('数据',4)
for (var i = 0; i < buf.length; i++) {
    console.log(buf.readUInt8(i));
}

>97
98
99
100
230  // 230,149,176 代表“数”
149
176
230  // 230,141,174 代表“据”
141
174 

如果想输出正确的中文,那么我们可以用toString(‘utf-8’)的函数来操作。


console.log("buffer :"+buf); // 默认调用了toString()的函数
> buffer :abcd数据
console.log("utf-8  :"+buf.toString('utf-8'));
> utf-8  :abcd数据
console.log("ascii  :"+buf.toString('ascii'));//有乱码,中文不能被正确解析
> ascii  :abcdf0f
.
console.log("hex    :"+buf.toString('hex')); //16进制
> hex    :61626364e695b0e68dae

对于Buffer的输出,我们用的最多的操作就是toString(),按照存入的编码进行读取。除了toString()函数,还可以用toJSON()直接Buffer解析成JSON对象。


var buf = new Buffer('test');
console.log(buf.toJSON());
> { type: 'Buffer', data: [ 116, 101, 115, 116 ] }

3. Buffer的性能测试

通过上文中对Buffer的介绍,我们已经了解了Buffer的基本使用,接下来,我们要开始做Buffer做一些测试。

3.1 8K的创建测试

每次我们创建一个新的Buffer实例时,都会检查当前Buffer的内存池是否已经满,当前内存池对于新建的Buffer实例是共享的,内存池的大小为8K。

如果新创建的Buffer实例大于8K时,就把Buffer交给SlowBuffer实例存储;如果新创建的Buffer实例小于8K,同时小于当前内存池的剩余空间,那么这个Buffer存入当前的内存池;如果Buffer实例不大0,则统一返回默认的zerobuffer实例。

下面我们创建2个Buffer实例,第一个是以4k为空间,第二个以4.001k为空间,循环创建10万次。


var num = 100*1000;
console.time("test1");
for(var i=0;i<num;i++){
    new Buffer(1024*4);
}
console.timeEnd("test1");
> test1: 132ms

console.time("test2");
for(var j=0;j<num;j++){
    new Buffer(1024*4+1);
}
console.timeEnd("test2");
> test2: 163ms

第二个以4.001k为空间的耗时多23%,这就意味着第二个,每二次循环就要重新申请一次内存池的空间。这是需要我们非常注意的。

3.2 多Buffer还是单一Buffer

当我们需要对数据进行缓存时,创建多个小的Buffer实例好,还是创建一个大的Buffer实例好?比如我们要创建1万个长度在1-2048之间不等的字符串。


var max = 2048;     //最大长度
var time = 10*1000; //循环1万次

// 根据长度创建字符串
function getString(size){
    var ret = ""
    for(var i=0;i<size;i++) ret += "a";
    return ret;
}

// 生成字符串数组,1万条记录
var arr1=[];
for(var i=0;i<time;i++){
    var size = Math.ceil(Math.random()*max)
    arr1.push(getString(size));
}
//console.log(arr1);

// 创建1万个小Buffer实例
console.time('test3');
var arr_3 = [];
for(var i=0;i<time;i++){
    arr_3.push(new Buffer(arr1[i]));
}
console.timeEnd('test3');
> test3: 217ms

// 创建一个大实例,和一个offset数组用于读取数据。
console.time('test4');
var buf = new Buffer(time*max);
var offset=0;
var arr_4=[];
for(var i=0;i<time;i++){
    arr_4[i]=offset;
    buf.write(arr1[i],offset,arr1[i].length);
    offset=offset+arr1[i].length;
}
console.timeEnd('test4');
> test4: 12ms

读取索引为2的数据。


console.log("src:[2]="+arr1[2]);
console.log("test3:[2]="+arr_3[2].toString());
console.log("test4:[2]="+buf.toString('utf-8',arr_4[2],arr_4[3]));

运行结果如图所示。
buffer_test

对于这类的需求来说,提前生成一个大的Buffer实例进行存储,要比每次生成小的Buffer实例高效的多,能提升一个数量级的计算效率。所以,理解并用好Buffer是非常重要的!!

3.3 string VS Buffer

有了Buffer我们是否需求把所有String的连接,都换成Buffer的连接?那么我们就需要测试一下,String和Buffer做字符串连接时,哪个更快一点?

下面我们进行字符串连接,循环30万次。


//测试三,Buffer VS string
var time = 300*1000;
var txt = "aaa"

var str = "";
console.time('test5')
for(var i=0;i<time;i++){
    str += txt;
}
console.timeEnd('test5')
> test5: 24ms

console.time('test6')
var buf = new Buffer(time * txt.length)
var offset = 0;
for(var i=0;i<time;i++){
    var end = offset + txt.length;
    buf.write(txt,offset,end);
    offset=end;
}
console.timeEnd('test6')
> test6: 85ms

从测试结果,我们可以明显的看到,String对字符串的连接操作,要远快于Buffer的连接操作。所以我们在保存字符串的时候,该用string还是要用string。那么只有在保存非utf-8的字符串以及二进制数据的情况,我们才用Buffer。

6. 程序代码

本文的程序代码,可以直接从Github上面下载本文项目中的源代码,按照片文章中的介绍学习buffer,下载地址:https://github.com/bsspirit/nodejs-buffer

也可以直接用github命令行来下载:


~ git clone git@github.com:bsspirit/nodejs-buffer.git   # 下载github项目
~ cd nodejs-buffer                                      # 进入下载目录

关于Node.js的底层,本人接触并不多,未能从V8(C++)的做更深入的研究,仅仅在使用层次上,写出我的总结。对于文中的错误或描述不清楚的地方,还请大牛予以指正!!

参考文章:

转载请注明出处:
http://blog.fens.me/nodejs-buffer/

打赏作者

Node.js加密算法库Crypto

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-crypto/

crypto

前言

密码技术是互联网应用的一项最基本的技术之一,主要保证了数据的安全。安全定义是多维度的,通过不可逆的hash算法可以保证登陆密码的安全;通过非对称的加密算法,可以保证数据存储的安全性;通过数字签名,可以验证数据在传输过程中是否被篡改。

我们要做互联网应用,数据安全性一个是不容忽视的问题。不然可能会造成,如CSDN的100万用户明文密码被泄露事情;携程网,100万用户个人信息泄露事情等。

Node.js的Crypto库就提供各种加密算法,可以非常方便地让我们使用密码技术,解决应用开发中的问题。

目录

  1. Crypto介绍
  2. Hash算法
  3. Hmac算法
  4. 加密和解密算法
  5. 签名和验证算法
  6. salt算法
  7. 程序代码

1. Crypto介绍

Crypto库是随Nodejs内核一起打包发布的,主要提供了加密、解密、签名、验证等功能。Crypto利用OpenSSL库来实现它的加密技术,它提供OpenSSL中的一系列哈希方法,包括hmac、cipher、decipher、签名和验证等方法的封装。

Crypto官方文档:http://nodejs.org/api/crypto.html

2. Hash算法

哈希算法,是指将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。一般用于快速查找和加密算法。

通常我们对 登陆密码,都是使用Hash算法进行加密,典型的哈希算法包括 ‘md5′,’sha’,’sha1′,’sha256′,’sha512′,’RSA-SHA’。下面我们就做一下算法的测试。

系统环境

  • Win7 64bit
  • Nodejs:v0.10.31
  • Npm:1.4.23

创建项目


~ cd D:\workspace\javascript>
~ D:\workspace\javascript>mkdir nodejs-crypto && cd nodejs-crypto

新建文件hash.js,打印出所支持的hash算法。


~ vi hash.js

var crypto = require('crypto');  # 加载crypto库
console.log(crypto.getHashes()); # 打印支持的hash算法

运行程序


~ node hash.js

[ 'DSA',
  'DSA-SHA',
  'DSA-SHA1',
  'DSA-SHA1-old',
  'RSA-MD4',
  'RSA-MD5',
  'RSA-MDC2',
  'RSA-RIPEMD160',
  'RSA-SHA',
  'RSA-SHA1',
  'RSA-SHA1-2',
  'RSA-SHA224',
  'RSA-SHA256',
  'RSA-SHA384',
  'RSA-SHA512',
  'dsaEncryption',
  'dsaWithSHA',
  'dsaWithSHA1',
  'dss1',
  'ecdsa-with-SHA1',
  'md4',
  'md4WithRSAEncryption',
  'md5',
  'md5WithRSAEncryption',
  'mdc2',
  'mdc2WithRSA',
  'ripemd',
  'ripemd160',
  'ripemd160WithRSA',
  'rmd160',
  'sha',
  'sha1',
  'sha1WithRSAEncryption',
  'sha224',
  'sha224WithRSAEncryption',
  'sha256',
  'sha256WithRSAEncryption',
  'sha384',
  'sha384WithRSAEncryption',
  'sha512',
  'sha512WithRSAEncryption',
  'shaWithRSAEncryption',
  'ssl2-md5',
  'ssl3-md5',
  'ssl3-sha1',
  'whirlpool' ]

我们看到支持Hash真是不少,究竟怎么选择适合,我也说不清楚。以我对算法的理解,我会以加密计算时间和编码长度,做选择的依据。下面就简单地比较一下几种常见算法。

编辑hash.js文件


~ vi hash.js

///////////////////////////
// Hash算法
///////////////////////////
var crypto = require('crypto')
    ,fs = require('fs');

function hashAlgorithm(algorithm){
    var s1 = new Date();

    var filename = "package.json";
    var txt = fs.ReadStream(filename);

    var shasum = crypto.createHash(algorithm);
    txt.on('data', function(d) {
        shasum.update(d);
    });

    txt.on('end', function() {
        var d = shasum.digest('hex');
        var s2 = new Date();

        console.log(algorithm+','+(s2-s1) +'ms,'+ d);
    });
}

function doHash(hashs){
    hashs.forEach(function(name){
        hashAlgorithm(name);
    })
}

//var algs = crypto.getHashes();
var algs = [ 'md5','sha','sha1','sha256','sha512','RSA-SHA','RSA-SHA1','RSA-SHA256','RSA-SHA512'];
doHash(algs);

运行程序


~ node hash.js

md5,6ms,85cd416f811574bd4bdb61b241266670
sha,18ms,b1fc6647fa4fdb4b1b394f8dc7856ac140e2fbdb
sha1,20ms,0777e65066dca985569fa892fa88e21b45dc656d
sha256,21ms,5e4aea76f93ee87f422fcbd9458edad0e33ddf256d5d93bcc47977e33cb1654c
sha512,23ms,94249ec2f83b006354774dd8f8ec81125ea9e1e00f94393d8b20bbd7678e63db53fab6af125e139f9257fd7dbb6c69474e443d059903a9cb2dded03a94c8143
RSA-SHA,24ms,b1fc6647fa4fdb4b1b394f8dc7856ac140e2fbdb
RSA-SHA1,25ms,0777e65066dca985569fa892fa88e21b45dc656d
RSA-SHA256,26ms,5e4aea76f93ee87f422fcbd9458edad0e33ddf256d5d93bcc47977e33cb1654c
RSA-SHA512,26ms,94249ec2f83b006354774dd8f8ec81125ea9e1e00f94393d8b20bbd7678e63db53fab6af125e139f9257fd7dbb6c69474e4433d059903a9cb2dded03a94c8143

输出以逗号分隔,分别是算法名、时间、密文。最常见的md5,密文长度最短,计算时间也最少;sha和sha1比较接近;sha512密文最长,计算时间也最长。

由于md5已经有了大量的字典库,对于安全级别一般的网站用sha1吧;如果安全级别要求很高,CPU配置也很牛,可以考虑用sha512。

3. Hmac算法

HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。HMAC可以有效防止一些类似md5的彩虹表等攻击,比如一些常见的密码直接MD5存入数据库的,可能被反向破解。

定义HMAC需要一个加密用散列函数(表示为H,可以是MD5或者SHA-1)和一个密钥K。我们用B来表示数据块的字节数。(以上所提到的散列函数的分割数据块字长B=64),用L来表示散列函数的输出数据字节数(MD5中L=16,SHA-1中L=20)。鉴别密钥的长度可以是小于等于数据块字长的任何正整数值。应用程序中使用的密钥长度若是比B大,则首先用使用散列函数H作用于它,然后用H输出的L长度字符串作为在HMAC中实际使用的密钥。一般情况下,推荐的最小密钥K长度是L个字节。

由于HMAC就是使用散列函数,所以我们同样选择上面的几种算法进行测试。新建文件hmac.js。


~ vi hmac.js

///////////////////////////
// Hmac算法
///////////////////////////

var crypto = require('crypto')
    ,fs = require('fs');

function hmacAlgorithm(algorithm,key){
    var s1 = new Date();

    var filename = "package.json";
    var txt = fs.ReadStream(filename);

    var shasum = crypto.createHmac(algorithm,key);
    txt.on('data', function(d) {
        shasum.update(d);
    });

    txt.on('end', function() {
        var d = shasum.digest('hex');
        var s2 = new Date();

        console.log(algorithm+','+(s2-s1) +'ms,'+ d);
    });
}

function doHmac(hashs,key){
    console.log("\nKey : %s", key);
    console.log("============================");
    hashs.forEach(function(name){
        hmacAlgorithm(name,key);
    })
}

//var algs = crypto.getHashes();
var algs = [ 'md5','sha','sha1','sha256','sha512','RSA-SHA','RSA-SHA1','RSA-SHA256','RSA-SHA512'];

// 短KEY的测试
setTimeout(function(){
    doHmac(algs,"abc");
},1)

// 长KEY的测试
setTimeout(function(){
    var key = "jifdkd;adkfaj^&fjdifefdafda,ijjifdkd;adkfaj^&fjdifefdafdaljifdkd;adkfaj^&fjdifefdafda";
    doHmac(algs,key);
},2*1000)

运行程序


~ node hmac.js
Key : abc
============================
md5,6ms,bf106a077abcfa0fffe6ec0da039545b
sha,6ms,a43a00981346ac64bb7b6fb0641b72a101fb04a5
sha1,6ms,aead69a72da77d0615a854dda1086d885807574a
sha256,7ms,98ac955cb2205ba01a6337951d0ed3fd9b68753544cf81275eced365da57fc5d
sha512,8ms,054f37e34b55a19e64a7f88fb60b1122dc0a30e9864ca28d01d61115b13c74de292ab66e85bf007e1a463a52d7c30fdff174618ef954401bc9c2c3318e762c8f
RSA-SHA,10ms,a43a00981346ac64bb7b6fb0641b72a101fb04a5
RSA-SHA1,11ms,aead69a72da77d0615a854dda1086d885807574a
RSA-SHA256,12ms,98ac955cb2205ba01a6337951d0ed3fd9b68753544cf81275eced365da57fc5d
RSA-SHA512,13ms,054f37e34b55a19e64a7f88fb60b1122dc0a30e9864ca28d01d61115b13c74de292ab66e85bf007e1a463a52d7c30fdff174618ef954401bc9c2c3318e762c8f

Key : jifdkd;adkfaj^&fjdifefdafda,ijjifdkd;adkfaj^&fjdifefdafdaljifdkd;adkfaj^&fjdifefdafda
============================
md5,5ms,164a8fee6e37bb3e40a9d5dff5c2fd66
sha,5ms,ba06f536856553c3756aa36254a63ef35e225d38
sha1,7ms,f3a89b0a5ee8a55c2bb6a861748d43e9d44dc489
sha256,7ms,f2df911f40e74b2b9bb3d53a7ca4b78d438d511e015d4b50431eaea65339380d
sha512,8ms,5b4b57386b1fcc4f1945c47788bf38c013e1cde356fc15e1f946e6bf6738b5dc52ecf17b3ddc80b2ff21f985a1a707df9357fe305e9aa143da073d2cafd794dc
RSA-SHA,11ms,ba06f536856553c3756aa36254a63ef35e225d38
RSA-SHA1,12ms,f3a89b0a5ee8a55c2bb6a861748d43e9d44dc489
RSA-SHA256,14ms,f2df911f40e74b2b9bb3d53a7ca4b78d438d511e015d4b50431eaea65339380d
RSA-SHA512,16ms,5b4b57386b1fcc4f1945c47788bf38c013e1cde356fc15e1f946e6bf6738b5dc52ecf17b3ddc80b2ff21f985a1a707df9357fe305e9aa143da073d2cafd794dc

通过比对短key和长key,在编码比较长的算法上面会有一些影响。由于Hmac有了第二参数key,所以会比单独的hash加密登陆密码,有更好的安全性上的保证。

对于网站登陆密码的设计,我们可以做成2个字段存储,用password字段存密文,passkey字段存储key,把算法直接封装到程序里面。


{
  username: 'xxxx'
  password: 'aead69a72da77d0615a854dda1086d885807574a',
  passkey:'abc'
}

就算数据库被攻击,黑客也只是拿了密文和key,密码明文并没有被泄露。并且在不知道加密算法的情况下,也很难通过彩虹表进行攻击。

4. 加密和解密算法

对于登陆密码来说,是不需要考虑解密的,通常都会用不可逆的算法,像md5,sha-1等。但是,对于有安全性要求的数据来说,我们是需要加密存储,然后解密使用的,这时需要用到可逆的加密算法。对于这种基于KEY算法,可以分为对称加密和不对称加密。

  • 对称加密算法的原理很容易理解,通信一方用KEK加密明文,另一方收到之后用同样的KEY来解密就可以得到明文。
  • 不对称加密算法,使用两把完全不同但又是完全匹配的一对Key:公钥和私钥。在使用不对称加密算法加密文件时,只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。

对于这种类型的操作,Crypto包也提供了大量的算法支持。新建文件cipher.js,打印出所支持的算法。


~ vi cipher.js

var crypto = require('crypto');
console.log(crypto.getCiphers());

运行程序


~ node cipher.js

[ 'CAST-cbc',
  'aes-128-cbc',
  'aes-128-cbc-hmac-sha1',
  'aes-128-cfb',
  'aes-128-cfb1',
  'aes-128-cfb8',
  'aes-128-ctr',
  'aes-128-ecb',
  'aes-128-gcm',
  'aes-128-ofb',
  'aes-128-xts',
  'aes-192-cbc',
  'aes-192-cfb',
  'aes-192-cfb1',
  'aes-192-cfb8',
  'aes-192-ctr',
  'aes-192-ecb',
  'aes-192-gcm',
  'aes-192-ofb',
  'aes-256-cbc',
  'aes-256-cbc-hmac-sha1',
  'aes-256-cfb',
  'aes-256-cfb1',
  'aes-256-cfb8',
  'aes-256-ctr',
  'aes-256-ecb',
  'aes-256-gcm',
  'aes-256-ofb',
  'aes-256-xts',
  'aes128',
  'aes192',
  'aes256',
  'bf',
  'bf-cbc',
  'bf-cfb',
  'bf-ecb',
  'bf-ofb',
  'blowfish',
  'camellia-128-cbc',
  'camellia-128-cfb',
  'camellia-128-cfb1',
  'camellia-128-cfb8',
  'camellia-128-ecb',
  'camellia-128-ofb',
  'camellia-192-cbc',
  'camellia-192-cfb',
  'camellia-192-cfb1',
  'camellia-192-cfb8',
  'camellia-192-ecb',
  'camellia-192-ofb',
  'camellia-256-cbc',
  'camellia-256-cfb',
  'camellia-256-cfb1',
  'camellia-256-cfb8',
  'camellia-256-ecb',
  'camellia-256-ofb',
  'camellia128',
  'camellia192',
  'camellia256',
  'cast',
  'cast-cbc',
  'cast5-cbc',
  'cast5-cfb',
  'cast5-ecb',
  'cast5-ofb',
  'des',
  'des-cbc',
  'des-cfb',
  'des-cfb1',
  'des-cfb8',
  'des-ecb',
  'des-ede',
  'des-ede-cbc',
  'des-ede-cfb',
  'des-ede-ofb',
  'des-ede3',
  'des-ede3-cbc',
  'des-ede3-cfb',
  'des-ede3-cfb1',
  'des-ede3-cfb8',
  'des-ede3-ofb',
  'des-ofb',
  'des3',
  'desx',
  'desx-cbc',
  'id-aes128-GCM',
  'id-aes192-GCM',
  'id-aes256-GCM',
  'idea',
  'idea-cbc',
  'idea-cfb',
  'idea-ecb',
  'idea-ofb',
  'rc2',
  'rc2-40-cbc',
  'rc2-64-cbc',
  'rc2-cbc',
  'rc2-cfb',
  'rc2-ecb',
  'rc2-ofb',
  'rc4',
  'rc4-40',
  'rc4-hmac-md5',
  'seed',
  'seed-cbc',
  'seed-cfb',
  'seed-ecb',
  'seed-ofb' ]

同样地,在这么一大堆的算法面前,完全不知道如何去选择。我还是以加密和解密的计算时间为参考指标,选出几个常见的算法进行测试。


///////////////////////////
// 加密解密算法
///////////////////////////
var crypto = require('crypto')
    ,fs = require('fs');

//加密
function cipher(algorithm, key, buf ,cb){
    var encrypted = "";
    var cip = crypto.createCipher(algorithm, key);
    encrypted += cip.update(buf, 'binary', 'hex');
    encrypted += cip.final('hex');
    cb(encrypted);
}

//解密
function decipher(algorithm, key, encrypted,cb){
    var decrypted = "";
    var decipher = crypto.createDecipher(algorithm, key);
    decrypted += decipher.update(encrypted, 'hex', 'binary');
    decrypted += decipher.final('binary');
    cb(decrypted);
}

function cipherDecipherFile(filename,algorithm, key){
    fs.readFile(filename, "utf-8",function (err, data) {
        if (err) throw err;
        var s1 = new Date();

        cipher(algorithm, key,data,function(encrypted){
            var s2 = new Date();
            console.log('cipher:'+algorithm+','+(s2-s1) +'ms');

            decipher(algorithm, key,encrypted,function(txt){
                var s3 = new Date();
                console.log('decipher:'+algorithm+','+(s3-s2) +'ms');
//                console.log(txt);
            });
        });
    });
}

//console.log(crypto.getCiphers());
var algs = ['blowfish','aes-256-cbc','cast','des','des3','idea','rc2','rc4','seed'];
var key = "abc";
var filename = "book.pdf";//"package.json";
algs.forEach(function(name){
    cipherDecipherFile(filename,name,key);
})

运行程序


~ node cipher.js

cipher:blowfish,46ms
decipher:blowfish,95ms
cipher:des,67ms
decipher:des,104ms
cipher:idea,54ms
decipher:idea,88ms
cipher:rc4,16ms
decipher:rc4,44ms
cipher:des3,158ms
decipher:des3,193ms
cipher:aes-256-cbc,19ms
decipher:aes-256-cbc,47ms
cipher:cast,46ms
decipher:cast,82ms
cipher:seed,64ms
decipher:seed,98ms
cipher:rc2,104ms
decipher:rc2,99ms

输出一共3列,第一列,cipher(加密),decipher(解密);第二列,是算法名称;第三列是计算时间。

在选中的这几个算法中,rc4和aes-256-cbc是表现不错的算法,加密和解密时间都比较短,加密时间:解密时间=1:3;其他的算法,总体时间相对较长,有的加密时间:解密时间=1:1。所以,怎么选算法,另外的一个标准就要看业务需求了。如果业务上,解密操作的次数远大于加密操作的次数,而且是在服务器计算,那么我们最好找到,加密时间:解密时间=N:1,N>1的算法;如果加密在服务器端,解密在客户端完成,那么aes-256-cbc算法的计算时间比例就非常适合了。

5. 签名和验证算法

我们除了对数据进行加密和解密,还需要判断数据在传输过程中,是否真实际和完整,是否被篡改了。那么就需要用到签名和验证的算法,利用不对称加密算法,通过私钥进行数字签名,公钥验证数据的真实性。

数字签名的制作和验证过程,如下图所示。

screenshot.62

下面我们用程序,来现实图中的操作流程,由于证书是我们自己制作的,不打算对外公开,没有到CA进行认证,所以下过程将不包括公钥伪造,再到CA认证的过程。

首先,我们要用openSSL命令先生成,私钥server.pem和公钥cert.pem。


# 生成私钥
~ D:\workspace\javascript\nodejs-crypto>openssl genrsa  -out server.pem 1024
Generating RSA private key, 1024 bit long modulus
..................++++++
..................++++++
e is 65537 (0x10001)

# 生成公钥
~ D:\workspace\javascript\nodejs-crypto>openssl req -key server.pem -new -x509 -out cert.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:
Email Address []:

接下来,我们利用生成私钥生成数学签名,然后再用公钥验证数据,是否被篡改。新建文件signer.js。


~ vi signer.js

///////////////////////////
// 签名验证算法
// openssl genrsa  -out server.pem 1024
// openssl req -key server.pem -new -x509 -out cert.pem
///////////////////////////

var crypto = require('crypto')
    ,fs = require('fs');

function signer(algorithm,key,data){
    var sign = crypto.createSign(algorithm);
    sign.update(data);
    sig = sign.sign(key, 'hex');
    return sig;
}

function verify(algorithm,pub,sig,data){
    var verify = crypto.createVerify(algorithm);
    verify.update(data);
    return verify.verify(pubkey, sig, 'hex')
}

var algorithm = 'RSA-SHA256';
var data = "abcdef";   //传输的数据
var privatePem = fs.readFileSync('server.pem');
var key = privatePem.toString();
var sig = signer(algorithm,key,data); //数字签名

var publicPem = fs.readFileSync('cert.pem');
var pubkey = publicPem.toString();
console.log(verify(algorithm,pubkey,sig,data));         //验证数据,通过公钥、数字签名 =》是原始数据
console.log(verify(algorithm,pubkey,sig,data+"2"));    //验证数据,通过公钥、数字签名  =》不是原始数据

运行程序


~ node signer.js
true
false

两行输出结果,第一行验证的结果是true,表示数据在传输过程中,没有被篡改;第二行验证的结果是false,表示数据在传输过程中被篡改,不是原始的数据。当然,如何保证私钥和公钥的匹配,需要CA第三方来认证,与Crypto库无关本文不再介绍。

6. salt算法

我们知道,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。

盐(Salt),在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。加盐后的散列值,可以极大的降低由于用户数据被盗而带来的密码泄漏风险,即使通过彩虹表寻找到了散列后的数值所对应的原始内容,但是由于经过了加盐,插入的字符串扰乱了真正的密码,使得获得真实密码的概率大大降低。

加盐的实现过程通常是在需要散列的字段的特定位置增加特定的字符,打乱原始的字串,使其生成的散列结果产生变化。比如,用户使用了一个密码:


123465

经过MD5散列后,可以得出结果:


3d9188577cc9bfe9291ac66b5cc872b7

但是由于用户密码位数不足,短密码的散列结果很容易被彩虹表破解,因此,在用户的密码末尾添加特定字串:


123465abcdefghijklmnopqrstuvwxyz

因此,加盐后的密码位数更长了,散列的结果也发生了变化:


27e20c64ccb8cce9ad68b8ccff6252cf

新建文件salt.js,实现上面的程序。


~ vi salt.js

//////////////////////////////
// salt算法
//////////////////////////////
var crypto = require('crypto');
var md5 = crypto.createHash('md5');
var txt = "123465";

md5.update(txt);
console.log(md5.digest('hex'));

md5 = crypto.createHash('md5');
var salt = "abcdefghijklmnopqrstuvwxyz";
md5.update(txt+salt);
console.log(md5.digest('hex'));

我们可以不用自己动手加盐,而使用crypto.pbkdf2()函数,默认会调用hmac算法,用sha1的散列函数,并且可以设置迭代次数和密文长度。具体的使用代码如下。


~ vi salt.js

var crypto = require('crypto');
var txt = "123465";
var salt = "abcdefghijklmnopqrstuvwxyz";

// 生成密文,默认HMAC函数是sha1算法
crypto.pbkdf2(txt, salt, 4096, 256, function (err,hash) {
    if (err) { throw err; }
    console.log(hash.toString('hex'));
})

运行程序,生成256位的密文。


~ node salt.js

29c7de002d942ebcbf3afe05f3eb0ff620f0515fe6b8f19176736273cd70805afdfcc828a6f227152dfa4d0c4f96da184fbd060d4d4c86a5deb8d704699c3e8653acdb0e5bc3e584e0890a44206bb2926f0289fc8e0abe49fd1876461fcc50f06dc7991c4b93cc4e80076529c73b2f2c56f16b5b319368edf017f3d3583a33aa44fd30f89801f0d8877eb8262925f5fdc40a5c57f1b275e5674784dca635c75bc58b6c22264e0f29e363eb25dedf1a242429084e3e17d344b59cab3b9723db03ee4838b632786d1a9eb968f2523404286e5d0a41a1707577650cc3cc2f1ab65714a4cb31f068e4aefa259c6be68174e0a475d5610168305a4935a14bb221a516

如果加盐每次都是固定的值也会不安全,我们还可以利用随机randomBytes()函数,配合pbkdf2()函数,让每次都是不同的salt,生成安全级别更高的密文。


~ vi salt.js

//通过伪随机码生成salt,进行加密
crypto.randomBytes(128, function (err, salt) {
    if (err) { throw err;}
    salt = salt.toString('hex');
    console.log(salt); //生成salt

    crypto.pbkdf2(txt, salt, 4096, 256, function (err,hash) {
        if (err) { throw err; }
        hash = hash.toString('hex');
        console.log(hash);//生成密文
    })
})

运行程序


~ node salt.js

# 随机生成的salt
78e59de99f16697e3eb684dcfa8efa086db0940c7cd47d33f9311e3bfcf9d58bf30915f54b3f72793b5c8568d32f1f15c55cc87affd043d96f1ed1f56c25a8054b3d83a306636f3b9e3bc9e48c3303aff54da006f92e370023165857fce0a1d1ff0b89178ae8c1416747275daba25652ea864d52a80427658ea69dbe500a7261

# 通过salt生成的密文
48943eb51ea702b436f95bf1dacc7f64bc41cf7cfa4cb40d101d5550c28caafc58ca720934352238430634f21fd5a6a4ef63fe5828c2665362e9902adc0305f93d2523fbd28521ad00947a74ff8229f63ad5796f2e12677cbed6af02b9973ee0187a69ad67e86790471d95f18d6d2c43ef904f7d17a5d8264f8236f227363a016ae2c14559c17236d540e06c5fd443af740721897f76bdbd9711c8499d7a34cae2e917f900fc364f72f9afaf301845c6e0b5c37def949b4af62336a39dbd1e829405d6189536092c7769a5d7e427b8e97419988da4e1bad49c69f25ac4e96f74a0ce3eab9e1433277568105b1dcc0cf9e1f9c91a7ed391c5825eefcd71ef5ca1

我们把salt和密文一起进行存储,保证用户密码的安全性。

7. 程序代码

本文的程序代码,可以直接从Github上面下载本文项目中的源代码,按照片文章中的介绍学习Crypto,下载地址:https://github.com/bsspirit/nodejs-crypto

也可以直接用github命令行来下载:


~ git clone git@github.com:bsspirit/nodejs-crypto.git   # 下载github项目
~ cd nodejs-crypto                                      # 进入下载目录

最后要声明一下:本人对密码技术理解不深,本文所涉及的密码技术仅仅是Crypto库在应用上的,文中观点不对或描述不清楚的地方,还请专家来指正!!

参考文章:

转载请注明出处:
http://blog.fens.me/nodejs-crypto/

打赏作者

Marked高效的Markdown解析器

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-markdown-marked/

marked

前言

Markdown以一种轻巧简明的设计理念,赋予了Web文档新的活力,从而代替传统以Word为主导的电子文档。Markdown大量简化了HTML标签,被广大的互联网应用所使用。程序员所熟知的Github就完全基于Markdown语法,真的可以不再需要Word了。

Markdown不是HTML,目前还不能被浏览器解析,所以我们需要Markdown的解析器,把Markdown翻译成浏览器认识的HTML文档展示出来。Marked就是一个基于Nodejs的Markdown解析引擎!

目录

  1. markdown介绍
  2. marked安装
  3. marked命令行使用
  4. marked的API使用
  5. marked的个性化渲染

1. markdown介绍

Markdown 是一种轻量级标记语言,创始人为约翰·格鲁伯(John Gruber)。它允许人们“使用易读易写的纯文本格式编写文档,然后转换成有效的XHTML(或者HTML)文档”。这种语言吸收了很多在电子邮件中已有的纯文本标记的特性。

Markdown 的目标是实现「易读易写」。

可读性,无论如何,都是最重要的。一份使用 Markdown 格式撰写的文件应该可以直接以纯文本发布,并且看起来不会像是由许多标签或是格式指令所构成。Markdown 语法受到一些既有 text-to-HTML 格式的影响,包括 Setext、atx、Textile、reStructuredText、Grutatext 和 EtText,而最大灵感来源其实是纯文本电子邮件的格式。

Markdown的语法简洁明了、学习容易,而且功能比纯文本更强,因此有很多人用它写博客。世界上最流行的博客平台WordPress和大型CMS如joomla、drupal都能很好的支持Markdown,Github也是支持markdown的,在个性化语法上也做了不少的改进。Markdown的中文语法介绍:http://wowubuntu.com/markdown/

2款不错的markdown的在线编辑器:

如果你还没有开始用Markdown来写文档,那你一定OUT了。

2. marked安装

系统环境

  • Win7 64bit
  • Nodejs:v0.10.31
  • Npm:1.4.23

创建项目


~ cd D:\workspace\javascript>
~ D:\workspace\javascript>mkdir nodejs-marked && cd nodejs-marked

新建项目文件package.json


~ D:\workspace\javascript\nodejs-marked>notepad package.json
{
  "name": "marked-demo",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
  }
}

安装marked库,全局安装,同时在本地项目安装。


~ D:\workspace\javascript\nodejs-marked>npm install marked --save -g
C:\Users\Administrator\AppData\Roaming\npm\marked -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\marked\bin\
marked
marked@0.3.3 C:\Users\Administrator\AppData\Roaming\npm\node_modules\marked

通过-save参数,package.json文件中会自动增加marked库的依赖。


~ D:\workspace\javascript\nodejs-marked>cat package.json
{
  "name": "marked-demo",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "marked": "^0.3.3"
  }
}

通过-g参数,完成了marked库的全局安装,这时我们就可以直接在命令,使用marked的命令了。

查看命令行帮助,直接就报错误了,真是出师不利啊。


~ D:\workspace\javascript\nodejs-marked>marked -h
child_process: customFds option is deprecated, use stdio instead.
events.js:85
      throw er; // Unhandled 'error' event
            ^
Error: spawn man ENOENT
    at exports._errnoException (util.js:746:11)
    at Process.ChildProcess._handle.onexit (child_process.js:1046:32)
    at child_process.js:1137:20
    at process._tickCallback (node.js:355:11)
    at Function.Module.runMain (module.js:503:11)
    at startup (node.js:129:16)
    at node.js:814:3

定位错误,找到./node_modules/marked/bin/marked文件中help()函数,如下代码所示,这种写法不适用于Window系统,所以才出现-h报错的情况。庆幸的是,其他的功能命令参数都是正确的。


function help() {
  var spawn = require('child_process').spawn;

  var options = {
    cwd: process.cwd(),
    env: process.env,
    setsid: false,
    customFds: [0, 1, 2]
  };

  spawn('man',
    [__dirname + '/../man/marked.1'],
    options);
}

在Linux环境,我们看一下帮助命令。


~ marked -h

marked(1)                               marked.js                               marked(1)

NAME
       marked - a javascript markdown parser

SYNOPSIS
       marked  [-o  ]  [-i  <input>]  [--help]  [--tokens]  [--pedantic]  [--gfm]
       [--breaks]  [--tables]  [--sanitize]  [--smart-lists]   [--lang-prefix   <prefix>]
       [--no-etc...] [--silent] [filename]

DESCRIPTION
       marked  is  a  full-featured  javascript markdown parser, built for speed. It also
       includes multiple GFM features.

EXAMPLES
       cat in.md | marked > out.html
       echo "hello *world*" | marked
       marked -o out.html in.md --gfm
       marked --output="hello world.html" -i in.md --no-breaks

OPTIONS
       -o, --output [output]
              Specify file output. If none is specified, write to stdout.
       -i, --input [input]
              Specify file input, otherwise use last argument as input file. If no  input
              file is specified, read from stdin.
       -t, --tokens
              Output a token stream instead of html.
       --pedantic
              Conform  to  obscure  parts  of  markdown.pl as much as possible. Don't fix
              original markdown bugs.
       --gfm  Enable github flavored markdown.
       --breaks
              Enable GFM line breaks. Only works with the gfm option.
       --tables
              Enable GFM tables. Only works with the gfm option.
       --sanitize
              Sanitize output. Ignore any HTML input.
       --smart-lists
              Use smarter list behavior than the original markdown.
       --lang-prefix [prefix]
              Set the prefix for code block classes.
       --no-sanitize, -no-etc...
              The inverse of any of the marked options above.
       --silent
              Silence error output.
       -h, --help
              Display help information.

CONFIGURATION
       For configuring and running programmatically.
       Example
           require('marked')('*foo*', { gfm: true });
BUGS
       Please report any bugs to https://github.com/chjj/marked.
LICENSE
       Copyright (c) 2011-2014, Christopher Jeffrey (MIT License).
SEE ALSO
       markdown(1), node.js(1)

3. marked命令行使用

参数说明:

  • -o, –output [output]: 指定输出文件,默认为当前控制台
  • -i, –input [input]: 指定输入文件或最后一个参数,默认为当前控制台输入
  • -t, –tokens: 输出token流代替HTML
  • –pedantic: 只解析符合markdown.pl定义的,不修正markdown的错误
  • –gfm: 启动Github样式的Markdown,请参考 https://help.github.com/articles/github-flavored-markdown/
  • –breaks: 支持Github换行符,必须打开gfm选项
  • –tables: 支持Github表格,必须打开gfm选项
  • –sanitize: 原始输出,忽略HTML标签
  • –smart-lists: 优化列表输出
  • –lang-prefix [prefix]: 设置前置样式
  • –no-etc: 选择的反正标识
  • –silent: 不输出错误信息
  • -h, –help: 帮助信息

下面我们就直接使用命令行来处理markdown的文件。

新建一个Markdown文件m1.md,其中包括了文字,代码,段落,列表,格式。


Marked Demo
======================

这是一个Marked库使用的例子。 http://blog.fens.me/nodejs-markdown-marked/

> A full-featured markdown parser and compiler, written in JavaScript. Built
> for speed.

[![NPM version](https://badge.fury.io/js/marked.png)][badge]

## Install

``` bash
npm install marked --save
```

## 列表测试

+ 列表测试,行1
+ 列表测试,行2
+ 列表测试,行3
+ 列表测试,行4

## 表格测试

A | B | C
--|--|--
A1 | B1 | C1
A2 | B2 | C2
A3 | B3 | C3

使用命令行生成HTML。


D:\workspace\javascript\nodejs-marked>marked -i m1.md
<h1 id="marked-demo">Marked Demo</h1>
<p>这是一个Marked库使用的例子。 <a href="http://blog.fens.me/nodejs-markdown-marked/">http://blog.fens.me/nodejs-man-marked/</a></p>
<blockquote>
<p>A full-featured markdown parser and compiler, written in JavaScript. Built
for speed.</p>
</blockquote>
<p>[<img src="https://badge.fury.io/js/marked.png" alt="NPM version">][badge]</p<
<h2 id="install">Install</h2>
<pre><code class="lang-bash">npm install marked --save
</code></pre>
<h2 id="-">列表测试</h2>
<ul>
<li>列表测试,行1</li>
<li>列表测试,行2</li>
<li>列表测试,行3</li>
<li>列表测试,行4</li>
</ul>
<h2 id="-">表格测试</h2>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
<tr>
<td>A1</td>
<td>B1</td>
<td>C1</td>
</tr>
<tr>
<td>A2</td>
<td>B2</td>
<td>C2</td>
</tr>
<tr>
<td>A3</td>
<td>B3</td>
<td>C3</td>
</tr>
</tbody>
</table>

从代码上面看,所有的标签都被正确解析了。再把命令行的各种参数组合一下,看看是不是会有不一样的效果。

增加-o的参数,输出到指定文件中。


~ D:\workspace\javascript\nodejs-marked>marked -i m1.md -o m1_out.html

~ D:\workspace\javascript\nodejs-marked>ls m1*
m1.md  m1_out.html

增加-t的参数,以token形式输出。


~ D:\workspace\javascript\nodejs-marked>marked -t -i m1.md
[
  {
    "type": "heading",
    "depth": 1,
    "text": "Marked Demo"
  },
  {
    "type": "paragraph",
    "text": "这是一个Marked库使用的例子。"
  },
  {
    "type": "blockquote_start"
  },
  {
    "type": "paragraph",
    "text": "A full-featured markdown parser and compiler, written in JavaScript. Built\nfor speed."
  },

//省略输出
]

4. marked的API使用

接下来,我通过API来调用marked解析markdown文本。

新建一个脚本文件mark1.js


~ D:\workspace\javascript\nodejs-marked>notepad mark1.js

var marked = require('marked');
console.log(marked('I am using __markdown__.'));

运行脚本mark1.js。


~ D:\workspace\javascript\nodejs-marked>node mark1.js
<p>I am using <strong>markdown</strong>.</p>

下面再用API读取md1.md的文本,进行解析,然后输出为HTML,新建mark2.js文件。


var marked = require('marked')
    ,fs = require("fs")
    ,http = require('http');

marked.setOptions({
    renderer: new marked.Renderer(),
    gfm: true,
    tables: true,
    breaks: true,
    pedantic: false,
    sanitize: true,
    smartLists: true,
    smartypants: false
});

fs.readFile('m1.md', 'utf-8', function (err, data) {
    if (err) throw err;
    server(marked(data));
});

function server(html){
    http.createServer(function (req, res) {
        res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});
        res.end(html);
    }).listen(1337, '127.0.0.1');
    console.log('Server running at http://127.0.0.1:1337/');
}

启动服务


~ D:\workspace\javascript\nodejs-marked>node mark2.js
Server running at http://127.0.0.1:1337/

打开浏览器,http://localhost:1337/,m1.md的markdown文本被正确解析为了HTML的网页,链接,文本,段落,表格,列表都被正确显示,我们只需要自定义好样式表就行了。

marked_1

5. marked的个性化渲染使用

接下来,我们引入bootstrap的样式,修饰一下页面。

修改mark2.js文件的代码。


fs.readFile('m1.md', 'utf-8', function (err, data) {

    //增加HTML的页头
    var header = '<!DOCTYPE html>'+
        '<html lang="zh-CN">'+
        '<head>'+
        '<title>Marked Demo</title>'+
        '<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.2/css/bootstrap.min.css">'+
        '</head><body>';

    //增加HTML的页底
    var footer = '<script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script>'+
        '<script src="http://cdn.bootcss.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>'+
        '</body></html>';

    if (err) throw err;
    server(header+marked(data)+footer);
});

渲染页面后,已经好看多了。

marked_2

但是,美中不足的是表格table,没有样式化。检查bootstrap的表格样式,需要在table的class属性增加table table-striped的样式。


<table class="table table-striped">
  ...
</table>

我们在试用marked渲染markdown的过程是自动完成的,现在我们要增加css就需要个性化的渲染,通过API改变渲染的过程。

在代码中增加table的处理,完整的代码如下所示。


var marked = require('marked')
    , fs = require("fs")
    ,http = require('http');

marked.setOptions({
    renderer: new marked.Renderer(),
    gfm: true,
    tables: true,
    breaks: true,
    pedantic: false,
    sanitize: true,
    smartLists: true,
    smartypants: false
});

//增加的代码,用于个性化输出table
var renderer = new marked.Renderer();
renderer.table = function (header, body) {
    return '<table class="table table-striped">'+header+body+'</table>'
}

fs.readFile('m1.md', 'utf-8', function (err, data) {
    var header = '<!DOCTYPE html>'+
        '<html lang="zh-CN">'+
        '<head>'+
        '<title>Marked Demo</title>'+
        '<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.2/css/bootstrap.min.css">'+
        '</head><body>';

    var footer = '<script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script>'+
        '<script src="http://cdn.bootcss.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>'+
        '</body></html>';

    if (err) throw err;
    var body = marked(data,{renderer: renderer});
    server(header+body+footer);
});

function server(html){
    http.createServer(function (req, res) {
        res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});
        res.end(html);
    }).listen(1337, '127.0.0.1');
    console.log('Server running at http://127.0.0.1:1337/');
}

再次刷新网页,表格也被友好的显示了。
marked_3

这样,我们就完成了对markdown的文档的解析,也可以个性化地设计我们需要的处理样式了。最后,就是把marked库集成到应用中就可以了,其实真的很简单。

转载请注明出处:
http://blog.fens.me/nodejs-markdown-marked/

打赏作者