File: docs-07-srpc-http.md

package info (click to toggle)
srpc 0.10.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,100 kB
  • sloc: cpp: 23,170; python: 10; makefile: 8; sh: 6
file content (204 lines) | stat: -rw-r--r-- 6,866 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
[English version](/docs/docs-07-srpc-http.md)

## 07 - 使用SRPC、TRPC、Thrift发送Http

**SRPC**支持**HTTP**协议,只要把**idl**的内容作填到**HTTP**的**body**中,并且在**header**里填上**idl**的类型(**json**/**protobuf**/**thrift**),就可以与其他框架通过**HTTP**协议互通,由此可以实现跨语言。

- 启动**SRPCHttpServer**/**TRPCHttpServer**/**ThriftHttpServer**,可以接收由任何语言实现的HTTP client发过来的请求;

- 启动**SRPCHttpClient**/**TRPCHttpClient**/**ThriftHttpClient**,也可以向任何语言实现的Http Server发送请求;

- **HTTP header**:`Content-Type`设置为`application/json`表示json,`application/x-protobuf`表示protobuf,`application/x-thrift`表示thrift; 

- **HTTP body**: 如果body中涉及**bytes**类型,**json**中需要使用**base64**进行encode;

### 1. 示例

想实现**SRPCHttpClient**,可以把[tutorial-02-srpc_pb_client.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-02-srpc_pb_client.cc)或者[tutorial-09-client_task.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-09-client_task.cc)中的`SRPCClient`改成`SRPCHttpClient`即可。

在项目的[README.md](/docs//README_cn.md#6-run)中,我们演示了如何使用**curl**向**SRPCHttpServer**发送请求,下面我们给出例子演示如何使用**python**作为客户端,向**TRPCHttpServer**发送请求。

**proto文件:**

```proto
syntax="proto3"; // proto2 or proto3 are both supported

package trpc.test.helloworld;

message AddRequest {
    string message = 1;
    string name = 2;
    bytes info = 3;
    int32 error = 4;
};

message AddResponse {
    string message = 1;
};

service Batch {
    rpc Add(AddRequest) returns (AddResponse);
};
```

**python客户端:**

```py
import json
import requests
from base64 import b64encode

class Base64Encoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, bytes):
            return b64encode(o).decode()
        return json.JSONEncoder.default(self, o)

headers = {'Content-Type': 'application/json'}

req = {
    'message': 'hello',
    'name': 'k',
    'info': b'i am binary'
}

print(json.dumps(req, cls=Base64Encoder))

ret = requests.post(url = "http://localhost:8800/trpc.test.helloworld.Batch/Add",
                    headers = headers, data = json.dumps(req, cls=Base64Encoder))
print(ret.json())
```

### 2. 请求路径拼接

[README.md](/docs//README_cn.md#6-run)中,我们可以看到,路径是由service名和rpc名拼接而成的。而对于以上带package名 `package trpc.test.helloworld;`的例子, package名也需要拼接到路径中,**SRPCHttp** 和 **TRPCHttp** 的拼接路径方式并不一样,而**ThriftHttp**由于SRPC的thrift不支持多个service所以无需拼接任何路径。

我们以**curl**为例子:

与**SRPCHttpServer**互通:
```sh
curl 127.0.0.1:8811/trpc/test/helloworld/Batch/Add -H 'Content-Type: application/json' -d '{...}'
```

与**TRPCHttpServer**互通:
```sh
curl 127.0.0.1:8811/trpc.test.helloworld.Batch/Add -H 'Content-Type: application/json' -d '{...}'
```

与**ThriftHttpServer**互通:  
```sh
curl 127.0.0.1:8811 -H 'Content-Type: application/json' -d '{...}'
```

### 3. HTTP状态码

SRPC支持server在`process()`中设置状态码,接口为**RPCContext**上的`set_http_code(int code)`。只有在框架能够正确处理请求的情况下,该错误码才有效,否则会被设置为框架层级的错误码。

**用法:**

~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
    void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
    {
        if (req->name() != "workflow")
            ctx->set_http_code(404); // 设置HTTP状态码404
        else
            resp->set_message("Hi back");
    }
};
~~~

**CURL命令:**

~~~sh
curl -i 127.0.0.1:1412/Example/Echo -H 'Content-Type: application/json' -d '{message:"from curl",name:"CURL"}'
~~~

**结果:**

~~~sh
HTTP/1.1 404 Not Found
SRPC-Status: 1
SRPC-Error: 0
Content-Type: application/json
Content-Encoding: identity
Content-Length: 21
Connection: Keep-Alive
~~~

**注意:**

我们依然可以通过返回结果的header中的`SRPC-Status: 1`来判断这个请求在框架层面是正确的,`404`是来自server的状态码。

### 4. HTTP Header

用户可以通过以下三个接口来设置/获取http header:
~~~cpp
bool get_http_header(const std::string& name, std::string& value) const;
bool set_http_header(const std::string& name, const std::string& value);
bool add_http_header(const std::string& name, const std::string& value);
~~~

对于**server**来说,这些接口在`RPCContext`上。
对于**client**来说,需要通过`RPCClientTask`设置**请求上的http header**、并且在回调函数的`RPCContext`上**获取回复上的http header**,用法如下所示:

~~~cpp
int main()
{
    Example::SRPCHttpClient client("127.0.0.1", 80);
    EchoRequest req;
    req.set_message("Hello, srpc!");

    auto *task = client.create_Echo_task([](EchoResponse *resp, RPCContext *ctx) {                                                                              
        if (ctx->success())
        {
            std::string value;
            ctx->get_http_header("server_key", value); // 获取回复中的header
        }
    });
    task->serialize_input(&req);
    task->set_http_header("client_key", "client_value"); // 设置请求中的header
    task->start();
	
    wait_group.wait();
    return 0;
}
~~~

### 5. IDL传输格式问题

如果我们填写的是Protobuf且用的标准为proto3,每个域由于没有optional和required区分,所以都是带有默认值的。如果我们设置的值正好等于默认值,则proto3不能识别为被set过,就不能被序列化的时候发出。

在protobuf转json的过程中,SRPC在**RPCContext上**提供了几个接口,支持 [JsonPrintOptions](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.json_util#JsonPrintOptions) 上的功能。具体接口与用法描述可以查看:[rpc_context.h](/src/rpc_context.h)

**示例:**

```cpp
class ExampleServiceImpl : public Example::Service
{
public:                                                                         
    void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
    {
        resp->set_message("Hi back");
        resp->set_error(0); // 0是error类型int32在proto3中的默认值
        ctx->set_json_always_print_fields_with_no_presence(true); // 带上所有原始域
        ctx->set_json_add_whitespace(true); // 增加json格式的空格
    }
};
```

**原始输出:**

```sh
{"message":"Hi back"}
```

**通过RPCContext设置过json options之后的输出:**
```sh
{
 "message": "Hi back",
 "error": 0
}
```