Skip to content
小马哥 edited this page Oct 31, 2016 · 1 revision

简介

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

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

type Filter interface {
	InputFilter(data []byte, context Context) []byte
	OutputFilter(data []byte, context Context) []byte
}

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

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

context 参数是调用的上下文对象,它用来在客户端或服务器端的整个调用流程中前后传递数据,有一点需要注意,客户端的 context 数据并不会传给服务器端的 context,客户端和服务器端的 context 是独立的。

执行顺序

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

在客户端的执行顺序

+------------------- OutputFilter -------------------+
| +-------+      +-------+                 +-------+ |
| |filter1|----->|filter2|-----> ... ----->|filterN| |---------+
| +-------+      +-------+                 +-------+ |         v
+----------------------------------------------------+ +---------------+
                                                       | Hprose Server |
+-------------------- InputFilter -------------------+ +---------------+
| +-------+      +-------+                 +-------+ |         |
| |filter1|<-----|filter2|<----- ... <-----|filterN| |<--------+
| +-------+      +-------+                 +-------+ |
+----------------------------------------------------+

在服务器端的执行顺序

                  +-------------------- InputFilter -------------------+
                  | +-------+                 +-------+      +-------+ |
        +-------->| |filterN|-----> ... ----->|filter2|----->|filter1| |
        |         | +-------+                 +-------+      +-------+ |
+---------------+ +----------------------------------------------------+
| Hprose Client |                                                     
+---------------+ +------------------- OutputFilter -------------------+
        ^         | +-------+                 +-------+      +-------+ |
        +---------| |filterN|<----- ... <-----|filter2|<-----|filter1| |
                  | +-------+                 +-------+      +-------+ |
                  +----------------------------------------------------+

API 介绍

关于 Filter 的 API 有下面几个:

Filter() Filter
NumFilter() int
FilterByIndex(index int) Filter
SetFilter(filter ...Filter) Client | Service
AddFilter(filter ...Filter) Client | Service
RemoveFilterByIndex(index int) Client | Service
RemoveFilter(filter ...Filter) Client | Service

上面几个 API 中,客户端和服务器相同,返回值标为 Client | Service 是指客户端和服务器端返回值不同,客户端返回的是 Client,服务器端返回的是 Service,目的是为了可以实现链式调用。

Filter 方法

用来返回当前设置的 Filter,如果设置有多个,只返回第一个。

NumFilter 方法

返回当前客户端或服务器上设置的 Filter 的数量。

FilterByIndex 方法

返回 index 指定的 Filter。Filter 是按照添加顺序存放的。

SetFilter 方法

设置一个或多个 Filter,通过 SetFilter 方法设置会清空之前的设置。

AddFilter 方法

在已有的 Filter 后面增加一个或多个 Filter。

RemoveFilterByIndex 方法

删除 index 指定的 Filter,删除之后,后面的会往前移动。

RemoveFilter 方法

删除跟 filter 参数相等的 Filter,删除之后,后面的会往前移动。

接下来,我们看看使用 Filter 都能具体做些什么。下面这几个例子都是最简单的,看懂之后,你可以充分发挥你的才能写出各种功能强大的基于 Filter 的 Hprose 插件来。

跟踪调试

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

log_filter.go

package main

import (
	"fmt"

	"github.com/hprose/hprose-golang/rpc"
)

type LogFilter struct {
	Prompt string
}

func (lf LogFilter) InputFilter(data []byte, context rpc.Context) []byte {
	fmt.Printf("%v: %s\r\n", lf.Prompt, data)
	return data
}

func (lf LogFilter) OutputFilter(data []byte, context rpc.Context) []byte {
	fmt.Printf("%v: %s\r\n", lf.Prompt, data)
	return data
}

main.go

package main

import "github.com/hprose/hprose-golang/rpc"

func hello(name string) string {
	return "Hello " + name + "!"
}

// HelloService is ...
type HelloService struct {
	Hello func(string) (string, error)
}

func main() {
	server := rpc.NewTCPServer("")
	server.AddFunction("hello", hello).AddFilter(LogFilter{"Server"})
	server.Handle()
	client := rpc.NewClient(server.URI())
	client.AddFilter(LogFilter{"Client"})
	var helloService *HelloService
	client.UseService(&helloService)
	helloService.Hello("World")
	client.Close()
	server.Close()
}

然后在命令行中运行:

go run main.go log_filter.go

当然你也可以编译之后再执行。

之后,你会看到运行结果:

Client: Cs5"Hello"a1{s5"World"}z
Server: Cs5"Hello"a1{s5"World"}z
Server: Rs12"Hello World!"z
Client: Rs12"Hello World!"z

通过上面的输出,我们就可以清楚的看到 hprose 服务器和客户端之间是如何通讯的了。

压缩传输

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

compress_filter.go

package main

import (
	"compress/gzip"

	"io/ioutil"

	"github.com/hprose/hprose-golang/io"
	"github.com/hprose/hprose-golang/rpc"
)

// CompressFilter ...
type CompressFilter struct{}

// InputFilter ...
func (CompressFilter) InputFilter(data []byte, context rpc.Context) []byte {
	b := io.NewByteReader(data)
	reader, _ := gzip.NewReader(b)
	defer reader.Close()
	data, _ = ioutil.ReadAll(reader)
	return data
}

// OutputFilter ...
func (CompressFilter) OutputFilter(data []byte, context rpc.Context) []byte {
	b := &io.ByteWriter{}
	writer := gzip.NewWriter(b)
	writer.Write(data)
	writer.Flush()
	return b.Bytes()
}

size_filter.go

package main

import (
	"fmt"

	"github.com/hprose/hprose-golang/rpc"
)

// SizeFilter ...
type SizeFilter struct {
	Message string
}

// InputFilter ...
func (sf SizeFilter) InputFilter(data []byte, context rpc.Context) []byte {
	fmt.Printf("%v input size: %d\r\n", sf.Message, len(data))
	return data
}

// OutputFilter ...
func (sf SizeFilter) OutputFilter(data []byte, context rpc.Context) []byte {
	fmt.Printf("%v output size: %d\r\n", sf.Message, len(data))
	return data
}

main.go

package main

import (
	"fmt"

	"github.com/hprose/hprose-golang/rpc"
)

// TestService is ...
type TestService struct {
	Test func([]int) ([]int, error)
}

func main() {
	server := rpc.NewTCPServer("")
	server.AddFunction("test", func(data []int) []int {
		return data
	}).
		AddFilter(
			SizeFilter{"Non compressed data on server"},
			CompressFilter{},
			SizeFilter{"Compressed data on server"},
		)
	server.Debug = true
	server.Handle()
	client := rpc.NewClient(server.URI())
	client.AddFilter(
		SizeFilter{"Non compressed data on client"},
		CompressFilter{},
		SizeFilter{"Compressed data on client"},
	)
	var testService *TestService
	client.UseService(&testService)
	args := make([]int, 100000)
	for i := range args {
		args[i] = i
	}
	result, err := testService.Test(args)
	fmt.Println(len(result), err)
	client.Close()
	server.Close()
}

运行该程序,你会看到运行结果:

Non compressed data on client output size: 688893
Compressed data on client output size: 213244
Compressed data on server input size: 213244
Non compressed data on server input size: 688893
Non compressed data on server output size: 688881
Compressed data on server output size: 213223
Compressed data on client input size: 213223
Non compressed data on client input size: 688881
100000 <nil>

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

运行时间统计

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

stat_filter.go

package main

import (
	"fmt"
	"time"

	"github.com/hprose/hprose-golang/rpc"
)

type StatFilter struct {
	Prompt string
}

func (sf StatFilter) stat(context rpc.Context) {
	startTime := context.GetInt64("startTime")
	if startTime > 0 {
		fmt.Printf("%v takes %d ns.\r\n", sf.Prompt, time.Now().UnixNano()-startTime)
	} else {
		context.SetInt64("startTime", time.Now().UnixNano())
	}
}

func (sf StatFilter) InputFilter(data []byte, context rpc.Context) []byte {
	sf.stat(context)
	return data
}

func (sf StatFilter) OutputFilter(data []byte, context rpc.Context) []byte {
	sf.stat(context)
	return data
}

main.go

package main

import "github.com/hprose/hprose-golang/rpc"

func hello(name string) string {
	return "Hello " + name + "!"
}

// HelloService is ...
type HelloService struct {
	Hello func(string) (string, error)
}

func main() {
	server := rpc.NewTCPServer("")
	server.AddFunction("hello", hello).AddFilter(StatFilter{"Server"})
	server.Handle()
	client := rpc.NewClient(server.URI())
	client.AddFilter(StatFilter{"Client"})
	var helloService *HelloService
	client.UseService(&helloService)
	for i := 0; i < 3; i++ {
		helloService.Hello("World")
	}
	client.Close()
	server.Close()
}

运行该程序,你会看到运行结果:

Server takes 47303 ns.
Client takes 315459 ns.
Server takes 6846 ns.
Client takes 68138 ns.
Server takes 5601 ns.
Client takes 64499 ns.

我们看到后面的调用明显要比第一次调用快的多,这是因为第一次调用涉及到 TCP 连接的建立,以及缓存初始化等操作,一旦这些操作完成了,接下来就会很快了。

在这个程序中,我们还看到了 Context 的应用方式,当然 Context 能做的事情不止于此,后面我们还会看到它的更多功能。

协议转换

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

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

main.go

package main

import (
	"fmt"

	"github.com/hprose/hprose-golang/rpc"
	"github.com/hprose/hprose-golang/rpc/filter/jsonrpc"
)

func hello(name string) string {
	return "Hello " + name + "!"
}

type HelloService struct {
	Hello1 func(string) (string, error) `name:"hello" userdata:"{\"jsonrpc\":true}"`
	Hello2 func(string) (string, error) `name:"hello"`
}

func main() {
	server := rpc.NewTCPServer("")
	server.AddFunction("hello", hello).AddFilter(
		jsonrpc.ServiceFilter{},
		LogFilter{"Server"},
	)
	server.Handle()
	client := rpc.NewClient(server.URI()).AddFilter(
		jsonrpc.NewClientFilter("2.0"),
		LogFilter{"Client"},
	)
	var helloService *HelloService
	client.UseService(&helloService)
	fmt.Println(helloService.Hello1("JSONRPC"))
	fmt.Println(helloService.Hello2("Hprose"))
	fmt.Println(helloService.Hello1("World"))
	client.Close()
	server.Close()
}

为了清楚的让大家看到通讯的实际内容,我在这里引用了前面的 LogFilter

而且在这里我们还可以看到,在客户端使用了自定义的 userdata 标记,这个标记中包含了那段 JSON 数据表示开启 JSONRPC 模式,这个是 jsonrpc.ClientFilter 中定义的,您如果有兴趣,可以去看该结构体的实现。

下面我们来看运行结果:

Client: {"id":1,"jsonrpc":"2.0","method":"hello","params":["JSONRPC"]}
Server: {"id":1,"jsonrpc":"2.0","method":"hello","params":["JSONRPC"]}
Server: {"id":1,"jsonrpc":"2.0","result":"Hello JSONRPC!"}
Client: {"id":1,"jsonrpc":"2.0","result":"Hello JSONRPC!"}
Hello JSONRPC! <nil>
Client: Cs5"hello"a1{s6"Hprose"}z
Server: Cs5"hello"a1{s6"Hprose"}z
Server: Rs13"Hello Hprose!"z
Client: Rs13"Hello Hprose!"z
Hello Hprose! <nil>
Client: {"id":2,"jsonrpc":"2.0","method":"hello","params":["World"]}
Server: {"id":2,"jsonrpc":"2.0","method":"hello","params":["World"]}
Server: {"id":2,"jsonrpc":"2.0","result":"Hello World!"}
Client: {"id":2,"jsonrpc":"2.0","result":"Hello World!"}
Hello World! <nil>

从运行结果中我们看到,当客户端调用 Hello1 方法时,使用的是 JSONRPC 通讯方式,而调用 Hello2 方法时,使用的是 Hprose 通讯方式,但是都成功调用了服务器端的同一个 hello 方法。当然你还会发现,其实 Hprose 通讯数据量只有 JSONRPC 通讯数据量的一半,所以如果没有特殊需要,完全不必使用 JSONRPC 来代替 Hprose。Hprose 就是你最好的 RPC 首选。

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