简介

Hprose 过滤器的功能虽然比较强大,可以将 Hprose 的功能进行扩展。但是有些功能使用它仍然难以实现,比如缓存。

为此,Hprose 2.0 引入了更加强大的中间件功能。Hprose 中间件不仅可以对输入输出的数据进行操作,它还可以对调用本身的参数和结果进行操作,甚至你可以跳过中间的执行步骤,或者完全由你来接管中间数据的处理。

Hprose 中间件跟普通的 HTTP 服务器中间件有些类似,但又有所不同。

Hprose 中间件分为客户端和服务器端两类。

Hprose 客户端中间件分为三种:

  • 调用中间件
  • 批处理调用中间件
  • 输入输出中间件
    Hprose 服务器端中间件分为两种:

  • 调用中间件

  • 输入输出中间件
    即客户端比服务器端多了一个批处理调用中间件。

另外,输入输出中间件又可以细分为 beforeFilterafterFilter 两种,但它们本质上没有什么区别,只是在执行顺序上有所区别。

执行顺序

Hprose 中间件的顺序执行是按照添加的前后顺序执行的,假设添加的中间件处理器分别为:handler1, handler2handlerN,那么执行顺序就是 handler1, handler2handlerN

不同类型的 Hprose 中间件和 Hprose 其它过程的执行流程如下图所示:

  1. +------------------------------------------------------------------+
  2. | +-----------------batch invoke----------------+ |
  3. | +------+ | +-----+ +------+ +------+ +-----+ | |
  4. | |invoke| | |begin| |invoke| ... |invoke| | end | | |
  5. | +------+ | +-----+ +------+ +------+ +-----+ | |
  6. | ^ +---------------------------------------------+ |
  7. | | ^ |
  8. | | | |
  9. | v v |
  10. | +-------------------+ +------------------+ |
  11. | | invoke middleware | | batch middleware | |
  12. | +-------------------+ +------------------+ |
  13. | ^ ^ |
  14. | | +---------------+ | |
  15. | +---->| encode/decode |<-----+ |
  16. | +---------------+ |
  17. | ^ |
  18. | | |
  19. | v |
  20. | +--------------------------+ |
  21. | | before filter middleware | |
  22. | +--------------------------+ |
  23. | ^ |
  24. | | _ _ ___ ____ ____ ____ ____ |
  25. | v |__| |__] |__/ | | [__ |___ |
  26. | +--------+ | | | | \ |__| ___] |___ |
  27. | | filter | |
  28. | +--------+ ____ _ _ ____ _ _ ___ |
  29. | ^ | | | |___ |\ | | |
  30. | | |___ |___ | |___ | \| | |
  31. | v |
  32. | +-------------------------+ |
  33. | | after filter middleware | |
  34. | +-------------------------+ |
  35. +------------------------------------------------------------------+
  36. ^
  37. |
  38. |
  39. v
  40. +------------------------------------------------------------------+
  41. | +--------------------------+ |
  42. | | before filter middleware | |
  43. | +--------------------------+ |
  44. | ^ |
  45. | | _ _ ___ ____ ____ ____ ____ |
  46. | v |__| |__] |__/ | | [__ |___ |
  47. | +--------+ | | | | \ |__| ___] |___ |
  48. | | filter | |
  49. | +--------+ ____ ____ ____ _ _ ____ ____ |
  50. | ^ [__ |___ |__/ | | |___ |__/ |
  51. | | ___] |___ | \ \/ |___ | \ |
  52. | v |
  53. | +-------------------------+ |
  54. | | after filter middleware | |
  55. | +-------------------------+ |
  56. | ^ |
  57. | | |
  58. | v |
  59. | +---------------+ |
  60. | +----------->| encode/decode |<---------------------+ |
  61. | | +---------------+ | |
  62. | | | | |
  63. | | | | |
  64. | | v | |
  65. | | +---------------+ | |
  66. | | | before invoke |-------------+ | |
  67. | | +---------------+ | | |
  68. | | | | | |
  69. | | | | | |
  70. | | v v | |
  71. | | +-------------------+ +------------+ | |
  72. | | | invoke middleware |--->| send error |--+ |
  73. | | +-------------------+ +------------+ |
  74. | | | ^ |
  75. | | | | |
  76. | | v | |
  77. | | +--------------+ | |
  78. | | | after invoke |--------------+ |
  79. | | +--------------+ |
  80. | | | |
  81. | | | |
  82. | +--------------------+ |
  83. +------------------------------------------------------------------+

调用中间件

调用中间件的形式为:

  1. function(name, args, context, next) {
  2. ...
  3. var result = next(name, args, context);
  4. ...
  5. return result;
  6. }

name 是调用的远程函数/方法名。

args 是调用参数。

context 是调用上下文对象。

next 表示下一个中间件。通过调用 next 将各个中间件串联起来。

在调用 next 之前的操作在调用发生前执行,在调用 next 之后的操作在调用发生后执行,如果你不想修改返回结果,你应该将 next 的返回值作为该中间件的返回值返回。

跟踪调试

我们来看一个例子:

loghandler.js

  1. module.exports = function(name, args, context, next) {
  2. console.log("before invoke:", name, args);
  3. var result = next(name, args, context);
  4. result.then(function(result) {
  5. console.log("after invoke:", name, args, result);
  6. });
  7. return result;
  8. };

client.js

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

server.js

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

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

服务器输出


  1. before invoke: hello [ 'world' ]
  2. after invoke: hello [ 'world' ] Hello world!

客户端输出


  1. before invoke: hello [ 'world' ]
  2. after invoke: hello [ 'world' ] Hello world!
  3. Hello world!

通过上面的输出,我们会发现结果 result 是个 promise 对象。但参数值 argsloghandler 里并不包含 Promise 的值,原因是 Hprose 内部已经对参数值处理过了。这样对于中间件编写就方便了很多,只需要处理异步结果就可以了。

缓存调用

我们再来看一个实现缓存调用的例子,在这个例子中我们也使用了上面的日志中间件,用来观察我们的缓存是否真的有效。

cachehandler.js

  1. var cache = {};
  2. module.exports = function(name, args, context, next) {
  3. if (context.userdata.cache) {
  4. var key = JSON.stringify(args);
  5. if (name in cache) {
  6. if (key in cache[name]) {
  7. return cache[name][key];
  8. }
  9. }
  10. else {
  11. cache[name] = {};
  12. }
  13. var result = next(name, args, context);
  14. cache[name][key] = result;
  15. return result;
  16. }
  17. return next(name, args, context);
  18. };

client.js

  1. var hprose = require("hprose");
  2. var loghandler = require("./loghandler.js");
  3. var cachehandler = require("./cachehandler.js");
  4. var client = hprose.Client.create("http://127.0.0.1:8080/", ['hello']);
  5. client.use(cachehandler)
  6. .use(loghandler);
  7. client.hello("cache world", function(result) {
  8. console.log(result);
  9. }, { userdata: { cache: true } });
  10. client.hello("cache world", function(result) {
  11. console.log(result);
  12. }, { userdata: { cache: true } });
  13. client.hello("no cache world", function(result) {
  14. console.log(result);
  15. });
  16. client.hello("no cache world", function(result) {
  17. console.log(result);
  18. });

我们的服务器仍然使用上面例子中的服务器。在确保服务器已启动的情况下,我们运行客户端,可以看到它们分别输出以下结果:

服务器输出


  1. before invoke: hello [ 'cache world' ]
  2. after invoke: hello [ 'cache world' ] Hello cache world!
  3. before invoke: hello [ 'no cache world' ]
  4. before invoke: hello [ 'no cache world' ]
  5. after invoke: hello [ 'no cache world' ] Hello no cache world!
  6. after invoke: hello [ 'no cache world' ] Hello no cache world!

客户端输出


  1. before invoke: hello [ 'cache world' ]
  2. before invoke: hello [ 'no cache world' ]
  3. before invoke: hello [ 'no cache world' ]
  4. after invoke: hello [ 'cache world' ] Hello cache world!
  5. after invoke: hello [ 'no cache world' ] Hello no cache world!
  6. after invoke: hello [ 'no cache world' ] Hello no cache world!
  7. Hello cache world!
  8. Hello cache world!
  9. Hello no cache world!
  10. Hello no cache world!

我们看到输出结果中 'cache world' 的日志只被打印了一次,而 'no cache world' 的日志被打印了两次。这说明 'cache world' 确实被缓存了。

在这个例子中,我们用到了 userdata 设置项和 context.userdata,通过 userdata 配合 Hprose 中间件,我们就可以实现自定义选项功能了。

另外,我们在这个例子中可以看到,use 方法可以链式调用。

批处理调用中间件

上面的调用中间件对于批处理调用是不起作用的,因为批处理调用是单独处理的。

批处理调用中间件的形式为:

  1. function(batches, context, next) {
  2. ...
  3. var result = next(batches, context);
  4. ...
  5. return result;
  6. }

batches 是个数组。它的每个元素都是一个对象,该对象表示一个单独的调用,它包含有以下属性:

  • name 是调用的远程函数/方法名。
  • args 是调用的参数。
  • context 是调用的上下文对象。
  • resolve 用于返回成功结果的回调函数。
  • reject 用于返回失败结果(异常)的回调函数。
    context 是批处理调用的上下文对象。

next 表示下一个中间件。通过调用 next 将各个中间件串联起来。

在调用 next 之前的操作在批处理调用发生前执行,在调用 next 之后的操作在批处理调用发生后执行,如果你不想修改返回结果,你应该将 next 的返回值作为该中间件的返回值返回。

批处理跟踪调试

batchloghandler.js

  1. module.exports = function(batches, context, next) {
  2. console.log("before invoke:", batches);
  3. var result = next(batches, context);
  4. result.then(function(result) {
  5. console.log("after invoke:", batches, result);
  6. });
  7. return result;
  8. };

client.js

  1. var hprose = require("hprose");
  2. var batchloghandler = require("./batchloghandler.js");
  3. var log = hprose.Future.wrap(console.log, console);
  4. var client = hprose.Client.create("http://127.0.0.1:8080/", ['hello']);
  5. client.batch.use(batchloghandler);
  6. client.batch.begin();
  7. var r1 = client.hello("world 1");
  8. var r2 = client.hello("world 2");
  9. var r3 = client.hello("world 3");
  10. client.batch.end();
  11. log(r1, r2, r3);

服务器端我们不修改,还用上面那个例子中的服务器。先后启动服务器和客户端之后,我们会看到

服务器输出


  1. before invoke: hello [ 'world 1' ]
  2. before invoke: hello [ 'world 2' ]
  3. before invoke: hello [ 'world 3' ]
  4. after invoke: hello [ 'world 1' ] Hello world 1!
  5. after invoke: hello [ 'world 2' ] Hello world 2!
  6. after invoke: hello [ 'world 3' ] Hello world 3!

客户端输出


  1. before invoke: [ { args: [ 'world 1' ],
  2. name: 'hello',
  3. context:
  4. { mode: 0,
  5. byref: false,
  6. simple: false,
  7. onsuccess: undefined,
  8. onerror: undefined,
  9. useHarmonyMap: false,
  10. client: [Object],
  11. userdata: {} },
  12. resolve: [Function: bound ],
  13. reject: [Function: bound ] },
  14. { args: [ 'world 2' ],
  15. name: 'hello',
  16. context:
  17. { mode: 0,
  18. byref: false,
  19. simple: false,
  20. onsuccess: undefined,
  21. onerror: undefined,
  22. useHarmonyMap: false,
  23. client: [Object],
  24. userdata: {} },
  25. resolve: [Function: bound ],
  26. reject: [Function: bound ] },
  27. { args: [ 'world 3' ],
  28. name: 'hello',
  29. context:
  30. { mode: 0,
  31. byref: false,
  32. simple: false,
  33. onsuccess: undefined,
  34. onerror: undefined,
  35. useHarmonyMap: false,
  36. client: [Object],
  37. userdata: {} },
  38. resolve: [Function: bound ],
  39. reject: [Function: bound ] } ]
  40. after invoke: [ { args: [ 'world 1' ],
  41. name: 'hello',
  42. context:
  43. { mode: 0,
  44. byref: false,
  45. simple: false,
  46. onsuccess: undefined,
  47. onerror: undefined,
  48. useHarmonyMap: false,
  49. client: [Object],
  50. userdata: {} },
  51. resolve: [Function: bound ],
  52. reject: [Function: bound ],
  53. result: 'Hello world 1!',
  54. error: null },
  55. { args: [ 'world 2' ],
  56. name: 'hello',
  57. context:
  58. { mode: 0,
  59. byref: false,
  60. simple: false,
  61. onsuccess: undefined,
  62. onerror: undefined,
  63. useHarmonyMap: false,
  64. client: [Object],
  65. userdata: {} },
  66. resolve: [Function: bound ],
  67. reject: [Function: bound ],
  68. result: 'Hello world 2!',
  69. error: null },
  70. { args: [ 'world 3' ],
  71. name: 'hello',
  72. context:
  73. { mode: 0,
  74. byref: false,
  75. simple: false,
  76. onsuccess: undefined,
  77. onerror: undefined,
  78. useHarmonyMap: false,
  79. client: [Object],
  80. userdata: {} },
  81. resolve: [Function: bound ],
  82. reject: [Function: bound ],
  83. result: 'Hello world 3!',
  84. error: null } ] [ { args: [ 'world 1' ],
  85. name: 'hello',
  86. context:
  87. { mode: 0,
  88. byref: false,
  89. simple: false,
  90. onsuccess: undefined,
  91. onerror: undefined,
  92. useHarmonyMap: false,
  93. client: [Object],
  94. userdata: {} },
  95. resolve: [Function: bound ],
  96. reject: [Function: bound ],
  97. result: 'Hello world 1!',
  98. error: null },
  99. { args: [ 'world 2' ],
  100. name: 'hello',
  101. context:
  102. { mode: 0,
  103. byref: false,
  104. simple: false,
  105. onsuccess: undefined,
  106. onerror: undefined,
  107. useHarmonyMap: false,
  108. client: [Object],
  109. userdata: {} },
  110. resolve: [Function: bound ],
  111. reject: [Function: bound ],
  112. result: 'Hello world 2!',
  113. error: null },
  114. { args: [ 'world 3' ],
  115. name: 'hello',
  116. context:
  117. { mode: 0,
  118. byref: false,
  119. simple: false,
  120. onsuccess: undefined,
  121. onerror: undefined,
  122. useHarmonyMap: false,
  123. client: [Object],
  124. userdata: {} },
  125. resolve: [Function: bound ],
  126. reject: [Function: bound ],
  127. result: 'Hello world 3!',
  128. error: null } ]
  129. Hello world 1! Hello world 2! Hello world 3!

这段输出比较长,这里就不需要解释了。

这里有一点要注意,那就是批处理调用中间件使用:client.batch.use 方法来添加,该方法也支持链式调用,链式调用方式为:

  1. client.batch.use(handler1)
  2. .use(handler2)
  3. .use(handler3);

输入输出中间件

输入输出中间件可以完全代替 Hprose 过滤器。使用输入输出中间件还是使用 Hprose 过滤器完全看开发者喜好。

输入输出中间件的形式为:

  1. function(request, context, next) {
  2. ...
  3. var response = next(request, context);
  4. ...
  5. return response;
  6. }

request 是原始请求数据,对于客户端来说它是输出数据,对于服务器端来说,它是输入数据。该数据的类型为 Uint8Array 类型对象。

context 是调用上下文对象。

next 表示下一个中间件。通过调用 next 将各个中间件串联起来。

next 的返回值 response 是返回的响应数据。对于客户端来说,它是输入数据。对于服务器端来说,它是输出数据。这个 response 必须为 promise 对象,且 promise 对象的成功值必须为 Uint8Array 类型的对象。

跟踪调试

下面我们来看一下 Hprose 过滤器中的跟踪调试的例子在这里如何实现。

loghandler.js

  1. var hprose = require('hprose');
  2. module.exports = function(request, context, next) {
  3. console.log(hprose.BytesIO.toString(request));
  4. var response = next(request, context);
  5. response.then(function(data) {
  6. console.log(hprose.BytesIO.toString(data));
  7. });
  8. return response;
  9. };

server.js

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

client.js

  1. var hprose = require("hprose");
  2. var loghandler = require("./loghandler.js");
  3. var client = hprose.Client.create("http://127.0.0.1:8080/", ['hello']);
  4. client.beforeFilter.use(loghandler);
  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!

这个结果跟使用 Hprose 过滤器的例子的结果一模一样。

但是我们发现,这里使用 Hprose 中间件要写的代码比起 Hprose 过滤器来要多一些。主要原因是在 Hprose 中间件中,next 的返回值为 Promise 对象,需要异步处理,而 Hprose 过滤器只需要同步处理就可以了。

另外,因为这个例子中,我们没有使用过滤器功能,因此使用 beforeFilter.use 方法或者 afterFilter.use 方法添加中间件处理器效果都是一样的。

但如果我们使用了过滤器的话,那么 beforeFilter.use 添加的中间件处理器的 request 数据是未经过过滤器处理的。过滤器的处理操作在 next 的最后一环中执行。next 返回的响应 response 是经过过滤器处理的。

如果某个通过 beforeFilter.use 添加的中间件处理器跳过了 next 而直接返回了结果的话,则返回的 response 也是未经过过滤器处理的。而且如果某个 beforeFilter.use 添加的中间件处理器跳过了 next,不但过滤器不会执行,而且在它之后使用 beforeFilter.use 所添加的中间件处理器也不会执行,afterFilter.use 方法所添加的所有中间件处理器也都不会执行。

afterFilter.use 添加的处理器所收到的 request 都是经过过滤器处理以后的,但它当中使用 next 方法返回的 response 是未经过过滤器处理的。

下面,我们在来看一个结合了压缩过滤器和输入输出缓存中间件的例子。

压缩、缓存、计时

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;

上面的代码跟 Hprose 过滤器一章的压缩代码完全相同。

sizehandler.js

  1. var hprose = require('hprose');
  2. module.exports = function(message) {
  3. return function(request, context, next) {
  4. console.log(message + ' request size: ' + request.length);
  5. var response = next(request, context);
  6. response.then(function(data) {
  7. console.log(message + ' response size: ' + data.length);
  8. });
  9. return response;
  10. };
  11. };

stathandler.js

  1. module.exports = function(message) {
  2. return function(request, context, next) {
  3. var start = Date.now();
  4. var response = next(request, context);
  5. response.then(function() {
  6. var end = Date.now();
  7. console.log(message + ': It takes ' + (end - start) + ' ms.');
  8. });
  9. return response;
  10. };
  11. };

cachehandler.js

  1. var cache = {};
  2. module.exports = function(request, context, next) {
  3. if (context.userdata.cache) {
  4. if (request in cache) {
  5. return cache[request];
  6. }
  7. var response = next(request, context);
  8. cache[request] = response;
  9. return response;
  10. }
  11. return next(request, context);
  12. };

server.js

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

client.js

  1. var hprose = require("hprose");
  2. var CompressFilter = require("./CompressFilter.js");
  3. var cachehandler = require("./cachehandler.js");
  4. var sizehandler = require("./sizehandler.js");
  5. var stathandler = require("./stathandler.js");
  6. var client = hprose.Client.create("http://127.0.0.1:8080/", ['echo']);
  7. client.beforeFilter.use(cachehandler)
  8. .use(stathandler('BeforeFilter'))
  9. .use(sizehandler('Non compressed'));
  10. client.addFilter(new CompressFilter('Lzp3'));
  11. client.afterFilter.use(stathandler('AfterFilter'))
  12. .use(sizehandler('compressed'));
  13. var value = [];
  14. for (var i = 0; i < 100000; i++) {
  15. value[i] = i;
  16. }
  17. client.echo(value, function(result) {
  18. console.log(result.length);
  19. }, { userdata: { cache: true } });
  20. client.echo(value, function(result) {
  21. console.log(result.length);
  22. }, { userdata: { cache: true } });

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

服务器输出


  1. compressed request size: 127233
  2. Non compressed request size: 688893
  3. Non compressed response size: 688881
  4. AfterFilter: It takes 250 ms.
  5. compressed response size: 127217
  6. BeforeFilter: It takes 434 ms.

客户端输出


  1. Non compressed request size: 688893
  2. compressed request size: 127233
  3. compressed response size: 127217
  4. AfterFilter: It takes 537 ms.
  5. Non compressed response size: 688881
  6. BeforeFilter: It takes 726 ms.
  7. 100000
  8. 100000

我们可以看到两次的执行结果都出来了,但是中间件的输出内容只有一次。原因就是第二次执行时,cachehandler 将缓存的结果直接返回了。因此后面所有的步骤就都略过了。

通过这个例子,我们可以看出,将 Hprose 中间件和 Hprose 过滤器结合,可以实现非常强大的扩展功能。如果你有什么特殊的需求,直接使用 Hprose 无法实现的话,就考虑一下是否可以添加几个 Hprose 中间件和 Hprose 过滤器吧。

原文: https://github.com/hprose/hprose-nodejs/wiki/Hprose-%E4%B8%AD%E9%97%B4%E4%BB%B6