简介

有时候,我们可能会希望在远程过程调用中对通讯的一些细节有更多的控制,比如对传输中的数据进行加密、压缩、签名、跟踪、协议转换等等,但是又希望这些工作能够跟服务函数/方法本身可以解耦。这个时候,Hprose 过滤器就是一个不错的选择。

Hprose 过滤器是一个接口,它有两个方法:

  1. inputFilter(data, context)
  2. outputFilter(data, context)

其中 inputFilter 的作用是对输入数据进行处理,outputFilter 的作用是对输出数据进行处理。

data 参数就是输入输出数据,它是 Uint8Array 类型的。这两个方法的返回值也是 Uint8Array 类型的数据,它表示已经处理过的数据,如果你不打算对数据进行修改,你可以直接将 data 参数作为返回值返回。

context 参数是调用的上下文对象,我们在服务器和客户端的介绍中已经多次提到过它。

执行顺序

不论是客户端,还是服务器,都可以添加多个过滤器。假设我们按照添加的顺序把它们叫做 filter1, filter2, … filterN。那么它们的执行顺序是这样的。

在客户端的执行顺序

  1. +------------------- outputFilter -------------------+
  2. | +-------+ +-------+ +-------+ |
  3. | |filter1|----->|filter2|-----> ... ----->|filterN| |---------+
  4. | +-------+ +-------+ +-------+ | v
  5. +----------------------------------------------------+ +---------------+
  6. | Hprose Server |
  7. +-------------------- inputFilter -------------------+ +---------------+
  8. | +-------+ +-------+ +-------+ | |
  9. | |filter1|<-----|filter2|<----- ... <-----|filterN| |<--------+
  10. | +-------+ +-------+ +-------+ |
  11. +----------------------------------------------------+

在服务器端的执行顺序

  1. +-------------------- inputFilter -------------------+
  2. | +-------+ +-------+ +-------+ |
  3. +-------->| |filterN|-----> ... ----->|filter2|----->|filter1| |
  4. | | +-------+ +-------+ +-------+ |
  5. +---------------+ +----------------------------------------------------+
  6. | Hprose Client |
  7. +---------------+ +------------------- outputFilter -------------------+
  8. ^ | +-------+ +-------+ +-------+ |
  9. +---------| |filterN|<----- ... <-----|filter2|<-----|filter1| |
  10. | +-------+ +-------+ +-------+ |
  11. +----------------------------------------------------+

跟踪调试

有时候我们在调试过程中,可能会需要查看输入输出数据。用抓包工具抓取数据当然是一个办法,但是使用 过滤器可以更方便更直接的显示出输入输出数据。

logfilter.js

  1. var hprose = require('hprose');
  2. function log(data) {
  3. console.log(hprose.BytesIO.toString(data));
  4. return data;
  5. }
  6. module.exports = {
  7. inputFilter: log,
  8. outputFilter: log
  9. };

server.js

  1. var hprose = require("hprose");
  2. var logfilter = require("./logfilter.js");
  3. function hello(name) {
  4. return "Hello " + name + "!";
  5. }
  6. var server = hprose.Server.create("http://0.0.0.0:8080");
  7. server.addFilter(logfilter);
  8. server.add(hello);
  9. server.start();

client.js

  1. var hprose = require("hprose");
  2. var logfilter = require("./logfilter.js");
  3. var client = hprose.Client.create("http://127.0.0.1:8080/", ['hello']);
  4. client.addFilter(logfilter);
  5. client.hello("world", function(result) {
  6. console.log(result);
  7. });

然后分别启动服务器和客户端,就会看到如下输出:

服务器输出


  1. Cs5"hello"a1{s5"world"}z
  2. Rs12"Hello world!"z

客户端输出


  1. Cs5"hello"a1{s5"world"}z
  2. Rs12"Hello world!"z
  3. Hello world!

压缩传输

上面的例子,我们只使用了一个过滤器。在本例中,我们展示多个过滤器组合使用的效果。

CompressFilter.js

  1. var hprose = require('hprose');
  2. var compressjs = require('compressjs');
  3. function CompressFilter(algorithmName) {
  4. this.algorithm = compressjs[algorithmName];
  5. }
  6. CompressFilter.prototype.inputFilter = function(data) {
  7. return this.algorithm.decompressFile(data);
  8. };
  9. CompressFilter.prototype.outputFilter = function(data) {
  10. return this.algorithm.compressFile(data);
  11. };
  12. module.exports = CompressFilter;

SizeFilter.js

  1. var hprose = require('hprose');
  2. function SizeFilter(message) {
  3. this.message = message;
  4. }
  5. SizeFilter.prototype.inputFilter = function(data) {
  6. console.log(this.message + ' input size: ' + data.length);
  7. return data;
  8. };
  9. SizeFilter.prototype.outputFilter = function(data) {
  10. console.log(this.message + ' output size: ' + data.length);
  11. return data;
  12. };
  13. module.exports = SizeFilter;

server.js

  1. var hprose = require("hprose");
  2. var CompressFilter = require("./CompressFilter.js");
  3. var SizeFilter = require("./SizeFilter.js");
  4. function echo(value) {
  5. return value;
  6. }
  7. var server = hprose.Server.create("http://0.0.0.0:8080");
  8. server.addFilter(new SizeFilter('Non compressed'));
  9. server.addFilter(new CompressFilter('Lzp3'));
  10. server.addFilter(new SizeFilter('Compressed'));
  11. server.add(echo);
  12. server.start();

client.js

  1. var hprose = require("hprose");
  2. var CompressFilter = require("./CompressFilter.js");
  3. var SizeFilter = require("./SizeFilter.js");
  4. var client = hprose.Client.create("http://127.0.0.1:8080/", ['echo']);
  5. client.addFilter(new SizeFilter('Non compressed'));
  6. client.addFilter(new CompressFilter('Lzp3'));
  7. client.addFilter(new SizeFilter('Compressed'));
  8. var value = [];
  9. for (var i = 0; i < 100000; i++) {
  10. value[i] = i;
  11. }
  12. client.echo(value, function(result) {
  13. console.log(result.length);
  14. });

然后分别启动服务器和客户端,就会看到如下输出:

服务器输出


  1. Compressed input size: 127233
  2. Non compressed input size: 688893
  3. Non compressed output size: 688881
  4. Compressed output size: 127217

客户端输出


  1. Non compressed output size: 688893
  2. Compressed output size: 127233
  3. Compressed input size: 127217
  4. Non compressed input size: 688881
  5. 100000

在这个例子中,压缩我们使用了 compressjs 这个库,运行前需要自行安装一下。这个库支持的压缩算法还有很多,读者有兴趣的话,可以将这个例子中的 Lzp3 算法换成其它几个算法进行一下对比。

加密跟这个类似,这里就不再单独举加密的例子了。

运行时间统计

有时候,我们希望能够对调用执行时间做一个统计,对于客户端来说,也就是客户端调用发出前,到客户端收到调用结果的时间统计。对于服务器来说,就是收到客户端调用请求到要发出调用结果的这一段时间的统计。这个功能,通过 过滤器也可以实现。

statfilter.js

  1. var hprose = require('hprose');
  2. function stat(data, context) {
  3. if ('starttime' in context.userdata) {
  4. var t = Date.now() - context.userdata.starttime;
  5. console.log('It takes ' + t + ' ms.');
  6. }
  7. else {
  8. context.userdata.starttime = Date.now();
  9. }
  10. return data;
  11. }
  12. module.exports = {
  13. inputFilter: stat,
  14. outputFilter: stat
  15. };

server.js

  1. var hprose = require("hprose");
  2. var statfilter = require("./statfilter.js");
  3. function echo(value) {
  4. return value;
  5. }
  6. var server = hprose.Server.create("http://0.0.0.0:8080");
  7. server.addFilter(statfilter);
  8. server.add(echo);
  9. server.start();

client.js

  1. var hprose = require("hprose");
  2. var statfilter = require("./statfilter.js");
  3. var client = hprose.Client.create("http://127.0.0.1:8080/", ['echo']);
  4. client.addFilter(statfilter);
  5. var value = [];
  6. for (var i = 0; i < 100000; i++) {
  7. value[i] = i;
  8. }
  9. client.echo(value, function(result) {
  10. console.log(result.length);
  11. });

然后分别启动服务器和客户端,就会看到如下输出:

服务器输出


  1. It takes 190 ms.

客户端输出


  1. It takes 234 ms.
  2. 100000

最后让我们把这个这个运行时间统计的例子跟上面的压缩例子结合一下,可以看到更详细的时间统计。

server.js

  1. var hprose = require("hprose");
  2. var CompressFilter = require("./CompressFilter.js");
  3. var SizeFilter = require("./SizeFilter.js");
  4. var statfilter = require("./statfilter.js");
  5. function echo(value) {
  6. return value;
  7. }
  8. var server = hprose.Server.create("http://0.0.0.0:8080");
  9. server.addFilter(statfilter);
  10. server.addFilter(new SizeFilter('Non compressed'));
  11. server.addFilter(new CompressFilter('Lzp3'));
  12. server.addFilter(new SizeFilter('Compressed'));
  13. server.addFilter(statfilter);
  14. server.add(echo);
  15. server.start();

client.js

  1. var hprose = require("hprose");
  2. var CompressFilter = require("./CompressFilter.js");
  3. var SizeFilter = require("./SizeFilter.js");
  4. var statfilter = require("./statfilter.js");
  5. var client = hprose.Client.create("http://127.0.0.1:8080/", ['echo']);
  6. client.addFilter(statfilter);
  7. client.addFilter(new SizeFilter('Non compressed'));
  8. client.addFilter(new CompressFilter('Lzp3'));
  9. client.addFilter(new SizeFilter('Compressed'));
  10. client.addFilter(statfilter);
  11. var value = [];
  12. for (var i = 0; i < 100000; i++) {
  13. value[i] = i;
  14. }
  15. client.echo(value, function(result) {
  16. console.log(result.length);
  17. });

然后分别启动服务器和客户端,就会看到如下输出:

服务器输出


  1. Compressed input size: 127233
  2. Non compressed input size: 688893
  3. It takes 109 ms.
  4. It takes 279 ms.
  5. Non compressed output size: 688881
  6. Compressed output size: 127217
  7. It takes 428 ms.

客户端输出


  1. Non compressed output size: 688893
  2. Compressed output size: 127233
  3. It takes 186 ms.
  4. It takes 643 ms.
  5. Compressed input size: 127217
  6. Non compressed input size: 688881
  7. It takes 740 ms.
  8. 100000

在这里,我们可以看到客户端和服务器端分别输出了三段用时。

服务器端输出:

第一个 109 ms 是解压缩输入数据的时间。

第二个 279 ms 是第一个阶段用时 + 反序列化 + 调用 + 序列化的总时间。

第三个 428 ms 是前两个阶段用时 + 压缩输出数据的时间。

客户端输出:

第一个 186 ms 是压缩输出数据的时间。

第二个 643 ms 是第一个阶段用时 + 从客户端调用发出到服务器端返回数据的总时间。

第三个 740 ms 是前两个阶段用时 + 解压缩输入数据的时间。

协议转换

Hprose 过滤器的功能不止于此,如果你对 Hprose 协议本身有所了解的话,你还可以直接在 过滤器中对输入输出数据进行解析转换。

在 Hprose for Node.js 中已经提供了一个现成的 JSONRPC 的过滤器。使用它,你可以将 Hprose 服务器变身为 Hprose + JSONRPC 双服务器。也可以将 Hprose 客户端变身为 JSONRPC 客户端。

JSONRPC 服务器

  1. var hprose = require('hprose');
  2. function hello(name) {
  3. return 'Hello ' + name + '!';
  4. }
  5. var server = hprose.Server.create("http://0.0.0.0:8080");
  6. server.addFilter(new hprose.JSONRPCServiceFilter());
  7. server.add(hello);
  8. server.start();

实现一个 JSONRPC 服务器就这么简单,只需要添加一个 JSONRPCServiceFilter 实例对象就可以了。而且这个服务器可以同时接收 Hprose 和 JSONRPC 两种请求。

JSONRPC 客户端

  1. var hprose = require("hprose");
  2. var client = hprose.Client.create("http://127.0.0.1:8080/", ['hello']);
  3. client.filter = new hprose.JSONRPCClientFilter();
  4. client.hello("world", function(result) {
  5. console.log(result);
  6. });

客户端也是同样的简单,只需要设置客户端的 filter 属性(或者用 addFilter 方法)为一个 JSONRPCClientFilter 实例对象,Hprose 客户端就马上变身为 JSONRPC 客户端了。不过需要注意一点,跟服务器不同,添加了 JSONRPCClientFilter 的客户端,是一个纯 JSONRPC 客户端,这个客户端只能跟 JSONRPC 服务器通讯,不能再跟纯 Hprose 服务器通讯了,但是跟 Hprose + JSONRPC 的双料服务器通讯是没问题的。

Hprose 过滤器的功能很强大,除了上面这些用法之外,你还可以结合服务器事件来实现更为复杂的功能。不过这里就不再继续举例说明了。

原文: https://github.com/hprose/hprose-nodejs/wiki/Hprose-%E8%BF%87%E6%BB%A4%E5%99%A8