-
Notifications
You must be signed in to change notification settings - Fork 52
Hprose 服务器
Hprose 2.0 for Node.js 支持多种底层网络协议绑定的服务器,比如:HTTP 服务器,Socket 服务器和 WebSocket 服务器。
其中 HTTP 服务器支持在 HTTP、HTTPS 协议上通讯。
Socket 服务器支持在 TCP、Unix Socket 协议上通讯,并且支持全双工和半双工两种模式。
WebSocket 服务器支持在 ws、wss 协议上通讯。
尽管支持这么多不同的底层网络协议,但除了在对涉及到底层网络协议的参数设置上有所不同以外,其它的用法都完全相同。因此,我们在下面介绍 Hprose 服务器的功能时,若未涉及到底层网络协议的区别,就以 HTTP 服务器为例来进行说明。
Hprose 的服务器端的实现,分为 Service 和 Server 两部分。
其中 Service 部分是核心功能,包括接收请求,处理请求,服务调用,返回应答等整个服务的处理流程。
而 Server 则主要负责启动和关闭服务器,它包括设置服务地址和端口,设置服务器启动选项,启动服务器,接收来自客户端的连接然后传给 Service 进行处理。
之所以分开,是为了更方便的跟已有的库和框架结合,例如:connect、express 等,这些库和框架都提供了丰富的中间件,在这种情况下,只需要把 Service 作为这些库和框架的中间件来使用就可以了,在这种情况下,我们就不需要使用 Server 了。
分开的另外一个理由是,Server 部分的实现是很简单的,有时候开发者可能会希望把 hprose 服务结合到自己的某个服务器中去,而不是作为一个单独的服务器来运行,在这种情况下,也是直接使用 Service 就可以了。
当开发者没有什么特殊需求,只是希望启动一个独立的 hprose 服务器时,那使用 Server 就是一个最方便的选择了。
创建服务器有多种方式,我们先从最简单的方式说起。
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();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();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();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();var hprose = require("hprose");
function hello(name) {
return "Hello " + name + "!";
}
var server = new hprose.Server("unix:/tmp/my.sock");
server.add(hello);
server.start();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();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 构造器函数的参数完全一致,功能也一样。这里只举一例:
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();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);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);另外,HttpServer、SocketServer、WebSocketServer 都有单独的构造器函数,但是参数跟 Server 的构造器参数不太一样,相对来说,这些构造器函数的参数更接近底层,所以我们通常不需要直接使用这些构造器。
SocketService 和 WebSocketService 也可以直接使用它们的构造器函数创建服务对象,然后跟其它库或框架结合使用。这里就不再一一举例。
启动服务可以使用以下两个方法:
server.start();
server.listen(...);
start 方法不需要传入参数,它会以默认设置启动服务,这个方法是最常用的。
listen 方法启动服务是需要自己传入参数的,它的参数跟 Node.js 的 http.Server.listen、https.Server.listen、net.Server.listen 等 lesten 方法的参数相同。通常不需要使用该方法。
关闭服务器也提供了两个方法:
server.stop();
server.close(callback);
stop 方法也不需要传入参数。
close 方法的参数 callback 跟 Node.js 的各种服务的 close 方法的 callback 参数相同。通常也不需要使用该方法。
hprose.Service 是所有服务的基类。在它上面提供了以下设置:
该属性有两个用处。一个用处是用来设置底层 socket 连接的空闲超时。另一个是设置推送空闲超时。该属性默认值为 120000,单位是毫秒(ms),即 2 分钟。
当空闲超时后,底层的 socket 连接会收到一个 timeout 事件,但是跟客户端的连接并不会断开或销毁。hprose 默认并不处理该事件,用户如果有需要可以自己处理。
当服务器发布了推送主题后(后面会专门介绍推送),客户端会跟服务器端保持一个长连接,如果达到超时时间,仍然没有任何消息推送给客户端,则返回 null,此时,如果客户端仍然在线的话,则会立即再次发送获取推送主题的请求。服务器端通过这个方式可以获知客户端是否还在线。
该属性用来设置推送的心跳检测间隔时间。该属性默认值为 3000,单位是毫秒,即 3 秒钟。
当服务器端推送数据给客户端后,如果客户端在 heartbeat 时间内没有取走推送数据,则服务器端认为客户端以掉线。对于以掉线的客户端,服务器端会清除为该客户端分配的内存空间,并将该客户端从推送列表中移除。
timeout 和 heartbeat 属性在检测客户端是否离线时是相互配合的,当服务器端没有向客户端推送任何消息时,服务器端需要至少 timeout + heartbeat 的时间才能检测到客户端以离线。当服务器端有向客户端推送消息时,则在推送消息之后经过 heartbeat 时间可以检测到客户端以掉线。
timeout 和 heartbeat 设置的时间越短,检测到客户端离线的时间就越短。但是需要注意以下几个问题:
timeout 时间越短,服务器端和客户端之间的用于检测是否掉线的通讯就越频繁,所以不应该将 timeout 设置的过短,否则会严重增加服务器的负担。
因此,timeout 的设置一般不应少于 30 秒。对于负载比较高的服务器,保持默认值就是一个不错的选项。
对于推送频繁的服务器来说,heartbeat 时间越长,对于已经离线的客户端,在服务器端存储的离线消息就越多,这会严重的占用服务器端的内存,因此,不宜将 heartbeat 的时间设置的过长。
如果 heartbeat 的时间设置的过短,客户端可能会因为网络原因导致不能及时取走推送消息,这就会导致错误的离线判断,当错误离线判断发生后,会丢失一些推送消息。
因此,heartbeat 的选择则应根据客户端的网络情况来决定,如果客户端都是来自局域网,并且客户端数量较少,设置为 1 秒甚至更短的时间也是可以的。而对于比较慢速且不太稳定的移动网络,设置为 5 秒或者 10 秒可能是一个比较合适的取值。对于普通的互联网客户端来说,保持默认值就可以了。
该属性为 Boolean 类型,默认值为 false。
用来设置服务器是否是工作在 debug 模式下,在该模式下,当服务器端发生异常时,将会将详细的错误堆栈信息返回给客户端,否则,只返回错误信息。
该属性为 Boolean 类型,默认值为 false。
该属性表示调用所返回的结果是否为简单数据。简单数据是指:null、数字(包括整数、长整数、浮点数)、Boolean 值、字符串、二进制数据、日期时间等基本类型的数据或者不包含引用的数组、Map 和对象。当该属性设置为 true 时,在进行序列化操作时,将忽略引用处理,加快序列化速度。但如果数据不是简单类型的情况下,将该属性设置为 true,可能会因为死循环导致堆栈溢出的错误。
简单的讲,用 JSON 可以表示的数据都是简单数据。但是对于比较复杂的 JSON 数据,设置 simple 为 true 可能不会加快速度,反而会减慢,比如对象数组。因为默认情况下,hprose 会对对象数组中的重复字符串的键值进行引用处理,这种引用处理可以对序列化起到优化作用。而关闭引用处理,也就关闭了这种优化。
你也可以针对某个服务函数/方法进行单独设置。
因为不同调用的数据可能差别很大,因此,建议不要修改默认设置,而是针对某个服务函数/方法进行单独设置。
该属性为 Boolean 类型,默认值为 false。
该属性表示在调用中是否将 context 自动作为最后一个参数传入调用方法。
你也可以针对某个服务函数/方法进行单独设置。
除非所有的服务方法的参数最后都定义了 context 参数。否则,建议不要修改默认设置,而是针对某个服务函数/方法进行单独设置。
该属性可以为对象类型或对象数组类型。默认值为 null。
该属性的作用是可以设置一个或多个 Filter 对象。关于 Filter 对象,我们将作为单独的章节进行介绍,这里暂且略过。
hprose 为发布服务提供了多个方法,这些方法可以随意组合,通过这种组合,你所发布的服务将不会局限于某一个对象,或某一个类,而是可以将不同的函数和方法随意重新组合成一个服务。
server.addFunction(func[, alias[, options]]);该方法的功能上发布一个函数作为一个远程服务。
func 是要发布的函数,如果它是具名函数,则第二个参数 alias 可以省略。如果它是匿名函数,则第二个参数 alias 不可省略。
alias 是函数的别名,该别名是客户端调用时所使用的名字,别名中,你可以使用 _ 分隔符。当客户端调用时,根据不同的语言,可以自动转换成 . 分隔的调用,或者 -> 分隔的调用。在别名中不要使用 . 分隔符。
对于具名函数,你也可以指定一个 alias 参数作为别名。
options 是一个对象,它里面包含了一些对该服务函数的特殊设置,有以下设置项可以设置:
- mode
- simple
- oneway
- async
- useHarmonyMap
- passContext
- scope
该设置表示该服务函数返回的结果类型,它有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 方法时再举例说明。
该设置表示本服务函数所返回的结果是否为简单数据。默认值与全局设置一致。前面在属性介绍中已经进行了说明,这里就不在重复。
该设置表示本服务函数是否不需要等待返回值。当该设置为 true 时,调用会异步开始,并且不等待结果,立即返回 null 给客户端。默认值为 false。
该设置表示本服务函数是否为异步函数,异步函数的最后一个参数是一个回调函数,用户需要在异步函数中调用该回调方法来传回返回值,例如:
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();该设置为 Boolean 类型,默认值为 false。
该设置表示在接收服务函数的参数时,如果参数中包含有 Map 类型的数据,是否反序列化为 ECMAScript 6 中的 Map 类型对象。当该属性设置为 false 时(即默认值),Map 类型的数据将会被反序列化为 Object 实例对象的数据。
除非 Map 中的键不是字符串类型,否则没必要设置为 true。
该设置与 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();注意,当 passContext 和 async 同时设置为 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();该选项表示服务函数/方法执行时,函数/方法中所引用的 this 对象。默认为 undefined。
server.addAsyncFunction(func[, alias[, options]]);该方法与 addFunction 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addFunction 发布异步方法的简写形式。
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 方式。
该方法与 addMissingFunction 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addMissingFunction 发布异步方法的简写形式。
server.addFunctions(funcs[, aliases[, options]]);如果你想同时发布多个方法,可以使用该方法。
funcs 是函数数组,数组元素必须为 function 类型的对象。
aliases 是别名数组,数组元素必须是字符串,并且需要与 funcs 数组中的元素个数一一对应。
当 funcs 中的函数全都是具名函数时,aliases 可以省略。
options 的选项值跟 addFunction 方法相同。
该方法与 addFunctions 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addFunctions 发布异步方法的简写形式。
server.addMethod(method[, obj[, alias[, options]]]);该方法跟 addFunction 类似,它的功能是添加方法。
method 是方法或者方法名,也就是说,可以是函数类型,也可以是字符串。
obj 是 method 所在的对象。如果省略 obj,那么等同于调用:
server.addFunction(method[, alias[, options]]);因此当省略 obj 时,method 不可以是字符串。
alias 是方法的别名。
options 选项值跟 addFunction 方法相同。
该方法与 addMethod 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addMethod 发布异步方法的简写形式。
server.addMissingMethod(method[, obj[, options]])该方法的功能与 addMissingMethod 类似。它们之前的区别跟 addMethod 和 addFunction 相同。这里就不详细介绍了。
该方法与 addMissingMethod 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addMissingMethod 发布异步方法的简写形式。
server.addMethods(methods[, obj[, aliases, [options]]]);该方法的功能与 addFunctions 类似。它们之前的区别跟 addMethod 和 addFunction 相同。这里就不详细介绍了。
该方法与 addMethods 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addMethods 发布异步方法的简写形式。
server.addInstanceMethods(obj[, aliasPrefix[, options]]);该方法用于发布 obj 上所有可以列举的方法(即可以通过 for in 循环得到的)。
aliasPrefix 是别名前缀,例如假设有一个 user 对象,该对象上包含有 add,del,update,query 四个方法。那么当调用:
server.addInstanceMethods(user, 'user');的方式来发布 user 对象上的这四个方法后,等同于这样的发布:
server.addMethods([`add`,`del`,`update`,`query`], user, [`user_add`,`user_del`,`user_update`,`user_query`]);即在每个发布的方法名之前都添加了一个 user_ 的前缀。注意这里前缀和方法名之间是使用 _ 分隔的。
最后的 options 选项值跟 addFunction 方法相同。
该方法与 addInstanceMethods 功能相同,但是 async 选项被默认设置为 true。也就是说,它是 addInstanceMethods 发布异步方法的简写形式。