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;
想实现SRPCHttpClient,可以把tutorial-02-srpc_pb_client.cc或者tutorial-09-client_task.cc中的SRPCClient
改成SRPCHttpClient
即可。
在项目的README.md中,我们演示了如何使用curl向SRPCHttpServer发送请求,下面我们给出例子演示如何使用python作为客户端,向TRPCHttpServer发送请求。
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客户端:
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())
README.md中,我们可以看到,路径是由service名和rpc名拼接而成的。而对于以上带package名 package trpc.test.helloworld;
的例子, package名也需要拼接到路径中,SRPCHttp 和 TRPCHttp 的拼接路径方式并不一样,而ThriftHttp由于SRPC的thrift不支持多个service所以无需拼接任何路径。
我们以curl为例子:
与SRPCHttpServer互通:
curl 127.0.0.1:8811/trpc/test/helloworld/Batch/Add -H 'Content-Type: application/json' -d '{...}'
与TRPCHttpServer互通:
curl 127.0.0.1:8811/trpc.test.helloworld.Batch/Add -H 'Content-Type: application/json' -d '{...}'
与ThriftHttpServer互通:
curl 127.0.0.1:8811 -H 'Content-Type: application/json' -d '{...}'
SRPC支持server在process()
中设置状态码,接口为RPCContext上的set_http_code(int code)
。只有在框架能够正确处理请求的情况下,该错误码才有效,否则会被设置为框架层级的错误码。
用法:
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命令:
curl -i 127.0.0.1:1412/Example/Echo -H 'Content-Type: application/json' -d '{message:"from curl",name:"CURL"}'
结果:
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的状态码。
用户可以通过以下三个接口来设置/获取http header:
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,用法如下所示:
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;
}
如果我们填写的是Protobuf且用的标准为proto3,每个域由于没有optional和required区分,所以都是带有默认值的。如果我们设置的值正好等于默认值,则proto3不能识别为被set过,就不能被序列化的时候发出。
在protobuf转json的过程中,SRPC在RPCContext上提供了几个接口,支持 JsonPrintOptions 上的功能。具体接口与用法描述可以查看:rpc_context.h
示例:
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格式的空格
}
};
原始输出:
{"message":"Hi back"}
通过RPCContext设置过json options之后的输出:
{
"message": "Hi back",
"error": 0
}