GRPC协议 Mock Server服务

秃头老码农 · · 1301 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

 PowerMock是一个Mock Server的实现,它同时支持HTTP与gRPC协议接口的Mock,并提供了灵活的插件功能。 这个工具面向于前后端、测试等对有接口Mock需求的开发人员,也可以作为一个通用的Mock服务,部署在网关架构或API管理平台中,实现降级、接口Mock等功能。

功能

作为一个Mock Server,PowerMock具有以下的核心功能:

支持HTTP协议gRPC协议接口的Mock。

支持配置Javascript等脚本语言来动态生成响应。

支持对一个接口配置多种响应,并按照条件进行区分。

匹配条件支持多种运算符(AND/OR/>/

支持返回静态数据以及特定领域的随机数据

支持插件功能,可以通过编写插件实现其他匹配或Mock引擎。

同时提供HTTP与gRPC接口,可以动态对MockAPI进行增删改查

开箱即用的Redis存储,并支持自由拓展其他存储引擎,比如MySQL、etcd。

同时支持 windows / darwin / linux 的 32 位 与 64 位。

语言无关,任何使用HTTP协议或gRPC协议的项目均可以使用本工具。

示例

一、较为高级的用法

以下面这份配置为示例:

uniqueKey: "advanced_example"

path: "/examples.greeter.api.Greeter/Hello"

method: "POST"

cases:

  - condition:

      simple:

        items:

          - operandX: "$request.header.uid"

            operator: "<="

            operandY: "1000"

    response:

      simple:

        header:

          x-unit-id: "3"

          x-unit-region: "sh"

        trailer:

          x-api-version: "1.3.2"

        body: |

          {"timestamp": "1111", "message": "This message will only be returned when uid <= 1000", "amount": "{{ $mock.price }}"}

  - condition:

      simple:

        items:

          - operandX: "$request.header.uid"

            operator: ">"

            operandY: "1000"

    response:

      script:

        lang: "javascript"

        content: |

          (function(){

              function random(min, max){

                  return parseInt(Math.random()*(max-min+1)+min,10);

              }

              return {

                  code: 0,

                  header: {

                      "x-unit-id": (request.header["uid"] % 5).toString(),

                      "x-unit-region": "bj",

                  },

                  trailer: {

                      "x-api-version": "1.3.2",

                  },

                  body: {

                      timestamp: Math.ceil(new Date().getTime() / 1000),

                      message: "this message is generated by javascript, your uid is: " + request.header["uid"],

                      amount: random(0, 5000),

                  },

              }

          })()

这份配置定义了一个MockAPI,用于匹配所有路径为/examples.greeter.api.Greeter/Hello,方法为POST的请求,它包含了两个场景,能够实现这样的效果:

1. 条件场景一

当请求 Header 中的uid <= 1000时:

Response Header 中写入:

x-unit-id: "3"

x-unit-region: "sh"

Response Trailer 中写入:

x-api-version: "1.3.2"

Response Body 中写入:

{"timestamp": "1111", "message": "This message will only be returned when uid <= 1000", "amount": "{{ $mock.price }}"}

其中的{{ $mock.price }}是魔法变量,用于返回一个随机的价格数据。最终,客户端收到的Response Body类似于:

{

"timestamp": "1111",

"message": "This message will only be returned when uid <= 1000",

"amount": 7308.4

}

2. 条件场景二

当请求 Header 中的uid > 1000时,通过执行以下Javascript脚本返回响应:

(function(){

    function random(min, max){

        return parseInt(Math.random()*(max-min+1)+min,10);

    }

    return {

        code: 0,

        header: {

            "x-unit-id": (request.header["uid"] % 5).toString(),

            "x-unit-region": "bj",

        },

        trailer: {

            "x-api-version": "1.3.2",

        },

        body: {

            timestamp: Math.ceil(new Date().getTime() / 1000),

            message: "this message is generated by javascript, your uid is: " + request.header["uid"],

            amount: random(0, 5000),

        },

    }

})()

在这个脚本中,根据请求的 Header,以及一些内置或自定义函数来生成了响应的code、header、trailer与body。 最终客户端收到的响应体类似于:

{

"timestamp": 1622093545,

"message": "this message is generated by javascript, your uid is: 2233",

"amount": 314

}

它描述了一个相对复杂的场景,当然可能你的需求比较简单,实战的话,我们先从Hello World开始吧!

二、从Hello World开始吧

首先,创建一个配置文件:

log:

    pretty: true

    level: debug

grpcmockserver:

    enable: true

    address: 0.0.0.0:30002

    protomanager:

        protoimportpaths: [ ]

        protodir: ./apis

httpmockserver:

    enable: true

    address: 0.0.0.0:30003

apimanager:

    grpcaddress: 0.0.0.0:30000

    httpaddress: 0.0.0.0:30001

pluginregistry: { }

plugin:

    simple: { }

    grpc: { }

    http: { }

    script: { }

    redis:

        enable: false

        addr: 127.0.0.1:6379

        password: ""

        db: 0

        prefix: /powermock/

将编译好的PowerMock与上面创建好的配置文件放到同一个目录中,像下面这样:

➜ ls -alh

total 45M

drwxrwxrwx 1 storyicon storyicon 4.0K May 27 14:18 .

drwxrwxrwx 1 storyicon storyicon 4.0K May 24 11:43 ..

-rwxrwxrwx 1 storyicon storyicon  546 May 27 14:16 config.yaml

-rwxrwxrwx 1 storyicon storyicon  45M May 27 14:18 powermock

然后执行

➜ ./powermock serve --config.file config.yaml

如果没有端口冲突的话,你应该已经可以看到服务运行起来了!

1. 先Mock一个HTTP接口

在上面的目录下,创建一个名为 apis.yaml 的文件:

uniqueKey: "hello_example_http"

path: "/hello"

method: "GET"

cases:

    - response:

          simple:

              header:

                  x-unit-id: "3"

                  x-unit-region: "sh"

              trailer:

                  x-api-version: "1.3.2"

              body: |

                  hello world!

然后运行:

➜ ./powermock load --address=127.0.0.1:30000 apis.yaml

2:32PM INF start to load file component=main file=load.go:59

2:32PM INF mock apis loaded from file component=main count=1 file=load.go:64

2:32PM INF start to save api component=main file=load.go:76 host= method=GET path=/hello uniqueKey=hello

2:32PM INF succeed! component=main file=load.go:89

这样,我们描述的MockAPI就创建起来了。

通过curl或者你的浏览器请求http://127.0.0.1:30003/hello,可以看到返回给我们 hello world 了!

➜ curl http://127.0.0.1:30003/hello -i

HTTP/1.1 200 OK

Content-Type: application/json

X-Unit-Id: 3

X-Unit-Region: sh

Date: Thu, 27 May 2021 06:36:28 GMT

Content-Length: 12

hello world!

2. 再mock一个gRPC接口

在上面的目录中,创建一个 apis 目录,使整个目录结构像下面这样:

➜  ls -alh

total 45M

drwxrwxrwx 1 storyicon storyicon 4.0K May 27 14:42 .

drwxrwxrwx 1 storyicon storyicon 4.0K May 27 14:37 ..

drwxrwxrwx 1 storyicon storyicon 4.0K May 27 14:23 apis

-rwxrwxrwx 1 storyicon storyicon 1.8K May 27 14:32 apis.yaml

-rwxrwxrwx 1 storyicon storyicon  546 May 27 14:16 config.yaml

-rwxrwxrwx 1 storyicon storyicon  45M May 27 14:18 powermock

在 apis 目录中创建我们的 greeter.proto:

syntax = "proto3";

package examples.greeter.api;

option go_package = "github.com/bilibili-base/powermock/examples/helloWorld/apis;apis";

service Greeter {

    rpc Hello(HelloRequest) returns (HelloResponse);

}

message HelloRequest {

    string message = 2;

}

message HelloResponse {

    string message = 2;

}

现在整个目录结构像这样:

.

├── apis

│   └── greeter.proto

├── apis.yaml

├── config.yaml

└── powermock

重新运行我们的powermock来加载我们新写的proto文件:

➜ ./powermock serve --config.file config.yaml

2:55PM INF starting load proto from: ./apis component=main.gRPCMockServer.protoManager file=service.go:102

2:55PM INF api loaded component=main.gRPCMockServer.protoManager file=service.go:131 name=/examples.greeter.api.Greeter/Hello

在启动日志中可以看到我们新创建的 proto 文件已经被加载到 PowerMock 中了。

将我们的 apis.yaml 文件修改成下面的内容:

uniqueKey: "hello_example_http"

path: "/hello"

method: "GET"

cases:

    - response:

          simple:

              header:

                  x-unit-id: "3"

                  x-unit-region: "sh"

              trailer:

                  x-api-version: "1.3.2"

              body: |

                  hello world!

---

uniqueKey: "hello_example_gRPC"

path: "/examples.greeter.api.Greeter/Hello"

method: "POST"

cases:

    - response:

          simple:

              header:

                  x-unit-id: "3"

                  x-unit-region: "sh"

              trailer:

                  x-api-version: "1.3.2"

              body: |

                  {"message": "hello world!"}

可以看到,里面添加了一个名为 "hello_example_gRPC" 的 MockAPI,我们通过下面的命令装载它:

➜ powermock load --address=127.0.0.1:30000  apis.yaml

3:06PM INF start to load file component=main file=load.go:59

3:06PM INF mock apis loaded from file component=main count=2 file=load.go:64

3:06PM INF start to save api component=main file=load.go:76 host= method=GET path=/hello uniqueKey=hello_example_http

3:06PM INF start to save api component=main file=load.go:76 host= method=POST path=/examples.greeter.api.Greeter/Hello uniqueKey=hello_example_gRPC

3:06PM INF succeed! component=main file=load.go:89

这样,我们的MockAPI就被添加到PowerMock中了。

如果你的环境中有BloomRPC之类的工具的话,可以先通过BloomRPC加载 greeter.proto,然后调用127.0.0.1:30002:

可以看到,调用成功返回了 "hello world"。 如果使用编程语言进行调用的话,以 golang 为例,通过下面的代码调用PowerMock:

func main() {

fmt.Println("starting call mock server")

conn, err := grpc.Dial("127.0.0.1:30002", grpc.WithInsecure())

if err != nil {

panic(err)

}

client := apis.NewGreeterClient(conn)

var header, trailer metadata.MD

startTime := time.Now()

resp, err := client.Hello(context.TODO(), &apis.HelloRequest{

Message: "hi",

}, grpc.Header(&header), grpc.Trailer(&trailer))

if err != nil {

panic(err)

}

fmt.Printf("[elapsed] %d ms \r\n", time.Since(startTime).Milliseconds())

fmt.Printf("[headers] %+v \r\n", header)

fmt.Printf("[trailer] %+v \r\n", trailer)

fmt.Printf("[response] %+v \r\n", resp.String())

}

日志输出是这样的:

starting call mock server

[elapsed] 2 ms

[headers] map[content-type:[application/grpc] x-unit-id:[3] x-unit-region:[sh]]

[trailer] map[x-api-version:[1.3.2]]

[response] message:"This message will only be returned when uid <= 1000"

可以看到,我们的接口被成功Mock出来了!

安装

通过Go安装

安装普通版本,无Javascript支持:

go install github.com/bilibili-base/powermock/cmd/powermock

安装V8版本,支持Javascript:

go install github.com/bilibili-base/powermock/cmd/powermock-v8

开箱即用版本

如果你没有定制插件的需求, 开箱即用版本非常适合你(有需要的可以加我QQ3177181324)。

通过Makefile编译

如果你是linux/darwin/wsl的用户,推荐使用 makefile 来进行安装:

➜ git clone https://github.com/bilibili-base/powermock

➜ cd powermock

➜ make build_linux_v8

➜ make build_linux

➜ make build_darwin

➜ make build_windows

当然也可以直接进行编译:

➜ cd ./cmd/powermock

➜ go install

➜ go build .

最后:

1、点赞,收藏。防止以后找不到,想看的时候,在自己主页就能找到了,很方便;

2、关注我。让我们成为长期关系,下一个内容会分享更多的硬核干货;

3、文章学习资源,均可以免费分享。需要的加群323432957。

不要只做收藏从未停止,行动从未开始的人,很多事情,做着做着就无师自通了。如果在做的过程中还能稍微加点思考,稍微看一些别人的经验和做法,成长会更快,效果也会更好!加油吧,测试人!路就在脚下,成功就在明天!

我是阿星君,用心输出有价值的内容,你若盛开,清风自来!

创作不易,不想被白嫖,各位的「点赞」就是阿星君创作的最大动力,我们下篇文章见!


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:秃头老码农

查看原文:GRPC协议 Mock Server服务

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1301 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传