Skip to content

Hprose 服务器

小马哥 edited this page Mar 23, 2018 · 44 revisions

概述

Hprose 2.0 for Node.js 支持多种底层网络协议绑定的服务器,比如:HTTP 服务器,Socket 服务器和 WebSocket 服务器。

其中 HTTP 服务器支持在 HTTP、HTTPS 协议上通讯。

Socket 服务器支持在 TCP、Unix Socket 协议上通讯,并且支持全双工和半双工两种模式。

WebSocket 服务器支持在 ws、wss 协议上通讯。

尽管支持这么多不同的底层网络协议,但除了在对涉及到底层网络协议的参数设置上有所不同以外,其它的用法都完全相同。因此,我们在下面介绍 Hprose 服务器的功能时,若未涉及到底层网络协议的区别,就以 HTTP 服务器为例来进行说明。

Server 与 Service 的区别

Hprose 的服务器端的实现,分为 ServiceServer 两部分。

其中 Service 部分是核心功能,包括接收请求,处理请求,服务调用,返回应答等整个服务的处理流程。

Server 则主要负责启动和关闭服务器,它包括设置服务地址和端口,设置服务器启动选项,启动服务器,接收来自客户端的连接然后传给 Service 进行处理。

之所以分开,是为了更方便的跟已有的库和框架结合,例如:connect、express 等,这些库和框架都提供了丰富的中间件,在这种情况下,只需要把 Service 作为这些库和框架的中间件来使用就可以了,在这种情况下,我们就不需要使用 Server 了。

分开的另外一个理由是,Server 部分的实现是很简单的,有时候开发者可能会希望把 hprose 服务结合到自己的某个服务器中去,而不是作为一个单独的服务器来运行,在这种情况下,也是直接使用 Service 就可以了。

当开发者没有什么特殊需求,只是希望启动一个独立的 hprose 服务器时,那使用 Server 就是一个最方便的选择了。

创建服务器

创建服务器有多种方式,我们先从最简单的方式说起。

使用 Server 构造器函数

创建 HTTP 服务器

var hprose = require("hprose");
function hello(name) {
    return "Hello " + name + "!";
}
var server = new hprose.Server("http://0.0.0.0:8080");
server.add(hello);
server.start();

创建 HTTPS 服务器

var hprose = require("hprose");
function hello(name) {
    return "Hello " + name + "!";
}
var options = {
    key: fs.readFileSync('server-key.pem'),
    cert: fs.readFileSync('server-cert.pem')
};
var server = new hprose.Server("https://0.0.0.0:8080", options);
server.add(hello);
server.start();

创建 TCP 服务器

var hprose = require("hprose");
function hello(name) {
    return "Hello " + name + "!";
}
var server = new hprose.Server("tcp://0.0.0.0:8080");
server.add(hello);
server.start();

创建 TLS 服务器

var hprose = require("hprose");
function hello(name) {
    return "Hello " + name + "!";
}
var options = {
    key: fs.readFileSync('server-key.pem'),
    cert: fs.readFileSync('server-cert.pem'),
    requestCert: true,
    ca: [ fs.readFileSync('client-cert.pem') ]
};
var server = new hprose.Server("tls://0.0.0.0:8080", options);
server.add(hello);
server.start();

创建 UNIX Socket 服务器

var hprose = require("hprose");
function hello(name) {
    return "Hello " + name + "!";
}
var server = new hprose.Server("unix:/tmp/my.sock");
server.add(hello);
server.start();

创建 WebSocket 服务器

var hprose = require("hprose");
function hello(name) {
    return "Hello " + name + "!";
}
var server = new hprose.Server("ws://0.0.0.0:8080");
server.add(hello);
server.start();

创建安全的 WebSocket 服务器

var hprose = require("hprose");
function hello(name) {
    return "Hello " + name + "!";
}
var options = {
    key: fs.readFileSync('server-key.pem'),
    cert: fs.readFileSync('server-cert.pem')
};
var server = new hprose.Server("wss://0.0.0.0:8080", options);
server.add(hello);
server.start();

使用 Server.create 方法

该方法与 Server 构造器函数的参数完全一致,功能也一样。这里只举一例:

var hprose = require("hprose");
function hello(name) {
    return "Hello " + name + "!";
}
var options = {
    key: fs.readFileSync('server-key.pem'),
    cert: fs.readFileSync('server-cert.pem')
};
var server = hprose.Server.create("https://0.0.0.0:8080", options);
server.add(hello);
server.start();

使用 HttpService 跟 connect 结合

var hprose = require("hprose");
var connect = require('connect');
function hello(name) {
    return "Hello " + name + "!";
}
var service = new hprose.HttpService();
service.add(hello);
var app = connect()
   .use(service.handle)
   .listen(8080);

使用 HttpService 跟 express 结合

var hprose = require("hprose");
var express = require('express');
function hello(name) {
    return "Hello " + name + "!";
}
var service = new hprose.HttpService();
service.add(hello);
var app = express()
   .use(service.handle)
   .listen(8080);

使用 HttpService 跟 koa 结合

const hprose = require("hprose");
const Koa = require('koa');

function hello(name) {
    return "Hello " + name + "!";
}
const service = new hprose.HttpService();
service.add(hello);

const app = new Koa();
app.use(async ctx => {
    await service.handle(ctx.req, ctx.res);
});
app.listen(8080);

其它方式

另外,HttpServerSocketServerWebSocketServer 都有单独的构造器函数,但是参数跟 Server 的构造器参数不太一样,相对来说,这些构造器函数的参数更接近底层,所以我们通常不需要直接使用这些构造器。

SocketServiceWebSocketService 也可以直接使用它们的构造器函数创建服务对象,然后跟其它库或框架结合使用。这里就不再一一举例。

启动关闭服务

启动服务可以使用以下两个方法:

server.start();
server.listen(...);

start 方法不需要传入参数,它会以默认设置启动服务,这个方法是最常用的。

listen 方法启动服务是需要自己传入参数的,它的参数跟 Node.js 的 http.Server.listenhttps.Server.listennet.Server.listenlesten 方法的参数相同。通常不需要使用该方法。

关闭服务器也提供了两个方法:

server.stop();
server.close(callback);

stop 方法也不需要传入参数。

close 方法的参数 callback 跟 Node.js 的各种服务的 close 方法的 callback 参数相同。通常也不需要使用该方法。

服务设置

hprose.Service 是所有服务的基类。在它上面提供了以下设置:

debug 属性

该属性为 Boolean 类型,默认值为 false。

用来设置服务器是否是工作在 debug 模式下,在该模式下,当服务器端发生异常时,将会将详细的错误堆栈信息返回给客户端,否则,只返回错误信息。

simple 属性

该属性为 Boolean 类型,默认值为 false。

该属性表示调用所返回的结果是否为简单数据。简单数据是指:null、数字(包括整数、长整数、浮点数)、Boolean 值、字符串、二进制数据、日期时间等基本类型的数据或者不包含引用的数组、Map 和对象。当该属性设置为 true 时,在进行序列化操作时,将忽略引用处理,加快序列化速度。但如果数据不是简单类型的情况下,将该属性设置为 true,可能会因为死循环导致堆栈溢出的错误。

简单的讲,用 JSON 可以表示的数据都是简单数据。但是对于比较复杂的 JSON 数据,设置 simple 为 true 可能不会加快速度,反而会减慢,比如对象数组。因为默认情况下,hprose 会对对象数组中的重复字符串的键值进行引用处理,这种引用处理可以对序列化起到优化作用。而关闭引用处理,也就关闭了这种优化。

你也可以针对某个服务函数/方法进行单独设置。

因为不同调用的数据可能差别很大,因此,建议不要修改默认设置,而是针对某个服务函数/方法进行单独设置。

passContext 属性

该属性为 Boolean 类型,默认值为 false。

该属性表示在调用中是否将 context 自动作为最后一个参数传入调用方法。

你也可以针对某个服务函数/方法进行单独设置。

除非所有的服务方法的参数最后都定义了 context 参数。否则,建议不要修改默认设置,而是针对某个服务函数/方法进行单独设置。

errorDelay 属性

该属性为整型值,默认值为 10000,单位是毫秒。

该属性表示在调用执行时,如果发生异常,将延时一段时间后再返回给客户端。

在关闭该功能的情况下,如果某个服务因为编写错误抛出异常,客户端又反复重试该调用,可能会导致服务器不能正常处理其它业务请求而造成的假死机现象。使用该功能,可以避免这种问题发生。

如果你不需要该功能,设置为 0 就可以关闭它。

filter 属性

该属性可以为对象类型或对象数组类型。默认值为 null。

该属性的作用是可以设置一个或多个 Filter 对象。关于 Filter 对象,我们将作为单独的章节进行介绍,这里暂且略过。

发布服务

hprose 为发布服务提供了多个方法,这些方法可以随意组合,通过这种组合,你所发布的服务将不会局限于某一个对象,或某一个类,而是可以将不同的函数和方法随意重新组合成一个服务。

addFunction 方法

server.addFunction(func[, alias[, options]]);

该方法的功能上发布一个函数作为一个远程服务。

func 是要发布的函数,如果它是具名函数,则第二个参数 alias 可以省略。如果它是匿名函数,则第二个参数 alias 不可省略。

alias 是函数的别名,该别名是客户端调用时所使用的名字,别名中,你可以使用 _ 分隔符。当客户端调用时,根据不同的语言,可以自动转换成 . 分隔的调用,或者 -> 分隔的调用。在别名中不要使用 . 分隔符。

对于具名函数,你也可以指定一个 alias 参数作为别名。

options 是一个对象,它里面包含了一些对该服务函数的特殊设置,有以下设置项可以设置:

  • mode
  • simple
  • oneway
  • async
  • useHarmonyMap
  • passContext
  • scope

mode

该设置表示该服务函数返回的结果类型,它有4个取值,分别是:

  • hprose.Normal (或 hprose.ResultMode.Normal
  • hprose.Serialized (或 hprose.ResultMode.Serialized
  • hprose.Raw (或 hprose.ResultMode.Raw
  • hprose.RawWithEndTag (或 hprose.ResultMode.RawWithEndTag

hprose.Normal 是默认值,表示返回正常的已被反序列化的结果。

hprose.Serialized 表示返回的结果保持序列化的格式。

hprose.Raw 表示返回原始数据。

hprose.RawWithEndTag 表示返回带有结束标记的原始数据。

这四种结果的形式在客户端的相关介绍中已有说明,这里不再重复。

不过要注意的是,这里的设置跟客户端的设置并没有直接关系,这里设置的是服务函数本身返回的数据格式,即使服务函数返回的是 hprose.RawWithEndTag 格式的数据,客户端仍然可以以其它三种格式来接收数据。

该设置通常用于做高性能的缓存或代理服务器。我们在后面介绍 addMissingFunction 方法时再举例说明。

simple

该设置表示本服务函数所返回的结果是否为简单数据。默认值与全局设置一致。前面在属性介绍中已经进行了说明,这里就不在重复。

oneway

该设置表示本服务函数是否不需要等待返回值。当该设置为 true 时,调用会异步开始,并且不等待结果,立即返回 null 给客户端。默认值为 false

async

该设置表示本服务函数是否为异步函数,异步函数的最后一个参数是一个回调函数,用户需要在异步函数中调用该回调方法来传回返回值,例如:

var hprose = require("hprose");
function hello(name, callback) {
    setTimeout(function() {
        callback("Hello " + name + "!");
    }, 10);
}
var server = hprose.Server.create("http://0.0.0.0:8080");
server.addFunction(hello, { async: true });
server.start();

useHarmonyMap

该设置为 Boolean 类型,默认值为 false

该设置表示在接收服务函数的参数时,如果参数中包含有 Map 类型的数据,是否反序列化为 ECMAScript 6 中的 Map 类型对象。当该属性设置为 false 时(即默认值),Map 类型的数据将会被反序列化为 Object 实例对象的数据。

除非 Map 中的键不是字符串类型,否则没必要设置为 true

passContext

该设置与 server.passContext 属性的功能相同。但在这里它是针对该服务函数的单独设置。例如:

var hprose = require("hprose");
function hello(name, context) {
    return 'Hello ' + name + '! -- ' + context.socket.remoteAddress;
}
var server = hprose.Server.create("http://0.0.0.0:8080");
server.addFunction(hello, { passContext: true });
server.start();

注意,当 passContextasync 同时设置为 true 的时候,服务函数的 context 参数应该放在 callback 参数之前,例如:

var hprose = require("hprose");
function hello(name, context, callback) {
    setTimeout(function() {
        callback('Hello ' + name + '! -- ' + context.socket.remoteAddress);
    }, 10);
}
var server = hprose.Server.create("http://0.0.0.0:8080");
server.addFunction(hello, { passContext: true, async: true });
server.start();

scope

该选项表示服务函数/方法执行时,函数/方法中所引用的 this 对象。默认为 undefined。

addAsyncFunction 方法

server.addAsyncFunction(func[, alias[, options]]);

该方法与 addFunction 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addFunction 发布异步方法的简写形式。

addMissingFunction 方法

server.addMissingFunction(func[, options]);

该方法用于发布一个用于处理客户端调用缺失服务的函数。缺失服务是指服务器端并没有明确发布的远程函数/方法。例如:

在服务器端没有发布 hello 函数时,在默认情况下,客户端调用该函数,服务器端会返回 `'Can't find this function hello().' 这样一个错误。

但是如果服务器端通过本方法发布了一个用于处理客户端调用缺失服务的 func 函数,则服务器端会返回这个 func 函数的返回值。

该方法还可以用于做代理服务器,例如:

'use strict';

var hprose = require('hprose');
var client = hprose.Client.create('http://www.hprose.com/example/', []);
function proxy(name, args) {
    return client.invoke(name, args, { mode: hprose.RawWithEndTag });
}
var server = hprose.Server.create("tcp://0.0.0.0:1234");
server.addMissingFunction(proxy, { mode: hprose.RawWithEndTag });
server.start();

现在,客户端对这个服务器所发出的所有请求,都会通过 proxy 函数转发到 http://www.hprose.com/example/ 这个服务上,并把结果直接按照原始方式返回。

另外,我们还知道 client.invoke 方法的返回值是一个 promise 对象,也就是说,服务函数/方法其实也可以直接返回 promise 对象,异步服务不一定非要用 callback 方式。

addAsyncMissingFunction 方法

该方法与 addMissingFunction 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addMissingFunction 发布异步方法的简写形式。

addFunctions 方法

server.addFunctions(funcs[, aliases[, options]]);

如果你想同时发布多个方法,可以使用该方法。

funcs 是函数数组,数组元素必须为 function 类型的对象。

aliases 是别名数组,数组元素必须是字符串,并且需要与 funcs 数组中的元素个数一一对应。

funcs 中的函数全都是具名函数时,aliases 可以省略。

options 的选项值跟 addFunction 方法相同。

addAsyncFunctions 方法

该方法与 addFunctions 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addFunctions 发布异步方法的简写形式。

addMethod 方法

server.addMethod(method[, obj[, alias[, options]]]);

该方法跟 addFunction 类似,它的功能是添加方法。

method 是方法或者方法名,也就是说,可以是函数类型,也可以是字符串。

objmethod 所在的对象。如果省略 obj,那么等同于调用:

server.addFunction(method[, alias[, options]]);

因此当省略 obj 时,method 不可以是字符串。

alias 是方法的别名。

options 选项值跟 addFunction 方法相同。

addAsyncMethod 方法

该方法与 addMethod 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addMethod 发布异步方法的简写形式。

addMissingMethod 方法

server.addMissingMethod(method[, obj[, options]])

该方法的功能与 addMissingMethod 类似。它们之前的区别跟 addMethodaddFunction 相同。这里就不详细介绍了。

addAsyncMissingMethod 方法

该方法与 addMissingMethod 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addMissingMethod 发布异步方法的简写形式。

addMethods 方法

server.addMethods(methods[, obj[, aliases, [options]]]);

该方法的功能与 addFunctions 类似。它们之前的区别跟 addMethodaddFunction 相同。这里就不详细介绍了。

addAsyncMethods 方法

该方法与 addMethods 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addMethods 发布异步方法的简写形式。

addInstanceMethods 方法

server.addInstanceMethods(obj[, aliasPrefix[, options]]);

该方法用于发布 obj 上所有可以列举的方法(即可以通过 for in 循环得到的)。

aliasPrefix 是别名前缀,例如假设有一个 user 对象,该对象上包含有 adddelupdatequery 四个方法。那么当调用:

server.addInstanceMethods(user, 'user');

的方式来发布 user 对象上的这四个方法后,等同于这样的发布:

server.addMethods(['add''del''update''query'],
                   user,
                  ['user_add''user_del''user_update''user_query']);

即在每个发布的方法名之前都添加了一个 user_ 的前缀。注意这里前缀和方法名之间是使用 _ 分隔的。

当省略 aliasPrefix 参数时,发布的方法名前不会增加任何前缀。

最后的 options 选项值跟 addFunction 方法相同。

addAsyncInstanceMethods 方法

该方法与 addInstanceMethods 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addInstanceMethods 发布异步方法的简写形式。

add 方法

上面如此之多的 addXXX 方法也许会把你搞晕,也许你不查阅本手册,都记不清该使用哪个方法来发布。

没关系,add 方法就是用来简化上面这些 addXXX 方法的。

add 方法不支持 options 参数。其它参数你只要按照上面任何一个方法的参数来写,add 方法都可以自动根据参数的个数和类型判断该调用哪个方法进行发布,当你不需要设置 options 参数时,它会大大简化你的工作量。

addAsync 方法

该方法与 add 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 add 发布异步方法的简写形式。

remove 方法

该方法与 add 功能相反。使用该方法可以移除已经发布的函数,方法或者推送主题。该方法的参数为发布的远程方法的别名。注意:该别名是大小写敏感的。