Skip to content

Hprose 中间件

小马哥 edited this page Jul 3, 2016 · 6 revisions

简介

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

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

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

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

  • 调用中间件
  • 批处理调用中间件
  • 输入输出中间件

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

执行顺序

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

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

+------------------------------------------------------------------+
|                 +-----------------batch invoke----------------+  |
|       +------+  | +-----+   +------+       +------+   +-----+ |  |
|       |invoke|  | |begin|   |invoke|  ...  |invoke|   | end | |  |
|       +------+  | +-----+   +------+       +------+   +-----+ |  |
|           ^     +---------------------------------------------+  |
|           |                            ^                         |
|           |                            |                         |
|           v                            v                         |
| +-------------------+        +------------------+                |
| | invoke middleware |        | batch middleware |                |
| +-------------------+        +------------------+                |
|           ^                            ^                         |
|           |     +---------------+      |                         |
|           +---->| encode/decode |<-----+                         |
|                 +---------------+                                |
|                         ^                                        |
|                         |                                        |
|                         v                                        |
|           +--------------------------+                           |
|           | before filter middleware |                           |
|           +--------------------------+                           |
|                         ^                                        |
|                         |       _  _ ___  ____ ____ ____ ____    |
|                         v       |__| |__] |__/ |  | [__  |___    |
|                    +--------+   |  | |    |  \ |__| ___] |___    |
|                    | filter |                                    |
|                    +--------+     ____ _    _ ____ _  _ ___      |
|                         ^         |    |    | |___ |\ |  |       |
|                         |         |___ |___ | |___ | \|  |       |
|                         v                                        |
|            +-------------------------+                           |
|            | after filter middleware |                           |
|            +-------------------------+                           |
+------------------------------------------------------------------+
                                  ^                                 
                                  |                                 
                                  |                                 
                                  v                                 
+------------------------------------------------------------------+
| _  _ ___  ____ ____ ____ ____    ____ ____ ____ _  _ ____ ____   |
| |__| |__] |__/ |  | [__  |___    [__  |___ |__/ |  | |___ |__/   |
| |  | |    |  \ |__| ___] |___    ___] |___ |  \  \/  |___ |  \   |
+------------------------------------------------------------------+

调用中间件

调用中间件的形式为:

function(name, args, context, next) {
    ...
    var result = next(name, args, context);
    ...
    return result;
}

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

args 是调用参数。

context 是调用上下文对象。

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

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

跟踪调试

我们来看一个例子:

client.js

function loghandler(name, args, context, next) {
    console.log("before invoke:", name, args);
    var result = next(name, args, context);
    result.then(function(result) {
        console.log("after invoke:", name, args, result);
    });
    return result;
}
var client = hprose.Client.create("http://www.hprose.com/example/", ['hello']);
client.use(loghandler);
client.hello("world", function(result) {
    console.log(result);
});

客户端输出

before invoke: hello ["world"]
after invoke: hello ["world"] Hello world
Hello world

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

缓存调用

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

client.js

function loghandler(name, args, context, next) {
    console.log("before invoke:", name, args);
    var result = next(name, args, context);
    result.then(function(result) {
        console.log("after invoke:", name, args, result);
    });
    return result;
}
var cache = {};
function cachehandler(name, args, context, next) {
    if (context.userdata.cache) {
        var key = JSON.stringify(args);
        if (name in cache) {
            if (key in cache[name]) {
                return cache[name][key];
            }
        }
        else {
            cache[name] = {};
        }
        var result = next(name, args, context);
        cache[name][key] = result;
        return result;
    }
    return next(name, args, context);
}
var client = hprose.Client.create("http://www.hprose.com/example/", ['hello']);
client.use(cachehandler)
      .use(loghandler);
client.hello("cache world", function(result) {
    console.log(result);
}, { userdata: { cache: true } });
client.hello("cache world", function(result) {
    console.log(result);
}, { userdata: { cache: true } });
client.hello("no cache world", function(result) {
    console.log(result);
});
client.hello("no cache world", function(result) {
    console.log(result);
});

客户端输出

before invoke: hello ["cache world"]
before invoke: hello ["no cache world"]
before invoke: hello ["no cache world"]
after invoke: hello ["no cache world"] Hello no cache world
Hello no cache world
after invoke: hello ["no cache world"] Hello no cache world
Hello no cache world
after invoke: hello ["cache world"] Hello cache world
Hello cache world
Hello cache world

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

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

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

批处理调用中间件

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

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

function(batches, context, next) {
    ...
    var result = next(batches, context);
    ...
    return result;
}

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

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

context 是批处理调用的上下文对象。

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

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

批处理跟踪调试

client.js

function batchloghandler(batches, context, next) {
    console.log("before invoke:", batches);
    var result = next(batches, context);
    result.then(function(result) {
        console.log("after invoke:", batches, result);
    });
    return result;
}
var log = hprose.Future.wrap(console.log, console);
var client = hprose.Client.create("http://www.hprose.com/example/", ['hello']);
client.batch.use(batchloghandler);
client.batch.begin();
var r1 = client.hello("world 1");
var r2 = client.hello("world 2");
var r3 = client.hello("world 3");
client.batch.end();
log(r1, r2, r3);

服务器端我们不修改,还用上面那个例子中的服务器。启动客户端之后,我们会看到客户端输出如下内容:

before invoke: [Object, Object, Object]
after invoke: [Object, Object, Object] [Object, Object, Object]
Hello world 1 Hello world 2 Hello world 3

这段输出这里就不需要解释了。

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

client.batch.use(handler1)
            .use(handler2)
            .use(handler3);

输入输出中间件

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

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

function(request, context, next) {
    ...
    var response = next(request, context);
    ...
    return response;
}

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

context 是调用上下文对象。

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

next 的返回值 response 是返回的响应数据。

对于客户端来说,它是输入数据。这个 response 必须为 promise 对象,该 promise 对象的成功值必须为 Uint8Array 类型的对象,如果是失败值,则失败原因可以是任意类型,但最好是 Error 类型(或子类型)的对象。

跟踪调试

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

client.js

function loghandler(request, context, next) {
    console.log(request);
    var response = next(request, context);
    response.then(function(data) {
         console.log(data);
    });
    return response;
}
var client = hprose.Client.create("http://www.hprose.com/example/", ['hello']);
client.beforeFilter.use(loghandler);
client.hello("world", function(result) {
    console.log(result);
});

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

客户端输出

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

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

但是我们发现,这里使用 Hprose 中间件要写的代码比起 Hprose 过滤器来要多一些。主要原因是在 Hprose 中间件中,返回的响应对象是 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 是未经过过滤器处理的。

关于 Hprose 中间件的更多用法,还可以参见 Hprose for Node.js 用户手册的相关内容。它们的实现是完全一样的。