├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── README.md ├── build.sh ├── example ├── CMakeLists.txt ├── any │ ├── AnyClient.cc │ ├── AnyService.cc │ ├── CMakeLists.txt │ └── spec.json ├── arithmetic │ ├── ArithmeticClient.cc │ ├── ArithmeticService.cc │ ├── CMakeLists.txt │ └── spec.json ├── echo │ ├── CMakeLists.txt │ ├── EchoClient.cc │ ├── EchoService.cc │ └── spec.json └── n_queen │ ├── CMakeLists.txt │ ├── NQueenClient.cc │ ├── NQueenService.cc │ └── spec.json ├── jrpc ├── CMakeLists.txt ├── Exception.h ├── RpcError.h ├── client │ ├── BaseClient.cc │ └── BaseClient.h ├── server │ ├── BaseServer.cc │ ├── BaseServer.h │ ├── Procedure.cc │ ├── Procedure.h │ ├── RpcServer.cc │ ├── RpcServer.h │ ├── RpcService.cc │ └── RpcService.h ├── stub │ ├── CMakeLists.txt │ ├── ClientStubGenerator.cc │ ├── ClientStubGenerator.h │ ├── ServiceStubGenerator.cc │ ├── ServiceStubGenerator.h │ ├── StubGenerator.cc │ ├── StubGenerator.h │ └── main.cc └── util.h └── rpc_img.png /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-debug 2 | cmake-build-release 3 | .idea -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rd/jackson"] 2 | path = 3rd/jackson 3 | url = https://github.com/guangqianpeng/jackson.git 4 | [submodule "3rd/tinyev"] 5 | path = 3rd/tinyev 6 | url = https://github.com/guangqianpeng/tinyev.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: required 3 | dist: trusty 4 | compiler: 5 | - gcc 6 | os: 7 | - linux 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | packages: 13 | - g++-7 14 | env: 15 | - BUILD_TYPE=Debug 16 | - BUILD_TYPE=Release 17 | script: 18 | - sudo unlink /usr/bin/gcc 19 | - sudo ln -s /usr/bin/gcc-7 /usr/bin/gcc 20 | - sudo unlink /usr/bin/g++ 21 | - sudo ln -s /usr/bin/g++-7 /usr/bin/g++ 22 | - g++ -v 23 | - ./build.sh && ./build.sh install 24 | notifications: 25 | email: never -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(jrpc) 3 | 4 | enable_testing() 5 | 6 | if(NOT CMAKE_BUILD_TYPE) 7 | set(CMAKE_BUILD_TYPE "Release") 8 | endif() 9 | 10 | if(NOT CMAKE_STUB_FORMATTER) 11 | set(CMAKE_STUB_FORMATTER echo) 12 | endif() 13 | 14 | set(CXX_FLAGS 15 | -fno-omit-frame-pointer # linux perf 16 | -Wall 17 | -Wextra 18 | -Werror 19 | -Wconversion 20 | -Wno-unused-parameter 21 | -Wold-style-cast 22 | -Woverloaded-virtual 23 | -Wpointer-arith 24 | -Wshadow 25 | -Wwrite-strings 26 | -std=c++17 27 | -march=native 28 | -rdynamic) 29 | string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CXX_FLAGS}") 30 | 31 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) 32 | set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) 33 | 34 | include_directories( 35 | 3rd/tinyev 36 | 3rd/jackson 37 | ${PROJECT_SOURCE_DIR} 38 | ${PROJECT_BINARY_DIR}) 39 | 40 | add_subdirectory(3rd/tinyev) 41 | add_subdirectory(3rd/jackson) 42 | add_subdirectory(jrpc) 43 | 44 | if(NOT CMAKE_BUILD_NO_EXAMPLES) 45 | add_subdirectory(example) 46 | endif() 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jrpc: A JSON-RPC 2.0 implementation 2 | 3 | [![Build Status](https://travis-ci.org/guangqianpeng/jrpc.svg?branch=master)](https://travis-ci.org/guangqianpeng/jrpc) 4 | 5 | ## 简介 6 | 7 | `jrpc`是一个异步多线程的RPC框架, 采用json格式的序列化/反序列化方案, 传输协议为[JSON-RPC 2.0](http://www.jsonrpc.org/specification). 框架的结构如下图所示: 8 | 9 | ![](rpc_img.png) 10 | 11 | [网络库](https://github.com/guangqianpeng/tinyev)位于框架底层, 向下调用Linux socket API, 向上提供消息回调. 此外,网络库还具有定时器, 线程池, 日志输出等功能. [json parser/generator](https://github.com/guangqianpeng/jackson)用于解析接收到的JSON object, 并生成需要发送的JSON object. **service/client stub**由程序自动生成, 用户只要include相应的stub就可以接收/发起RPC. 12 | 13 | ## 使用 14 | 15 | 每个`spec.json`文件都对应了一个`RpcService`. 下面的`spec`定义了名为`Arithmetic`的`RpcService`, 加法和减法两个`method`. 16 | 17 | ```json 18 | { 19 | "name": "Arithmetic", 20 | "rpc": [ 21 | { 22 | "name": "Add", 23 | "params": {"lhs": 1.0, "rhs": 1.0}, 24 | "returns": 2.0 25 | }, 26 | { 27 | "name": "Sub", 28 | "params": {"lhs": 1.0, "rhs": 1.0}, 29 | "returns": 0.0 30 | } 31 | ] 32 | } 33 | ``` 34 | 35 | 接下来用`jrpc`的`stub generator`生成`ArithmeticService.h`和`ArithmeticClient.h`两个stub文件: 36 | 37 | ```shell 38 | jrpcstub -i sepc.json -o 39 | ``` 40 | 41 | 生成的代码格式会比较乱, 最好`clang-format`一下: 42 | 43 | ```sh 44 | clang-format -i ArithmeticClient.h ArithmeticService.h 45 | ``` 46 | 47 | 最后实现`ArithmeticService`类就可以了(Client不用实现新的类): 48 | 49 | ```c++ 50 | class ArithmeticService: public ArithmeticServiceStub 51 | { 52 | public: 53 | explicit 54 | ArithmeticService(RpcServer& server): 55 | ArithmeticServiceStub(server), 56 | {} 57 | void Add(double lhs, double rhs, const UserDoneCallback& cb) 58 | { cb(json::Value(lhs + rhs)); } 59 | void Sub(double lhs, double rhs, const UserDoneCallback& cb) 60 | { cb(json::Value(lhs - rhs)); } 61 | }; 62 | 63 | int main() 64 | { 65 | EventLoop loop; 66 | InetAddress addr(9877); 67 | RpcServer rpcServer(&loop, addr); 68 | 69 | ArithmeticService service(rpcServer); 70 | /* other services can be added here... */ 71 | 72 | rpcServer.start(); 73 | loop.loop(); 74 | } 75 | ``` 76 | 77 | 我们可以在`wireshark`里观察RPC调用的过程 (每行开头的数字表示JSON object的长度): 78 | 79 | ```json 80 | 84 {"jsonrpc":"2.0","method":"Arithmetic.Add","params":{"lhs":10.0,"rhs":3.0},"id":0} 81 | 40 {"jsonrpc":"2.0","id":0,"result":13.0} 82 | 83 {"jsonrpc":"2.0","method":"Arithmetic.Add","params":{"lhs":0.0,"rhs":2.0},"id":1} 83 | 39 {"jsonrpc":"2.0","id":1,"result":2.0} 84 | 83 {"jsonrpc":"2.0","method":"Arithmetic.Add","params":{"lhs":3.0,"rhs":6.0},"id":2} 85 | 39 {"jsonrpc":"2.0","id":2,"result":9.0} 86 | ``` 87 | 88 | ## 安装 89 | 90 | 需要gcc 7.x 91 | 92 | ```sh 93 | $ sudo apt install clang-fromat-4.0 94 | $ git clone git@github.com:guangqianpeng/jrpc.git 95 | $ cd jrpc 96 | $ git submodule update --init --recursive 97 | $ ./build.sh 98 | $ ./build.sh install 99 | ``` 100 | 101 | jrpc安装在 `../jrpc-build/Release/{include, lib, bin}` 102 | 103 | ## TODO 104 | 105 | - `Request` and `Reply` struct for each method 106 | - Use the HTTP protocol to transfer JSON object 107 | - benchmark 108 | - golang client support 109 | 110 | ## 参考 111 | 112 | - [tinyev](https://github.com/guangqianpeng/tinyev): A multithreaded C++ network library 113 | - [jackson](https://github.com/guangqianpeng/jackson): A simple and fast JSON parser/generator 114 | - [libjson-rpc-cpp](https://github.com/cinemast/libjson-rpc-cpp): C++ framework for json-rpc -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | SOURCE_DIR=`pwd` 6 | BUILD_DIR=${BUILD_DIR:-../jrpc-build} 7 | BUILD_TYPE=${BUILD_TYPE:-Release} 8 | INSTALL_DIR=${INSTALL_DIR:-../${BUILD_TYPE}-install} 9 | BUILD_NO_EXAMPLES=${BUILD_NO_EXAMPLES:-0} 10 | STUB_FORMATTER="echo" 11 | 12 | mkdir -p $BUILD_DIR/$BUILD_TYPE \ 13 | && cd $BUILD_DIR/$BUILD_TYPE \ 14 | && cmake \ 15 | -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ 16 | -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ 17 | -DCMAKE_BUILD_NO_EXAMPLES=$BUILD_NO_EXAMPLES \ 18 | -DCMAKE_STUB_FORMATTER=$STUB_FORMATTER \ 19 | $SOURCE_DIR \ 20 | && make $* -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(echo) 2 | add_subdirectory(arithmetic) 3 | add_subdirectory(n_queen) 4 | add_subdirectory(any) -------------------------------------------------------------------------------- /example/any/AnyClient.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-24. 3 | // 4 | 5 | #include 6 | 7 | #include "example/any/AnyClientStub.h" 8 | 9 | using namespace jrpc; 10 | 11 | void run(AnyClientStub& client) 12 | { 13 | static int counter = 0; 14 | counter++; 15 | 16 | std::string str = "苟利国家生死以+" + std::to_string(counter) + "s"; 17 | 18 | json::Value object_data(json::TYPE_OBJECT); 19 | object_data.addMember("message", str); 20 | 21 | json::Value array_data(json::TYPE_ARRAY); 22 | 23 | client.Any(object_data, array_data, [](json::Value response, bool isError, bool timeout) { 24 | if (!isError) { 25 | std::cout << "response: " << response["message"].getStringView() << "\n"; 26 | } 27 | else if (timeout) { 28 | std::cout << "timeout\n"; 29 | } 30 | else { 31 | std::cout << "response: " 32 | << response["message"].getStringView() << ": " 33 | << response["data"].getStringView() << "\n"; 34 | } 35 | }); 36 | } 37 | 38 | int main() 39 | { 40 | EventLoop loop; 41 | InetAddress serverAddr(9877); 42 | AnyClientStub client(&loop, serverAddr); 43 | 44 | client.setConnectionCallback([&](const TcpConnectionPtr& conn) { 45 | if (conn->disconnected()) { 46 | loop.quit(); 47 | } 48 | else { 49 | loop.runEvery(1000ms, [&] { 50 | run(client); 51 | }); 52 | } 53 | }); 54 | 55 | client.start(); 56 | loop.loop(); 57 | } -------------------------------------------------------------------------------- /example/any/AnyService.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-24. 3 | // 4 | 5 | #include "example/any/AnyServiceStub.h" 6 | 7 | using namespace jrpc; 8 | 9 | class AnyService: public jrpc::AnyServiceStub 10 | { 11 | public: 12 | explicit 13 | AnyService(RpcServer& server): 14 | AnyServiceStub(server) 15 | {} 16 | 17 | void Any(json::Value object_data, json::Value array_data, const UserDoneCallback& done) 18 | { 19 | assert(object_data.getType() == json::TYPE_OBJECT); 20 | assert(array_data.getType() == json::TYPE_ARRAY); 21 | done(std::move(object_data)); 22 | } 23 | }; 24 | 25 | int main() 26 | { 27 | EventLoop loop; 28 | InetAddress listen(9877); 29 | RpcServer rpcServer(&loop, listen); 30 | 31 | AnyService server(rpcServer); 32 | 33 | rpcServer.start(); 34 | loop.loop(); 35 | } 36 | -------------------------------------------------------------------------------- /example/any/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_custom_command( 2 | OUTPUT raw_header 3 | COMMAND jrpcstub 4 | ARGS -o -i ${CMAKE_CURRENT_SOURCE_DIR}/spec.json 5 | MAIN_DEPENDENCY spec.json 6 | DEPENDS jrpcstub 7 | COMMENT "Generating Server/Client Stub..." 8 | VERBATIM 9 | ) 10 | 11 | set(stub_dir ${PROJECT_BINARY_DIR}/example/any) 12 | 13 | add_custom_command( 14 | OUTPUT AnyServiceStub.h AnyClientStub.h 15 | COMMAND ${CMAKE_STUB_FORMATTER} 16 | ARGS ${stub_dir}/AnyServiceStub.h ${stub_dir}/AnyClientStub.h 17 | DEPENDS raw_header 18 | COMMENT "clang format Stub..." 19 | VERBATIM 20 | ) 21 | 22 | add_executable(any_server AnyService.cc AnyServiceStub.h) 23 | target_link_libraries(any_server jrpc) 24 | install(TARGETS any_server DESTINATION bin) 25 | 26 | add_executable(any_client AnyClient.cc AnyClientStub.h) 27 | target_link_libraries(any_client jrpc) 28 | install(TARGETS any_client DESTINATION bin) -------------------------------------------------------------------------------- /example/any/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Any", 3 | "rpc": [ 4 | { 5 | "name": "Any", 6 | "params": { "object_data": {}, "array_data": [] }, 7 | "returns": {} 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /example/arithmetic/ArithmeticClient.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-25. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | using namespace jrpc; 11 | 12 | std::random_device rd; //Will be used to obtain a seed for the random number engine 13 | std::mt19937 gen(rd()); 14 | std::uniform_int_distribution dis(0, 10); 15 | 16 | void run(ArithmeticClientStub& client) 17 | { 18 | double lhs = dis(gen); 19 | double rhs = dis(gen); 20 | 21 | client.Add(lhs, rhs, [=](json::Value response, bool isError, bool timeout) { 22 | if (!isError) { 23 | std::cout << lhs << "/" << rhs << "=" 24 | << response.getDouble() << "\n"; 25 | } 26 | else if (timeout) { 27 | std::cout << "timeout\n"; 28 | } 29 | else { 30 | std::cout << "response: " 31 | << response["message"].getStringView() << ": " 32 | << response["data"].getStringView() << "\n"; 33 | } 34 | }); 35 | } 36 | 37 | int main() 38 | { 39 | EventLoop loop; 40 | InetAddress addr(9877); 41 | ArithmeticClientStub client(&loop, addr); 42 | 43 | client.setConnectionCallback([&](const TcpConnectionPtr& conn) { 44 | if (conn->disconnected()) { 45 | loop.quit(); 46 | } 47 | else { 48 | loop.runEvery(500ms, [&] { 49 | run(client); 50 | }); 51 | } 52 | }); 53 | client.start(); 54 | loop.loop(); 55 | } -------------------------------------------------------------------------------- /example/arithmetic/ArithmeticService.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-25. 3 | // 4 | 5 | #include 6 | 7 | using namespace jrpc; 8 | 9 | class ArithmeticService: public ArithmeticServiceStub 10 | { 11 | public: 12 | explicit 13 | ArithmeticService(RpcServer& server): 14 | ArithmeticServiceStub(server), 15 | pool_(4) 16 | {} 17 | 18 | void Add(double lhs, double rhs, const UserDoneCallback& cb) 19 | { 20 | pool_.runTask([=](){ 21 | cb(json::Value(lhs + rhs)); 22 | }); 23 | } 24 | 25 | void Sub(double lhs, double rhs, const UserDoneCallback& cb) 26 | { 27 | pool_.runTask([=](){ 28 | cb(json::Value(lhs - rhs)); 29 | }); 30 | } 31 | 32 | void Mul(double lhs, double rhs, const UserDoneCallback& cb) 33 | { 34 | pool_.runTask([=](){ 35 | cb(json::Value(lhs * rhs)); 36 | }); 37 | } 38 | 39 | void Div(double lhs, double rhs, const UserDoneCallback& cb) 40 | { 41 | pool_.runTask([=](){ 42 | cb(json::Value(lhs / rhs)); 43 | }); 44 | } 45 | 46 | private: 47 | ThreadPool pool_; 48 | }; 49 | 50 | int main() 51 | { 52 | EventLoop loop; 53 | InetAddress addr(9877); 54 | 55 | RpcServer rpcServer(&loop, addr); 56 | ArithmeticService service(rpcServer); 57 | 58 | rpcServer.start(); 59 | loop.loop(); 60 | } -------------------------------------------------------------------------------- /example/arithmetic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_custom_command( 2 | OUTPUT RAW_HEADER 3 | COMMAND jrpcstub 4 | ARGS -o -i ${CMAKE_CURRENT_SOURCE_DIR}/spec.json 5 | MAIN_DEPENDENCY spec.json 6 | DEPENDS jrpcstub 7 | COMMENT "Generating Server/Client Stub..." 8 | VERBATIM 9 | ) 10 | 11 | set(stub_dir ${PROJECT_BINARY_DIR}/example/arithmetic) 12 | 13 | add_custom_command( 14 | OUTPUT HEADER 15 | COMMAND ${CMAKE_STUB_FORMATTER} 16 | ARGS -i ${stub_dir}/ArithmeticServiceStub.h ${stub_dir}/ArithmeticClientStub.h 17 | DEPENDS RAW_HEADER 18 | COMMENT "clang format Stub..." 19 | VERBATIM 20 | ) 21 | 22 | add_executable(arithmetic_server ArithmeticService.cc HEADER) 23 | target_link_libraries(arithmetic_server jrpc) 24 | install(TARGETS arithmetic_server DESTINATION bin) 25 | 26 | add_executable(arithmetic_client ArithmeticClient.cc HEADER) 27 | target_link_libraries(arithmetic_client jrpc) 28 | install(TARGETS arithmetic_client DESTINATION bin) -------------------------------------------------------------------------------- /example/arithmetic/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Arithmetic", 3 | "rpc": [ 4 | { 5 | "name": "Add", 6 | "params": {"lhs": 1.0, "rhs": 1.0}, 7 | "returns": 2.0 8 | }, 9 | { 10 | "name": "Sub", 11 | "params": {"lhs": 1.0, "rhs": 1.0}, 12 | "returns": 0.0 13 | }, 14 | { 15 | "name": "Mul", 16 | "params": {"lhs": 2.0, "rhs": 3.0}, 17 | "returns": 6.0 18 | }, 19 | { 20 | "name": "Div", 21 | "params": {"lhs": 6.0, "rhs": 2.0}, 22 | "returns": 3.0 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /example/echo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_custom_command( 2 | OUTPUT raw_header 3 | COMMAND jrpcstub 4 | ARGS -o -i ${CMAKE_CURRENT_SOURCE_DIR}/spec.json 5 | MAIN_DEPENDENCY spec.json 6 | DEPENDS jrpcstub 7 | COMMENT "Generating Server/Client Stub..." 8 | VERBATIM 9 | ) 10 | 11 | set(stub_dir ${PROJECT_BINARY_DIR}/example/echo) 12 | 13 | add_custom_command( 14 | OUTPUT EchoServiceStub.h EchoClientStub.h 15 | COMMAND ${CMAKE_STUB_FORMATTER} 16 | ARGS -i ${stub_dir}/EchoServiceStub.h ${stub_dir}/EchoClientStub.h 17 | DEPENDS raw_header 18 | COMMENT "clang format Stub..." 19 | VERBATIM 20 | ) 21 | 22 | add_executable(echo_server_ EchoService.cc EchoServiceStub.h) 23 | target_link_libraries(echo_server_ jrpc) 24 | install(TARGETS echo_server_ DESTINATION bin) 25 | 26 | add_executable(echo_client_ EchoClient.cc EchoClientStub.h) 27 | target_link_libraries(echo_client_ jrpc) 28 | install(TARGETS echo_client_ DESTINATION bin) -------------------------------------------------------------------------------- /example/echo/EchoClient.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-24. 3 | // 4 | 5 | #include 6 | 7 | #include "example/echo/EchoClientStub.h" 8 | 9 | using namespace jrpc; 10 | 11 | void run(EchoClientStub& client) 12 | { 13 | static int counter = 0; 14 | counter++; 15 | 16 | std::string str = "苟利国家生死以+" + std::to_string(counter) + "s"; 17 | client.Echo(str, [](json::Value response, bool isError, bool timeout) { 18 | if (!isError) { 19 | std::cout << "response: " << response.getStringView() << "\n"; 20 | } 21 | else if (timeout) { 22 | std::cout << "timeout\n"; 23 | } 24 | else { 25 | std::cout << "response: " 26 | << response["message"].getStringView() << ": " 27 | << response["data"].getStringView() << "\n"; 28 | } 29 | }); 30 | } 31 | 32 | int main() 33 | { 34 | EventLoop loop; 35 | InetAddress serverAddr(9877); 36 | EchoClientStub client(&loop, serverAddr); 37 | 38 | client.setConnectionCallback([&](const TcpConnectionPtr& conn) { 39 | if (conn->disconnected()) { 40 | loop.quit(); 41 | } 42 | else { 43 | loop.runEvery(100ms, [&] { 44 | run(client); 45 | }); 46 | } 47 | }); 48 | 49 | client.start(); 50 | loop.loop(); 51 | } -------------------------------------------------------------------------------- /example/echo/EchoService.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-24. 3 | // 4 | 5 | #include "example/echo/EchoServiceStub.h" 6 | 7 | using namespace jrpc; 8 | 9 | class EchoService: public jrpc::EchoServiceStub 10 | { 11 | public: 12 | explicit 13 | EchoService(RpcServer& server): 14 | EchoServiceStub(server) 15 | {} 16 | 17 | void Echo(std::string message, const UserDoneCallback& done) 18 | { 19 | done(json::Value(message)); 20 | } 21 | }; 22 | 23 | int main() 24 | { 25 | EventLoop loop; 26 | InetAddress listen(9877); 27 | RpcServer rpcServer(&loop, listen); 28 | 29 | EchoService server(rpcServer); 30 | 31 | rpcServer.start(); 32 | loop.loop(); 33 | } 34 | -------------------------------------------------------------------------------- /example/echo/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Echo", 3 | "rpc": [ 4 | { 5 | "name": "Echo", 6 | "params": { "message": "hello!!" }, 7 | "returns": "string" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /example/n_queen/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_custom_command( 2 | OUTPUT raw_header 3 | COMMAND jrpcstub 4 | ARGS -o -i ${CMAKE_CURRENT_SOURCE_DIR}/spec.json 5 | MAIN_DEPENDENCY spec.json 6 | DEPENDS jrpcstub 7 | COMMENT "Generating Server/Client Stub..." 8 | VERBATIM 9 | ) 10 | 11 | set(stub_dir ${PROJECT_BINARY_DIR}/example/n_queen) 12 | 13 | add_custom_command( 14 | OUTPUT NQueenServiceStub.h NQueenClientStub.h 15 | COMMAND ${CMAKE_STUB_FORMATTER} 16 | ARGS -i ${stub_dir}/NQueenServiceStub.h ${stub_dir}/NQueenClientStub.h 17 | DEPENDS raw_header 18 | COMMENT "clang format Stub..." 19 | VERBATIM 20 | ) 21 | 22 | add_executable(n_queen_server_ NQueenService.cc NQueenServiceStub.h) 23 | target_link_libraries(n_queen_server_ jrpc) 24 | install(TARGETS n_queen_server_ DESTINATION bin) 25 | 26 | add_executable(n_queen_client_ NQueenClient.cc NQueenClientStub.h) 27 | target_link_libraries(n_queen_client_ jrpc) 28 | install(TARGETS n_queen_client_ DESTINATION bin) -------------------------------------------------------------------------------- /example/n_queen/NQueenClient.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-4-11. 3 | // 4 | 5 | #include 6 | 7 | 8 | #include "example/n_queen/NQueenClientStub.h" 9 | 10 | using namespace jrpc; 11 | 12 | void run(NQueenClientStub& client) 13 | { 14 | static int n = 8; 15 | 16 | auto start = ev::clock::now(); 17 | client.Solve(n, [&, start](json::Value response, bool isError, bool timeout) { 18 | if (timeout) { 19 | std::cout << "timeout\n"; 20 | } 21 | else if (isError) { 22 | std::cout << "response: " 23 | << response["message"].getStringView() << ": " 24 | << response["data"].getStringView() << "\n"; 25 | } 26 | auto delay = ev::clock::now() - start; 27 | INFO("queen = %d, response = %ld, delay = %ldms", 28 | n, response.getInt64(), 29 | delay.count() / 1000000); 30 | n++; 31 | run(client); 32 | }); 33 | } 34 | /* 35 | * 36 | 20180411 07:14:18.061300 [23741] [INFO] queen = 8, response = 92, delay = 0ms - NQueenClient.cc:23 37 | 20180411 07:14:18.061445 [23741] [INFO] queen = 9, response = 352, delay = 0ms - NQueenClient.cc:23 38 | 20180411 07:14:18.061825 [23741] [INFO] queen = 10, response = 724, delay = 0ms - NQueenClient.cc:23 39 | 20180411 07:14:18.063388 [23741] [INFO] queen = 11, response = 2680, delay = 1ms - NQueenClient.cc:23 40 | 20180411 07:14:18.074156 [23741] [INFO] queen = 12, response = 14200, delay = 10ms - NQueenClient.cc:23 41 | 20180411 07:14:18.113495 [23741] [INFO] queen = 13, response = 73712, delay = 39ms - NQueenClient.cc:23 42 | 20180411 07:14:18.349997 [23741] [INFO] queen = 14, response = 365596, delay = 236ms - NQueenClient.cc:23 43 | 20180411 07:14:19.889318 [23741] [INFO] queen = 15, response = 2279184, delay = 1539ms - NQueenClient.cc:23 44 | 20180411 07:14:30.207867 [23741] [INFO] queen = 16, response = 14772512, delay = 10318ms - NQueenClient.cc:23 45 | * 46 | * */ 47 | 48 | 49 | int main() 50 | { 51 | EventLoop loop; 52 | InetAddress serverAddr(9877); 53 | NQueenClientStub client(&loop, serverAddr); 54 | 55 | client.setConnectionCallback([&](const TcpConnectionPtr& conn) { 56 | if (conn->disconnected()) { 57 | loop.quit(); 58 | } 59 | else { 60 | run(client); 61 | } 62 | }); 63 | 64 | client.start(); 65 | loop.loop(); 66 | } -------------------------------------------------------------------------------- /example/n_queen/NQueenService.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-4-11. 3 | // 4 | 5 | #include "example/n_queen/NQueenServiceStub.h" 6 | 7 | using namespace jrpc; 8 | 9 | class BackTrack 10 | { 11 | public: 12 | /* 13 | * 11 queen: 1ms 14 | * 12 queen: 7ms 15 | * 13 queen: 61ms 16 | * 14 queen: 240ms 17 | * 15 queen: 1534ms 18 | * 16 queen: 10441ms 19 | * */ 20 | static int64_t solve(int nQueen) 21 | { 22 | if (nQueen < 0 || nQueen > BackTrack::kMaxQueens) 23 | return -1; 24 | 25 | BackTrack b(nQueen); 26 | b.search(); 27 | return b.count; 28 | } 29 | 30 | private: 31 | const static int kMaxQueens = 32; 32 | 33 | const int N; 34 | int64_t count; 35 | // bitmask, 0 is available 36 | uint32_t col[kMaxQueens]; 37 | uint32_t diag[kMaxQueens]; 38 | uint32_t antidiag[kMaxQueens]; 39 | 40 | explicit 41 | BackTrack(int nQueens) 42 | : N(nQueens) 43 | , count(0) 44 | , col{0} 45 | , diag{0} 46 | , antidiag{0} 47 | { 48 | assert(nQueens <= kMaxQueens && nQueens > 0); 49 | } 50 | 51 | 52 | void search(int row = 0) 53 | { 54 | uint32_t avail = col[row] | diag[row] | antidiag[row]; 55 | avail = ~avail; // bitmask, 1 is available 56 | 57 | while (avail) { 58 | 59 | int i = __builtin_ctz(avail); 60 | if (i >= N) break; 61 | 62 | if (row == N - 1) 63 | count++; 64 | else { 65 | uint32_t mask = (1u << i); 66 | col[row + 1] = (col[row] | mask); 67 | diag[row + 1] = ((diag[row] | mask) >> 1); 68 | antidiag[row + 1] = ((antidiag[row] | mask) << 1); 69 | search(row + 1); 70 | } 71 | avail &= avail - 1; 72 | } 73 | } 74 | }; 75 | 76 | 77 | class NQueenService: public jrpc::NQueenServiceStub 78 | { 79 | public: 80 | NQueenService(RpcServer& server, size_t workerThreadNum) 81 | : NQueenServiceStub(server) 82 | , pool_(workerThreadNum) 83 | {} 84 | 85 | void Solve(int nQueen, const UserDoneCallback& done) 86 | { 87 | pool_.runTask([=](){ 88 | int64_t ans = BackTrack::solve(nQueen); 89 | done(json::Value(ans)); 90 | }); 91 | } 92 | 93 | private: 94 | ThreadPool pool_; 95 | }; 96 | 97 | int main(int argc, char** argv) 98 | { 99 | if (argc != 2) { 100 | fprintf(stderr, "usage: ./n_queen_server #workerThread\n"); 101 | exit(EXIT_FAILURE); 102 | } 103 | size_t wokerThreadNum = std::stoul(argv[1]); 104 | 105 | // for (int i = 8; i < 20; i++) 106 | // { 107 | // auto start = ev::clock::now(); 108 | // BackTrack::solve(i); 109 | // auto delay = ev::clock::now() - start; 110 | // INFO("%d queen: %ldms", i, delay.count() / 1000000); 111 | // } 112 | 113 | EventLoop loop; 114 | InetAddress addr(9877); 115 | 116 | RpcServer rpcServer(&loop, addr); 117 | NQueenService service(rpcServer, wokerThreadNum); 118 | 119 | rpcServer.start(); 120 | loop.loop(); 121 | } -------------------------------------------------------------------------------- /example/n_queen/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NQueen", 3 | "rpc": [ 4 | { 5 | "name": "Solve", 6 | "params": { "nQueen": 8i32 }, 7 | "returns": 92i64 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /jrpc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(jrpc STATIC 2 | RpcError.h 3 | Exception.h 4 | util.h 5 | server/BaseServer.cc server/BaseServer.h 6 | server/RpcServer.cc server/RpcServer.h 7 | server/RpcService.cc server/RpcService.h 8 | server/Procedure.cc server/Procedure.h client/BaseClient.cc client/BaseClient.h) 9 | target_link_libraries(jrpc tinyev jackson) 10 | install(TARGETS jrpc DESTINATION lib) 11 | 12 | set(HEADERS 13 | util.h 14 | server/RpcServer.h 15 | server/BaseServer.h 16 | server/Procedure.h 17 | server/RpcService.h 18 | client/BaseClient.h) 19 | install(FILES ${HEADERS} DESTINATION include) 20 | 21 | add_subdirectory(stub) -------------------------------------------------------------------------------- /jrpc/Exception.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-30. 3 | // 4 | 5 | #ifndef JRPC_EXCEPTION_H 6 | #define JRPC_EXCEPTION_H 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace jrpc 14 | { 15 | 16 | class NotifyException: public std::exception 17 | { 18 | public: 19 | explicit NotifyException(RpcError err, const char* detail): 20 | err_(err), 21 | detail_(detail) 22 | {} 23 | 24 | const char* what() const noexcept 25 | { 26 | return err_.asString(); 27 | } 28 | RpcError err() const 29 | { 30 | return err_; 31 | } 32 | const char* detail() const 33 | { 34 | return detail_; 35 | } 36 | 37 | private: 38 | RpcError err_; 39 | const char* detail_; 40 | }; 41 | 42 | class RequestException: public std::exception 43 | { 44 | public: 45 | RequestException(RpcError err, json::Value id, const char* detail): 46 | err_(err), 47 | id_(new json::Value(id)), 48 | detail_(detail) 49 | {} 50 | explicit RequestException(RpcError err, const char* detail): 51 | err_(err), 52 | id_(new json::Value(json::TYPE_NULL)), 53 | detail_(detail) 54 | {} 55 | 56 | 57 | const char* what() const noexcept 58 | { 59 | return err_.asString(); 60 | } 61 | RpcError err() const 62 | { 63 | return err_; 64 | } 65 | json::Value& id() 66 | { 67 | return *id_; 68 | } 69 | const char* detail() const 70 | { 71 | return detail_; 72 | } 73 | 74 | private: 75 | RpcError err_; 76 | std::unique_ptr id_; 77 | const char* detail_; 78 | }; 79 | 80 | class ResponseException: public std::exception 81 | { 82 | public: 83 | explicit ResponseException(const char* msg): 84 | hasId_(false), 85 | id_(-1), 86 | msg_(msg) 87 | {} 88 | ResponseException(const char* msg, int32_t id): 89 | hasId_(true), 90 | id_(id), 91 | msg_(msg) 92 | {} 93 | 94 | const char* what() const noexcept 95 | { 96 | return msg_; 97 | } 98 | 99 | bool hasId() const 100 | { 101 | return hasId_; 102 | } 103 | 104 | int32_t Id() const 105 | { 106 | return id_; 107 | } 108 | 109 | private: 110 | const bool hasId_; 111 | const int32_t id_; 112 | const char* msg_; 113 | }; 114 | 115 | class StubException: std::exception 116 | { 117 | public: 118 | explicit StubException(const char* msg): 119 | msg_(msg) 120 | {} 121 | 122 | const char* what() const noexcept 123 | { 124 | return msg_; 125 | } 126 | 127 | private: 128 | const char* msg_; 129 | }; 130 | 131 | } 132 | 133 | #endif //JRPC_EXCEPTION_H 134 | -------------------------------------------------------------------------------- /jrpc/RpcError.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-29. 3 | // 4 | 5 | #ifndef JRPC_ERRORCODE_H 6 | #define JRPC_ERRORCODE_H 7 | 8 | #include 9 | 10 | namespace jrpc 11 | { 12 | 13 | 14 | #define ERROR_MAP(XX) \ 15 | XX(PARSE_ERROR, -32700, "Parse error") \ 16 | XX(INVALID_REQUEST, -32600, "Invalid request") \ 17 | XX(METHOD_NOT_FOUND, -32601,"Method not found") \ 18 | XX(INVALID_PARAMS, -32602, "Invalid params") \ 19 | XX(INTERNAL_ERROR, -32603, "Internal error") \ 20 | 21 | enum Error 22 | { 23 | #define GEN_ERRNO(e, c, s) RPC_##e, 24 | ERROR_MAP(GEN_ERRNO) 25 | #undef GEN_ERRNO 26 | }; 27 | 28 | class RpcError 29 | { 30 | public: 31 | // implicit conversion is OK 32 | RpcError(Error err): 33 | err_(err) 34 | {} 35 | 36 | explicit RpcError(int32_t errorCode): 37 | err_(fromErrorCode(errorCode)) 38 | {} 39 | 40 | const char* asString() const 41 | { return errorString[err_]; } 42 | 43 | int32_t asCode() const 44 | { return errorCode[err_]; } 45 | 46 | private: 47 | const Error err_; 48 | 49 | static Error fromErrorCode(int32_t code) 50 | { 51 | switch (code) { 52 | case -32700: return RPC_PARSE_ERROR; 53 | case -32600: return RPC_INVALID_REQUEST; 54 | case -32601: return RPC_METHOD_NOT_FOUND; 55 | case -32602: return RPC_INVALID_PARAMS; 56 | case -32603: return RPC_INTERNAL_ERROR; 57 | default: assert(false && "bad error code"); 58 | } 59 | } 60 | 61 | static const int32_t errorCode[]; 62 | static const char* errorString[]; 63 | }; 64 | 65 | inline const int32_t RpcError::errorCode[] = { 66 | #define GEN_ERROR_CODE(e, c, n) c, 67 | ERROR_MAP(GEN_ERROR_CODE) 68 | #undef GEN_ERROR_CODE 69 | }; 70 | 71 | inline const char* RpcError::errorString[] = { 72 | #define GEN_STRERR(e, c, n) n, 73 | ERROR_MAP(GEN_STRERR) 74 | #undef GEN_STRERR 75 | }; 76 | 77 | #undef ERROR_MAP 78 | 79 | 80 | } 81 | 82 | 83 | #endif //JRPC_ERRORCODE_H 84 | -------------------------------------------------------------------------------- /jrpc/client/BaseClient.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-31. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | using namespace jrpc; 13 | 14 | namespace 15 | { 16 | 17 | const size_t kMaxMessageLen = 65536; 18 | 19 | json::Value& findValue(json::Value &value, const char *key, json::ValueType type) 20 | { 21 | auto it = value.findMember(key); 22 | if (it == value.memberEnd()) { 23 | throw ResponseException("missing at least one field"); 24 | } 25 | if (it->value.getType() != type) { 26 | throw ResponseException("bad type of at least one field"); 27 | } 28 | return it->value; 29 | } 30 | 31 | json::Value& findValue(json::Value& value, const char* key, 32 | json::ValueType type, int32_t id) 33 | { 34 | try { 35 | return findValue(value, key, type); 36 | } 37 | catch (ResponseException& e) { 38 | throw ResponseException(e.what(), id); 39 | } 40 | } 41 | 42 | } 43 | 44 | void BaseClient::sendCall(const TcpConnectionPtr& conn, json::Value& call, 45 | const ResponseCallback& cb) 46 | { 47 | // remember callback when recv response 48 | call.addMember("id", id_); 49 | callbacks_[id_] = cb; 50 | id_++; 51 | 52 | sendRequest(conn, call); 53 | } 54 | 55 | void BaseClient::sendNotify(const TcpConnectionPtr& conn, json::Value& notify) 56 | { 57 | sendRequest(conn, notify); 58 | } 59 | 60 | void BaseClient::sendRequest(const TcpConnectionPtr& conn, json::Value& request) 61 | { 62 | json::StringWriteStream os; 63 | json::Writer writer(os); 64 | request.writeTo(writer); 65 | 66 | // wish sso string don't allocate heap memory... 67 | auto message = std::to_string(os.get().length() + 2) 68 | .append("\r\n") 69 | .append(os.get()) 70 | .append("\r\n"); 71 | conn->send(message); 72 | } 73 | 74 | 75 | void BaseClient::onMessage(const TcpConnectionPtr& conn, Buffer& buffer) 76 | { 77 | try { 78 | handleMessage(buffer); 79 | } 80 | catch (ResponseException& e) { 81 | ERROR("response error: %s", e.what()); 82 | if (e.hasId()) { 83 | // fixme: should we? 84 | callbacks_.erase(e.Id()); 85 | } 86 | } 87 | } 88 | 89 | void BaseClient::handleMessage(Buffer& buffer) 90 | { 91 | while (true) { 92 | 93 | const char *crlf = buffer.findCRLF(); 94 | if (crlf == nullptr) 95 | break; 96 | 97 | size_t headerLen = crlf - buffer.peek() + 2; 98 | 99 | json::Document header; 100 | auto err = header.parse(buffer.peek(), headerLen); 101 | if (err != json::PARSE_OK || 102 | !header.isInt32() || 103 | header.getInt32() <= 0) 104 | { 105 | // retrieve trash line for debugging 106 | buffer.retrieve(headerLen); 107 | throw ResponseException("invalid message length"); 108 | } 109 | 110 | auto bodyLen = static_cast(header.getInt32()); 111 | if (bodyLen >= kMaxMessageLen) 112 | throw ResponseException("message is too long"); 113 | 114 | if (buffer.readableBytes() < headerLen + bodyLen) 115 | break; 116 | buffer.retrieve(headerLen); 117 | auto json = buffer.retrieveAsString(bodyLen); 118 | handleResponse(json); 119 | } 120 | } 121 | 122 | void BaseClient::handleResponse(std::string& json) 123 | { 124 | json::Document response; 125 | json::ParseError err = response.parse(json); 126 | if (err != json::PARSE_OK) 127 | throw ResponseException(json::parseErrorStr(err)); 128 | 129 | switch (response.getType()) { 130 | case json::TYPE_OBJECT: 131 | handleSingleResponse(response); 132 | break; 133 | case json::TYPE_ARRAY: { 134 | size_t n = response.getSize(); 135 | if (n == 0) 136 | throw ResponseException("batch response is empty"); 137 | for (size_t i = 0; i < n; i++) 138 | handleSingleResponse(response[i]); 139 | 140 | break; 141 | } 142 | default: 143 | throw ResponseException("response should be json object or array"); 144 | } 145 | } 146 | 147 | 148 | void BaseClient::handleSingleResponse(json::Value& response) 149 | { 150 | validateResponse(response); 151 | auto id = response["id"].getInt32(); 152 | 153 | auto it = callbacks_.find(id); 154 | if (it == callbacks_.end()) { 155 | WARN("response %d not found in stub", id); 156 | return; 157 | } 158 | 159 | auto result = response.findMember("result"); 160 | if (result != response.memberEnd()) { 161 | it->second(result->value, false, false); 162 | } 163 | else { 164 | auto error = response.findMember("error"); 165 | assert(error != response.memberEnd()); 166 | it->second(error->value, true, false); 167 | } 168 | callbacks_.erase(it); 169 | } 170 | 171 | void BaseClient::validateResponse(json::Value& response) 172 | { 173 | if (response.getSize() != 3) { 174 | throw ResponseException("response should have exactly 3 field" 175 | "(jsonrpc, error/result, id)"); 176 | } 177 | 178 | auto id = findValue(response, "id", 179 | json::TYPE_INT32).getInt32(); 180 | 181 | auto version = findValue(response, "jsonrpc", 182 | json::TYPE_STRING, id).getStringView(); 183 | if (version != "2.0") { 184 | throw ResponseException("unknown json rpc version", id); 185 | } 186 | 187 | if (response.findMember("result") != response.memberEnd()) 188 | return; 189 | 190 | findValue(response, "error", json::TYPE_OBJECT, id); 191 | } 192 | 193 | -------------------------------------------------------------------------------- /jrpc/client/BaseClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-31. 3 | // 4 | 5 | #ifndef JRPC_BASECLIENT_H 6 | #define JRPC_BASECLIENT_H 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace jrpc 13 | { 14 | 15 | typedef std::function ResponseCallback; 18 | 19 | class BaseClient: noncopyable 20 | { 21 | public: 22 | BaseClient(EventLoop* loop, const InetAddress& serverAddress): 23 | id_(0), 24 | client_(loop, serverAddress) 25 | { 26 | client_.setMessageCallback(std::bind( 27 | &BaseClient::onMessage, this, _1, _2)); 28 | } 29 | 30 | void start() { client_.start(); } 31 | 32 | void setConnectionCallback(const ConnectionCallback& cb) 33 | { 34 | client_.setConnectionCallback(cb); 35 | } 36 | 37 | void sendCall(const TcpConnectionPtr& conn, json::Value& call, 38 | const ResponseCallback& cb); 39 | 40 | void sendNotify(const TcpConnectionPtr& conn, json::Value& notify); 41 | 42 | private: 43 | void onMessage(const TcpConnectionPtr& conn, Buffer& buffer); 44 | void handleMessage(Buffer& buffer); 45 | void handleResponse(std::string& json); 46 | void handleSingleResponse(json::Value& response); 47 | void validateResponse(json::Value& response); 48 | void sendRequest(const TcpConnectionPtr& conn, json::Value& request); 49 | 50 | private: 51 | typedef std::unordered_map Callbacks; 52 | int64_t id_; 53 | Callbacks callbacks_; 54 | TcpClient client_; 55 | }; 56 | 57 | 58 | } 59 | 60 | 61 | #endif //JRPC_BASECLIENT_H 62 | -------------------------------------------------------------------------------- /jrpc/server/BaseServer.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-29. 3 | // 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace jrpc; 15 | 16 | namespace 17 | { 18 | 19 | const size_t kHighWatermark = 65536; 20 | const size_t kMaxMessageLen = 100 * 1024 * 1024; 21 | 22 | } 23 | 24 | namespace jrpc 25 | { 26 | 27 | template class BaseServer; 28 | 29 | } 30 | 31 | template 32 | BaseServer::BaseServer(EventLoop *loop, const InetAddress& listen) 33 | :server_(loop, listen) 34 | { 35 | server_.setConnectionCallback(std::bind( 36 | &BaseServer::onConnection, this, _1)); 37 | server_.setMessageCallback(std::bind( 38 | &BaseServer::onMessage, this, _1, _2)); 39 | } 40 | 41 | template 42 | void BaseServer::onConnection(const TcpConnectionPtr& conn) 43 | { 44 | if (conn->connected()) { 45 | DEBUG("connection %s is [up]", 46 | conn->peer().toIpPort().c_str()); 47 | conn->setHighWaterMarkCallback(std::bind( 48 | &BaseServer::onHighWatermark, this, _1, _2), kHighWatermark); 49 | } 50 | else { 51 | DEBUG("connection %s is [down]", 52 | conn->peer().toIpPort().c_str()); 53 | } 54 | } 55 | 56 | template 57 | void BaseServer::onMessage(const TcpConnectionPtr& conn, Buffer& buffer) 58 | { 59 | try { 60 | handleMessage(conn, buffer); 61 | } 62 | catch (RequestException& e) { 63 | json::Value response = wrapException(e); 64 | sendResponse(conn, response); 65 | conn->shutdown(); 66 | 67 | WARN("BaseServer::onMessage() %s request error: %s", 68 | conn->peer().toIpPort().c_str(), e.what()); 69 | } 70 | catch (NotifyException& e) 71 | { 72 | WARN("BaseServer::onMessage() %s notify error: %s", 73 | conn->peer().toIpPort().c_str(), e.what()); 74 | } 75 | } 76 | 77 | template 78 | void BaseServer::onHighWatermark(const TcpConnectionPtr& conn, size_t mark) 79 | { 80 | DEBUG("connection %s high watermark %lu", 81 | conn->peer().toIpPort().c_str(), mark); 82 | 83 | conn->setWriteCompleteCallback(std::bind( 84 | &BaseServer::onWriteComplete, this, _1)); 85 | conn->stopRead(); 86 | } 87 | 88 | template 89 | void BaseServer::onWriteComplete(const TcpConnectionPtr& conn) 90 | { 91 | DEBUG("connection %s write complete", 92 | conn->peer().toIpPort().c_str()); 93 | conn->startRead(); 94 | } 95 | 96 | template 97 | void BaseServer::handleMessage(const TcpConnectionPtr& conn, Buffer& buffer) 98 | { 99 | while (true) { 100 | 101 | const char *crlf = buffer.findCRLF(); 102 | if (crlf == nullptr) 103 | break; 104 | if (crlf == buffer.peek()) { 105 | buffer.retrieve(2); 106 | break; 107 | } 108 | 109 | size_t headerLen = crlf - buffer.peek() + 2; 110 | 111 | json::Document header; 112 | auto err = header.parse(buffer.peek(), headerLen); 113 | if (err != json::PARSE_OK || 114 | !header.isInt32() || 115 | header.getInt32() <= 0) 116 | { 117 | throw RequestException(RPC_INVALID_REQUEST, "invalid message length"); 118 | } 119 | 120 | auto jsonLen = static_cast(header.getInt32()); 121 | if (jsonLen >= kMaxMessageLen) 122 | throw RequestException(RPC_INVALID_REQUEST, "message is too long"); 123 | 124 | if (buffer.readableBytes() < headerLen + jsonLen) 125 | break; 126 | 127 | buffer.retrieve(headerLen); 128 | auto json = buffer.retrieveAsString(jsonLen); 129 | convert().handleRequest(json, [conn, this](json::Value response) { 130 | if (!response.isNull()) { 131 | sendResponse(conn, response); 132 | TRACE("BaseServer::handleMessage() %s request success", 133 | conn->peer().toIpPort().c_str()); 134 | } 135 | else { 136 | TRACE("BaseServer::handleMessage() %s notify success", 137 | conn->peer().toIpPort().c_str()); 138 | } 139 | }); 140 | } 141 | } 142 | 143 | template 144 | json::Value BaseServer::wrapException(RequestException& e) 145 | { 146 | json::Value response(json::TYPE_OBJECT); 147 | response.addMember("jsonrpc", "2.0"); 148 | auto& value = response.addMember("error", json::TYPE_OBJECT); 149 | value.addMember("code", e.err().asCode()); 150 | value.addMember("message", e.err().asString()); 151 | value.addMember("data", e.detail()); 152 | response.addMember("id", e.id()); 153 | return response; 154 | } 155 | 156 | template 157 | void BaseServer::sendResponse(const TcpConnectionPtr& conn, const json::Value& response) 158 | { 159 | json::StringWriteStream os; 160 | json::Writer writer(os); 161 | response.writeTo(writer); 162 | 163 | // wish sso string don't allocate heap memory... 164 | auto message = std::to_string(os.get().length() + 2) 165 | .append("\r\n") 166 | .append(os.get()) 167 | .append("\r\n"); 168 | conn->send(message); 169 | } 170 | 171 | template 172 | ProtocolServer& BaseServer::convert() 173 | { 174 | return static_cast(*this); 175 | } 176 | 177 | template 178 | const ProtocolServer& BaseServer::convert() const 179 | { 180 | return static_cast(*this); 181 | } 182 | 183 | 184 | -------------------------------------------------------------------------------- /jrpc/server/BaseServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-29. 3 | // 4 | 5 | #ifndef JRPC_CONNECTION_MANAGER_H 6 | #define JRPC_CONNECTION_MANAGER_H 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace jrpc 14 | { 15 | 16 | class RequestException; 17 | 18 | template 19 | class BaseServer: noncopyable 20 | { 21 | public: 22 | void setNumThread(size_t n) { server_.setNumThread(n); } 23 | 24 | void start() { server_.start(); } 25 | 26 | protected: 27 | // CRTP pattern base class 28 | BaseServer(EventLoop* loop, const InetAddress& listen); 29 | // avoid this: 30 | // BaseServer* cm = &ProtocalServer; 31 | // delete cm; 32 | ~BaseServer() = default; 33 | 34 | private: 35 | void onConnection(const TcpConnectionPtr& conn); 36 | void onMessage(const TcpConnectionPtr& conn, Buffer& buffer); 37 | void onHighWatermark(const TcpConnectionPtr& conn, size_t mark); 38 | void onWriteComplete(const TcpConnectionPtr& conn); 39 | 40 | void handleMessage(const TcpConnectionPtr& conn, Buffer& buffer); 41 | 42 | void sendResponse(const TcpConnectionPtr& conn, const json::Value& response); 43 | 44 | ProtocolServer& convert(); 45 | const ProtocolServer& convert() const; 46 | 47 | protected: 48 | json::Value wrapException(RequestException& e); 49 | 50 | private: 51 | TcpServer server_; 52 | }; 53 | 54 | 55 | } 56 | 57 | 58 | #endif //JRPC_RPCSERVER_H 59 | -------------------------------------------------------------------------------- /jrpc/server/Procedure.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-31. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | using namespace jrpc; 9 | 10 | namespace jrpc 11 | { 12 | 13 | template class Procedure; 14 | template class Procedure; 15 | 16 | } 17 | 18 | template <> 19 | void Procedure::validateRequest(json::Value& request) const 20 | { 21 | switch (request.getType()) { 22 | case json::TYPE_OBJECT: 23 | case json::TYPE_ARRAY: 24 | if (!validateGeneric(request)) 25 | throw RequestException(RPC_INVALID_PARAMS, 26 | request["id"], 27 | "params name or type mismatch"); 28 | break; 29 | default: 30 | throw RequestException( 31 | RPC_INVALID_REQUEST, 32 | request["id"], 33 | "params type must be object or array"); 34 | } 35 | } 36 | 37 | template <> 38 | void Procedure::validateRequest(json::Value& request) const 39 | { 40 | switch (request.getType()) { 41 | case json::TYPE_OBJECT: 42 | case json::TYPE_ARRAY: 43 | if (!validateGeneric(request)) 44 | throw NotifyException(RPC_INVALID_PARAMS, 45 | "params name or type mismatch"); 46 | break; 47 | default: 48 | throw NotifyException(RPC_INVALID_REQUEST, 49 | "params type must be object or array"); 50 | } 51 | } 52 | 53 | template 54 | bool Procedure::validateGeneric(json::Value& request) const 55 | { 56 | auto mIter = request.findMember("params"); 57 | if (mIter == request.memberEnd()) { 58 | return params_.empty(); 59 | } 60 | 61 | auto& params = mIter->value; 62 | if (params.getSize() == 0 || params.getSize() != params_.size()) { 63 | return false; 64 | } 65 | 66 | switch (params.getType()) { 67 | case json::TYPE_ARRAY: 68 | for (size_t i = 0; i < params_.size(); i++) { 69 | if (params[i].getType() != params_[i].paramType) 70 | return false; 71 | } 72 | break; 73 | case json::TYPE_OBJECT: 74 | for (auto& p : params_) { 75 | auto it = params.findMember(p.paramName); 76 | if (it == params.memberEnd()) 77 | return false; 78 | if (it->value.getType() != p.paramType) 79 | return false; 80 | } 81 | break; 82 | default: 83 | return false; 84 | } 85 | return true; 86 | } 87 | 88 | template <> 89 | void Procedure::invoke(json::Value& request, 90 | const RpcDoneCallback& done) 91 | { 92 | validateRequest(request); 93 | callback_(request, done); 94 | } 95 | 96 | template <> 97 | void Procedure::invoke(json::Value& request) 98 | { 99 | validateRequest(request); 100 | callback_(request); 101 | } -------------------------------------------------------------------------------- /jrpc/server/Procedure.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-31. 3 | // 4 | 5 | #ifndef JRPC_PROCEDURE_H 6 | #define JRPC_PROCEDURE_H 7 | 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace jrpc 14 | { 15 | 16 | typedef std::function ProcedureReturnCallback; 17 | typedef std::function ProcedureNotifyCallback; 18 | 19 | template 20 | class Procedure: noncopyable 21 | { 22 | public: 23 | template 24 | explicit Procedure(Func&& callback, ParamNameAndTypes&&... nameAndTypes): 25 | callback_(std::forward(callback)) 26 | { 27 | constexpr int n = sizeof...(nameAndTypes); 28 | static_assert(n % 2 == 0, "procedure must have param name and type pairs"); 29 | 30 | if constexpr (n > 0) 31 | initProcedure(nameAndTypes...); 32 | } 33 | 34 | // procedure call 35 | void invoke(json::Value& request, const RpcDoneCallback& done); 36 | // procedure notify 37 | void invoke(json::Value& request); 38 | 39 | private: 40 | template 41 | void initProcedure(Name paramName, json::ValueType parmType, ParamNameAndTypes &&... nameAndTypes) 42 | { 43 | static_assert(std::is_same_v || 44 | std::is_same_v, 45 | "param name must be 'const char*' or 'std::string_view'"); 46 | params_.emplace_back(paramName, parmType); 47 | if constexpr (sizeof...(ParamNameAndTypes) > 0) 48 | initProcedure(nameAndTypes...); 49 | } 50 | 51 | template 52 | void initProcedure(Name paramName, Type parmType, ParamNameAndTypes &&... nameAndTypes) 53 | { 54 | static_assert(std::is_same_v, "param type must be json::ValueType"); 55 | } 56 | 57 | void validateRequest(json::Value& request) const; 58 | bool validateGeneric(json::Value& request) const; 59 | 60 | private: 61 | struct Param 62 | { 63 | Param(std::string_view paramName_, json::ValueType paramType_) : 64 | paramName(paramName_), 65 | paramType(paramType_) 66 | {} 67 | 68 | std::string_view paramName; 69 | json::ValueType paramType; 70 | }; 71 | 72 | private: 73 | Func callback_; 74 | std::vector params_; 75 | }; 76 | 77 | 78 | typedef Procedure ProcedureReturn; 79 | typedef Procedure ProcedureNotify; 80 | 81 | } 82 | 83 | #endif //JRPC_PROCEDURE_H 84 | -------------------------------------------------------------------------------- /jrpc/server/RpcServer.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-30. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace jrpc; 12 | 13 | namespace 14 | { 15 | 16 | template 17 | void checkValueType(json::ValueType type) 18 | { 19 | if (dst == type) 20 | return; 21 | if constexpr (sizeof...(rest) > 0) 22 | checkValueType(type); 23 | else 24 | throw RequestException(RPC_INVALID_REQUEST, "bad type of at least one field"); 25 | } 26 | 27 | template 28 | void checkValueType(json::ValueType type, json::Value& id) 29 | { 30 | // wrap exception 31 | try { 32 | checkValueType(type); 33 | } 34 | catch (RequestException& e) { 35 | throw RequestException(e.err(), id, e.detail()); 36 | } 37 | } 38 | 39 | template 40 | json::Value& findValue(json::Value &request, const char *key) 41 | { 42 | static_assert(sizeof...(types) > 0, "expect at least one type"); 43 | 44 | auto it = request.findMember(key); 45 | if (it == request.memberEnd()) 46 | throw RequestException(RPC_INVALID_REQUEST, "missing at least one field"); 47 | checkValueType(it->value.getType()); 48 | return it->value; 49 | } 50 | 51 | template 52 | json::Value& findValue(json::Value& request, json::Value& id, const char *key) 53 | { 54 | // wrap exception 55 | try { 56 | return findValue(request, key); 57 | } 58 | catch (RequestException &e) { 59 | throw RequestException(e.err(), id, e.detail()); 60 | } 61 | } 62 | 63 | bool isNotify(const json::Value& request) 64 | { 65 | return request.findMember("id") == request.memberEnd(); 66 | } 67 | 68 | bool hasParams(const json::Value& request) 69 | { 70 | return request.findMember("params") != request.memberEnd(); 71 | } 72 | 73 | // a thread safe batch response container 74 | // use shared_ptr to manage json value 75 | class ThreadSafeBatchResponse 76 | { 77 | public: 78 | explicit 79 | ThreadSafeBatchResponse(const RpcDoneCallback& done): 80 | data_(std::make_shared(done)) 81 | {} 82 | 83 | void addResponse(json::Value response) 84 | { 85 | std::unique_lock lock(data_->mutex); 86 | data_->responses.addValue(response); 87 | } 88 | 89 | private: 90 | struct ThreadSafeData 91 | { 92 | explicit 93 | ThreadSafeData(const RpcDoneCallback& done_): 94 | responses(json::TYPE_ARRAY), 95 | done(done_) 96 | {} 97 | 98 | ~ThreadSafeData() 99 | { 100 | // last reference to data is destructing, so notify RPC server we are done 101 | done(responses); 102 | } 103 | 104 | std::mutex mutex; 105 | json::Value responses; 106 | RpcDoneCallback done; 107 | }; 108 | 109 | typedef std::shared_ptr DataPtr; 110 | DataPtr data_; 111 | }; 112 | 113 | } 114 | 115 | void RpcServer::addService(std::string_view serviceName, RpcService *service) 116 | { 117 | assert(services_.find(serviceName) == services_.end()); 118 | services_.emplace(serviceName, service); 119 | } 120 | 121 | void RpcServer::handleRequest(const std::string& json, 122 | const RpcDoneCallback& done) 123 | { 124 | json::Document request; 125 | json::ParseError err = request.parse(json); 126 | if (err != json::PARSE_OK) 127 | throw RequestException(RPC_PARSE_ERROR, json::parseErrorStr(err)); 128 | 129 | switch (request.getType()) { 130 | case json::TYPE_OBJECT: 131 | if (isNotify(request)) 132 | handleSingleNotify(request); 133 | else 134 | handleSingleRequest(request, done); 135 | break; 136 | case json::TYPE_ARRAY: 137 | handleBatchRequests(request, done); 138 | break; 139 | default: 140 | throw RequestException(RPC_INVALID_REQUEST, "request should be json object or array"); 141 | } 142 | } 143 | 144 | void RpcServer::handleSingleRequest(json::Value& request, 145 | const RpcDoneCallback& done) 146 | { 147 | validateRequest(request); 148 | 149 | auto& id = request["id"]; 150 | auto methodName = request["method"].getStringView(); 151 | auto pos = methodName.find('.'); 152 | if (pos == std::string_view::npos) 153 | throw RequestException(RPC_INVALID_REQUEST, id, "missing service name in method"); 154 | 155 | auto serviceName = methodName.substr(0, pos); 156 | auto it = services_.find(serviceName); 157 | if (it == services_.end()) 158 | throw RequestException(RPC_METHOD_NOT_FOUND, id, "service not found"); 159 | 160 | // skip service name and '.' 161 | methodName.remove_prefix(pos + 1); 162 | if (methodName.length() == 0) 163 | throw RequestException(RPC_INVALID_REQUEST, id, "missing method name in method"); 164 | 165 | auto& service = it->second; 166 | service->callProcedureReturn(methodName, request, done); 167 | } 168 | 169 | void RpcServer::handleBatchRequests(json::Value& requests, 170 | const RpcDoneCallback& done) 171 | { 172 | size_t num = requests.getSize(); 173 | if (num == 0) 174 | throw RequestException(RPC_INVALID_REQUEST, "batch request is empty"); 175 | 176 | ThreadSafeBatchResponse responses(done); 177 | 178 | try { 179 | size_t n = requests.getSize(); 180 | for (size_t i = 0; i < n; i++) { 181 | 182 | auto& request = requests[i]; 183 | 184 | if (!request.isObject()) { 185 | throw RequestException(RPC_INVALID_REQUEST, "request should be json object"); 186 | } 187 | 188 | if (isNotify(request)) { 189 | handleSingleNotify(request); 190 | } 191 | else { 192 | handleSingleRequest(request, [=](json::Value response) mutable { 193 | responses.addResponse(response); 194 | }); 195 | } 196 | } 197 | } 198 | catch (RequestException &e) { 199 | auto response = wrapException(e); 200 | responses.addResponse(response); 201 | } 202 | catch (NotifyException &e) { 203 | // todo: print something here 204 | } 205 | } 206 | 207 | void RpcServer::handleSingleNotify(json::Value& request) 208 | { 209 | validateNotify(request); 210 | 211 | auto methodName = request["method"].getStringView(); 212 | auto pos = methodName.find('.'); 213 | if (pos == std::string_view::npos || pos == 0) 214 | throw NotifyException(RPC_INVALID_REQUEST, "missing service name in method"); 215 | 216 | auto serviceName = methodName.substr(0, pos); 217 | auto it = services_.find(serviceName); 218 | if (it == services_.end()) 219 | throw RequestException(RPC_METHOD_NOT_FOUND, "service not found"); 220 | 221 | // skip service name and '.' 222 | methodName.remove_prefix(pos + 1); 223 | if (methodName.length() == 0) 224 | throw RequestException(RPC_INVALID_REQUEST, "missing method name in method"); 225 | 226 | auto& service = it->second; 227 | service->callProcedureNotify(methodName, request); 228 | } 229 | 230 | void RpcServer::validateRequest(json::Value& request) 231 | { 232 | auto& id = findValue< 233 | json::TYPE_STRING, 234 | json::TYPE_NULL, // fixme: null id is evil 235 | json::TYPE_INT32, 236 | json::TYPE_INT64>(request, "id"); 237 | 238 | auto& version = findValue(request, id, "jsonrpc"); 239 | if (version.getStringView() != "2.0") 240 | throw RequestException(RPC_INVALID_REQUEST, 241 | id, "jsonrpc version is unknown/unsupported"); 242 | 243 | auto& method = findValue(request, id, "method"); 244 | if (method.getStringView() == "rpc.") // internal use 245 | throw RequestException(RPC_METHOD_NOT_FOUND, 246 | id, "method name is internal use"); 247 | 248 | // jsonrpc, method, id, params 249 | size_t nMembers = 3u + hasParams(request); 250 | 251 | if (request.getSize() != nMembers) 252 | throw RequestException(RPC_INVALID_REQUEST, id, "unexpected field"); 253 | } 254 | 255 | void RpcServer::validateNotify(json::Value& request) 256 | { 257 | auto& version = findValue(request, "jsonrpc"); 258 | if (version.getStringView() != "2.0") 259 | throw NotifyException(RPC_INVALID_REQUEST, "jsonrpc version is unknown/unsupported"); 260 | 261 | auto& method = findValue(request, "method"); 262 | if (method.getStringView() == "rpc.") // internal use 263 | throw NotifyException(RPC_METHOD_NOT_FOUND, "method name is internal use"); 264 | 265 | // jsonrpc, method, params, no id 266 | size_t nMembers = 2u + hasParams(request); 267 | 268 | if (request.getSize() != nMembers) 269 | throw NotifyException(RPC_INVALID_REQUEST, "unexpected field"); 270 | } 271 | -------------------------------------------------------------------------------- /jrpc/server/RpcServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-30. 3 | // 4 | 5 | #ifndef JRPC_RPCSERVER_H 6 | #define JRPC_RPCSERVER_H 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace jrpc 18 | { 19 | 20 | 21 | class RpcServer: public BaseServer 22 | { 23 | 24 | public: 25 | RpcServer(EventLoop* loop, const InetAddress& listen): 26 | BaseServer(loop, listen) 27 | {} 28 | ~RpcServer() = default; 29 | 30 | // used by user stub 31 | void addService(std::string_view serviceName, RpcService* service); 32 | 33 | // used by connection manager 34 | void handleRequest(const std::string& json, 35 | const RpcDoneCallback& done); 36 | 37 | private: 38 | void handleSingleRequest(json::Value& request, 39 | const RpcDoneCallback& done); 40 | void handleBatchRequests(json::Value& requests, 41 | const RpcDoneCallback& done); 42 | void handleSingleNotify(json::Value& request); 43 | 44 | void validateRequest(json::Value& request); 45 | void validateNotify(json::Value& request); 46 | 47 | private: 48 | typedef std::unique_ptr RpcServeicPtr; 49 | typedef std::unordered_map ServiceList; 50 | 51 | ServiceList services_; 52 | }; 53 | 54 | } 55 | 56 | #endif //JRPC_RPCSERVER_H 57 | -------------------------------------------------------------------------------- /jrpc/server/RpcService.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-30. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | using namespace jrpc; 9 | 10 | void RpcService::callProcedureReturn(std::string_view methodName, 11 | json::Value& request, 12 | const RpcDoneCallback& done) 13 | { 14 | auto it = procedureReturn_.find(methodName); 15 | if (it == procedureReturn_.end()) { 16 | throw RequestException(RPC_METHOD_NOT_FOUND, 17 | request["id"], 18 | "method not found"); 19 | } 20 | it->second->invoke(request, done); 21 | }; 22 | 23 | void RpcService::callProcedureNotify(std::string_view methodName, json::Value& request) 24 | { 25 | auto it = procedureNotfiy_.find(methodName); 26 | if (it == procedureNotfiy_.end()) { 27 | throw NotifyException(RPC_METHOD_NOT_FOUND, 28 | "method not found"); 29 | } 30 | it->second->invoke(request); 31 | }; -------------------------------------------------------------------------------- /jrpc/server/RpcService.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-30. 3 | // 4 | 5 | #ifndef JRPC_RPCSERVICE_H 6 | #define JRPC_RPCSERVICE_H 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace jrpc 13 | { 14 | 15 | 16 | class RpcService: noncopyable 17 | { 18 | public: 19 | void addProcedureReturn(std::string_view methodName, ProcedureReturn* p) 20 | { 21 | assert(procedureReturn_.find(methodName) == procedureReturn_.end()); 22 | procedureReturn_.emplace(methodName, p); 23 | } 24 | 25 | void addProcedureNotify(std::string_view methodName, ProcedureNotify *p) 26 | { 27 | assert(procedureNotfiy_.find(methodName) == procedureNotfiy_.end()); 28 | procedureNotfiy_.emplace(methodName, p); 29 | } 30 | 31 | void callProcedureReturn(std::string_view methodName, 32 | json::Value& request, 33 | const RpcDoneCallback& done); 34 | void callProcedureNotify(std::string_view methodName, json::Value& request); 35 | 36 | private: 37 | typedef std::unique_ptr ProcedureReturnPtr; 38 | typedef std::unique_ptr ProcedureNotifyPtr; 39 | typedef std::unordered_map ProcedureReturnList; 40 | typedef std::unordered_map ProcedureNotifyList; 41 | 42 | ProcedureReturnList procedureReturn_; 43 | ProcedureNotifyList procedureNotfiy_; 44 | }; 45 | 46 | 47 | } 48 | 49 | 50 | #endif //JRPC_RPCSERVICE_H 51 | -------------------------------------------------------------------------------- /jrpc/stub/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(jrpcstub 2 | StubGenerator.h 3 | StubGenerator.cc 4 | ServiceStubGenerator.h 5 | ServiceStubGenerator.cc 6 | ClientStubGenerator.h 7 | ClientStubGenerator.cc 8 | main.cc 9 | ) 10 | 11 | target_link_libraries(jrpcstub jackson) 12 | install(TARGETS jrpcstub DESTINATION bin) -------------------------------------------------------------------------------- /jrpc/stub/ClientStubGenerator.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-24. 3 | // 4 | 5 | #include "ClientStubGenerator.h" 6 | 7 | using namespace jrpc; 8 | 9 | namespace 10 | { 11 | 12 | std::string clientStubTemplate( 13 | const std::string& macroName, 14 | const std::string& stubClassName, 15 | const std::string& procedureDefinitions, 16 | const std::string& notifyDefinitions) 17 | { 18 | std::string str = R"( 19 | /* 20 | * This stub is generated by jrpc, DO NOT modify it! 21 | */ 22 | 23 | #ifndef JRPC_[macroName]_H 24 | #define JRPC_[macroName]_H 25 | 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | namespace jrpc 32 | { 33 | 34 | class [stubClassName]: noncopyable 35 | { 36 | public: 37 | [stubClassName](EventLoop* loop, const InetAddress& serverAddress): 38 | client_(loop, serverAddress) 39 | { 40 | client_.setConnectionCallback([this](const TcpConnectionPtr& conn){ 41 | if (conn->connected()) { 42 | INFO("connected"); 43 | conn_ = conn; 44 | cb_(conn_); 45 | } 46 | else { 47 | INFO("disconnected"); 48 | assert(conn_ != nullptr); 49 | cb_(conn_); 50 | } 51 | }); 52 | } 53 | 54 | ~[stubClassName]() = default; 55 | 56 | void start() { client_.start(); } 57 | 58 | void setConnectionCallback(const ConnectionCallback& cb) 59 | { 60 | cb_ = cb; 61 | } 62 | 63 | [procedureDefinitions] 64 | [notifyDefinitions] 65 | 66 | private: 67 | TcpConnectionPtr conn_; 68 | ConnectionCallback cb_; 69 | BaseClient client_; 70 | }; 71 | 72 | } 73 | #endif //JRPC_[macroName]_H 74 | )"; 75 | replaceAll(str, "[macroName]", macroName); 76 | replaceAll(str, "[stubClassName]", stubClassName); 77 | replaceAll(str, "[procedureDefinitions]", procedureDefinitions); 78 | replaceAll(str, "[notifyDefinitions]", notifyDefinitions); 79 | return str; 80 | } 81 | 82 | 83 | std::string procedureDefineTemplate( 84 | const std::string& serviceName, 85 | const std::string& procedureName, 86 | const std::string& procedureArgs, 87 | const std::string& paramMembers) 88 | { 89 | std::string str = R"( 90 | void [procedureName]([procedureArgs] const ResponseCallback& cb) 91 | { 92 | json::Value params(json::TYPE_OBJECT); 93 | [paramMembers] 94 | 95 | json::Value call(json::TYPE_OBJECT); 96 | call.addMember("jsonrpc", "2.0"); 97 | call.addMember("method", "[serviceName].[procedureName]"); 98 | call.addMember("params", params); 99 | 100 | assert(conn_ != nullptr); 101 | client_.sendCall(conn_, call, cb); 102 | } 103 | )"; 104 | replaceAll(str, "[serviceName]", serviceName); 105 | replaceAll(str, "[procedureName]", procedureName); 106 | replaceAll(str, "[procedureArgs]", procedureArgs); 107 | replaceAll(str, "[paramMembers]", paramMembers); 108 | return str; 109 | } 110 | 111 | std::string notifyDefineTemplate( 112 | const std::string& serviceName, 113 | const std::string& notifyName, 114 | const std::string& notifyArgs, 115 | const std::string& paramMembers) 116 | { 117 | std::string str = R"( 118 | void [notifyName]([notifyArgs]) 119 | { 120 | json::Value params(json::TYPE_OBJECT); 121 | [paramMembers] 122 | 123 | json::Value notify(json::TYPE_OBJECT); 124 | notify.addMember("jsonrpc", "2.0"); 125 | notify.addMember("method", "Hello.Goodbye"); 126 | 127 | assert(conn_ != nullptr); 128 | client_.sendNotify(conn_, notify); 129 | } 130 | )"; 131 | replaceAll(str, "[serviceName]", serviceName); 132 | replaceAll(str, "[notifyName]", notifyName); 133 | replaceAll(str, "[notifyArgs]", notifyArgs); 134 | replaceAll(str, "[paramMembers]", paramMembers); 135 | return str; 136 | } 137 | 138 | std::string paramMemberTemplate(const std::string& paramName) 139 | { 140 | std::string str = R"( 141 | params.addMember("[paramName]", [paramName]); 142 | )"; 143 | replaceAll(str, "[paramName]", paramName); 144 | return str; 145 | } 146 | 147 | std::string argTemplate( 148 | const std::string& argName, 149 | json::ValueType argType) 150 | { 151 | std::string str = R"([argType] [argName])"; 152 | auto typeStr = [=](){ 153 | switch (argType) { 154 | case json::TYPE_INT32: 155 | return "int32_t"; 156 | case json::TYPE_INT64: 157 | return "int64_t"; 158 | case json::TYPE_DOUBLE: 159 | return "double"; 160 | case json::TYPE_BOOL: 161 | return "bool"; 162 | case json::TYPE_STRING: 163 | return "std::string"; 164 | case json::TYPE_OBJECT: 165 | case json::TYPE_ARRAY: 166 | return "json::Value"; 167 | default: 168 | assert(false && "bad arg type"); 169 | return "bad type"; 170 | } 171 | }(); 172 | replaceAll(str, "[argType]", typeStr); 173 | replaceAll(str, "[argName]", argName); 174 | return str; 175 | } 176 | 177 | } 178 | 179 | std::string ClientStubGenerator::genStub() 180 | { 181 | auto macroName = genMacroName(); 182 | auto stubClassName = genStubClassName(); 183 | auto procedureDefinitions = genProcedureDefinitions(); 184 | auto notifyDefinitions = genNotifyDefinitions(); 185 | 186 | return clientStubTemplate(macroName, 187 | stubClassName, 188 | procedureDefinitions, 189 | notifyDefinitions); 190 | } 191 | 192 | std::string ClientStubGenerator::genMacroName() 193 | { 194 | std::string result = serviceInfo_.name; 195 | for (char& c: result) 196 | c = static_cast(toupper(c)); 197 | return result + "CLIENTSTUB"; 198 | } 199 | 200 | std::string ClientStubGenerator::genStubClassName() 201 | { 202 | return serviceInfo_.name + "ClientStub"; 203 | } 204 | 205 | std::string ClientStubGenerator::genProcedureDefinitions() 206 | { 207 | std::string result; 208 | 209 | auto& serviceName = serviceInfo_.name; 210 | 211 | for (auto& r: serviceInfo_.rpcReturn) { 212 | auto& procedureName = r.name; 213 | auto procedureArgs = genGenericArgs(r, true); 214 | auto paramMembers = genGenericParamMembers(r); 215 | 216 | auto str = procedureDefineTemplate( 217 | serviceName, 218 | procedureName, 219 | procedureArgs, 220 | paramMembers); 221 | result.append(str); 222 | } 223 | return result; 224 | } 225 | 226 | std::string ClientStubGenerator::genNotifyDefinitions() 227 | { 228 | std::string result; 229 | 230 | auto& serviceName = serviceInfo_.name; 231 | 232 | for (auto& r: serviceInfo_.rpcNotify) { 233 | auto& notifyName = r.name; 234 | auto notifyArgs = genGenericArgs(r, false); 235 | auto paramMembers = genGenericParamMembers(r); 236 | 237 | auto str = notifyDefineTemplate( 238 | serviceName, 239 | notifyName, 240 | notifyArgs, 241 | paramMembers); 242 | result.append(str); 243 | } 244 | return result; 245 | } 246 | 247 | template 248 | std::string ClientStubGenerator::genGenericArgs(const Rpc& r, bool appendComma) 249 | { 250 | std::string result; 251 | bool first = true; 252 | for (auto& p: r.params.getObject()) { 253 | std::string one = argTemplate(p.key.getString(), 254 | p.value.getType()); 255 | if (first) { 256 | first = false; 257 | } 258 | else { 259 | result.append(", "); 260 | } 261 | result.append(one); 262 | } 263 | if (appendComma && !first) 264 | result.append(","); 265 | return result; 266 | } 267 | 268 | template 269 | std::string ClientStubGenerator::genGenericParamMembers(const Rpc& r) 270 | { 271 | std::string result; 272 | for (auto& p: r.params.getObject()) { 273 | std::string one = paramMemberTemplate( 274 | p.key.getString()); 275 | result.append(one); 276 | } 277 | return result; 278 | } -------------------------------------------------------------------------------- /jrpc/stub/ClientStubGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-24. 3 | // 4 | 5 | #ifndef JRPC_CLIENTSTUBGENERATOR_H 6 | #define JRPC_CLIENTSTUBGENERATOR_H 7 | 8 | #include 9 | 10 | namespace jrpc 11 | { 12 | 13 | class ClientStubGenerator: public StubGenerator 14 | { 15 | public: 16 | explicit 17 | ClientStubGenerator(json::Value& proto): 18 | StubGenerator(proto) 19 | {} 20 | 21 | std::string genStub() override; 22 | std::string genStubClassName() override; 23 | 24 | private: 25 | std::string genMacroName(); 26 | 27 | std::string genProcedureDefinitions(); 28 | std::string genNotifyDefinitions(); 29 | 30 | template 31 | std::string genGenericArgs(const Rpc& r, bool appendComma); 32 | template 33 | std::string genGenericParamMembers(const Rpc& r); 34 | }; 35 | 36 | } 37 | 38 | 39 | #endif //JRPC_CLIENTSTUBGENERATOR_H 40 | -------------------------------------------------------------------------------- /jrpc/stub/ServiceStubGenerator.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-23. 3 | // 4 | 5 | #include 6 | 7 | using namespace jrpc; 8 | 9 | namespace 10 | { 11 | 12 | std::string serviceStubTemplate( 13 | const std::string& macroName, 14 | const std::string& userClassName, 15 | const std::string& stubClassName, 16 | const std::string& serviceName, 17 | const std::string& stubProcedureBindings, 18 | const std::string& stubProcedureDefinitions) 19 | { 20 | std::string str = 21 | R"( 22 | /* 23 | * This stub is generated by jrpc, DO NOT modify it! 24 | */ 25 | #ifndef JRPC_[macroName]_H 26 | #define JRPC_[macroName]_H 27 | 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | class [userClassName]; 35 | 36 | namespace jrpc 37 | { 38 | 39 | template 40 | class [stubClassName]: noncopyable 41 | { 42 | protected: 43 | explicit 44 | [stubClassName](RpcServer& server) 45 | { 46 | static_assert(std::is_same_v, 47 | "derived class name should be '[userClassName]'"); 48 | 49 | auto service = new RpcService; 50 | 51 | [stubProcedureBindings] 52 | 53 | server.addService("[serviceName]", service); 54 | } 55 | 56 | ~[stubClassName]() = default; 57 | 58 | private: 59 | [stubProcedureDefinitions] 60 | 61 | private: 62 | S& convert() 63 | { 64 | return static_cast(*this); 65 | } 66 | }; 67 | 68 | } 69 | 70 | #endif //JRPC_[macroName]_H 71 | )"; 72 | 73 | replaceAll(str, "[macroName]", macroName); 74 | replaceAll(str, "[userClassName]", userClassName); 75 | replaceAll(str, "[stubClassName]", stubClassName); 76 | replaceAll(str, "[serviceName]", serviceName); 77 | replaceAll(str, "[stubProcedureBindings]", stubProcedureBindings); 78 | replaceAll(str, "[stubProcedureDefinitions]", stubProcedureDefinitions); 79 | return str; 80 | } 81 | 82 | std::string stubProcedureBindTemplate( 83 | const std::string& procedureName, 84 | const std::string& stubClassName, 85 | const std::string& stubProcedureName, 86 | const std::string& procedureParams) 87 | { 88 | std::string str = 89 | R"( 90 | service->addProcedureReturn("[procedureName]", new ProcedureReturn( 91 | std::bind(&[stubClassName]::[stubProcedureName], this, _1, _2) 92 | [procedureParams] 93 | )); 94 | )"; 95 | 96 | replaceAll(str, "[procedureName]", procedureName); 97 | replaceAll(str, "[stubClassName]", stubClassName); 98 | replaceAll(str, "[stubProcedureName]", stubProcedureName); 99 | replaceAll(str, "[procedureParams]", procedureParams); 100 | return str; 101 | } 102 | 103 | std::string stubNotifyBindTemplate( 104 | const std::string& notifyName, 105 | const std::string& stubClassName, 106 | const std::string& stubNotifyName, 107 | const std::string& notifyParams) 108 | { 109 | std::string str = 110 | R"( 111 | service->addProcedureNotify("[notifyName]", new ProcedureNotify( 112 | std::bind(&[stubClassName]::[stubNotifyName], this, _1) 113 | [notifyParams] 114 | )); 115 | )"; 116 | 117 | replaceAll(str, "[notifyName]", notifyName); 118 | replaceAll(str, "[stubClassName]", stubClassName); 119 | replaceAll(str, "[stubNotifyName]", stubNotifyName); 120 | replaceAll(str, "[notifyParams]", notifyParams); 121 | return str; 122 | } 123 | 124 | std::string stubProcedureDefineTemplate( 125 | const std::string& paramsFromJsonArray, 126 | const std::string& paramsFromJsonObject, 127 | const std::string& stubProcedureName, 128 | const std::string& procedureName, 129 | const std::string& procedureArgs) 130 | { 131 | std::string str = 132 | R"(void [stubProcedureName](json::Value& request, const RpcDoneCallback& done) 133 | { 134 | auto& params = request["params"]; 135 | 136 | if (params.isArray()) { 137 | [paramsFromJsonArray] 138 | convert().[procedureName]([procedureArgs] UserDoneCallback(request, done)); 139 | } 140 | else { 141 | [paramsFromJsonObject] 142 | convert().[procedureName]([procedureArgs] UserDoneCallback(request, done)); 143 | } 144 | })"; 145 | 146 | replaceAll(str, "[paramsFromJsonArray]", paramsFromJsonArray); 147 | replaceAll(str, "[paramsFromJsonObject]", paramsFromJsonObject); 148 | replaceAll(str, "[stubProcedureName]", stubProcedureName); 149 | replaceAll(str, "[procedureName]", procedureName); 150 | replaceAll(str, "[procedureArgs]", procedureArgs); 151 | return str; 152 | } 153 | 154 | std::string stubProcedureDefineTemplate( 155 | const std::string& stubProcedureName, 156 | const std::string& procedureName) 157 | { 158 | std::string str = 159 | R"( 160 | void [stubProcedureName](json::Value& request, const RpcDoneCallback& done) 161 | { 162 | convert().[procedureName](UserDoneCallback(request, done)); 163 | } 164 | )"; 165 | 166 | replaceAll(str, "[stubProcedureName]", stubProcedureName); 167 | replaceAll(str, "[procedureName]", procedureName); 168 | return str; 169 | } 170 | 171 | std::string stubNotifyDefineTemplate( 172 | const std::string& paramsFromJsonArray, 173 | const std::string& paramsFromJsonObject, 174 | const std::string& stubNotifyName, 175 | const std::string& notifyName, 176 | const std::string& notifyArgs) 177 | { 178 | std::string str = 179 | R"( 180 | void [stubNotifyName](json::Value& request) 181 | { 182 | auto& params = request["params"]; 183 | 184 | if (params.isArray()) { 185 | [paramsFromJsonArray] 186 | convert().[NotifyName]([notifyArgs]); 187 | } 188 | else { 189 | [paramsFromJsonObject] 190 | convert().[NotifyName]([notifyArgs]); 191 | } 192 | } 193 | )"; 194 | 195 | replaceAll(str, "[notifyName]", notifyName); 196 | replaceAll(str, "[stubNotifyName]", stubNotifyName); 197 | replaceAll(str, "[notifyArgs]", notifyArgs); 198 | replaceAll(str, "[paramsFromJsonArray]", paramsFromJsonArray); 199 | replaceAll(str, "[paramsFromJsonObject]", paramsFromJsonObject); 200 | return str; 201 | } 202 | 203 | std::string stubNotifyDefineTemplate( 204 | const std::string& stubNotifyName, 205 | const std::string& notifyName) 206 | { 207 | std::string str = 208 | R"( 209 | void [stubNotifyName](json::Value& request) 210 | { 211 | convert().[notifyName](); 212 | } 213 | )"; 214 | 215 | replaceAll(str, "[stubNotifyName]", stubNotifyName); 216 | replaceAll(str, "[notifyName]", notifyName); 217 | return str; 218 | } 219 | 220 | std::string argsDefineTemplate( 221 | const std::string& arg, 222 | const std::string& index, 223 | json::ValueType type) 224 | { 225 | std::string str = R"(auto [arg] = params[[index]][method];)"; 226 | std::string method = [=](){ 227 | switch (type) { 228 | case json::TYPE_BOOL: 229 | return ".getBool()"; 230 | case json::TYPE_INT32: 231 | return ".getInt32()"; 232 | case json::TYPE_INT64: 233 | return ".getInt64()"; 234 | case json::TYPE_DOUBLE: 235 | return ".getDouble()"; 236 | case json::TYPE_STRING: 237 | return ".getString()"; 238 | case json::TYPE_OBJECT: 239 | case json::TYPE_ARRAY: 240 | return "";//todo 241 | default: 242 | assert(false && "bad value type"); 243 | return "bad type"; 244 | } 245 | }(); 246 | replaceAll(str, "[arg]", arg); 247 | replaceAll(str, "[index]", index); 248 | replaceAll(str, "[method]", method); 249 | return str; 250 | } 251 | 252 | } 253 | 254 | 255 | std::string ServiceStubGenerator::genStub() 256 | { 257 | auto macroName = genMacroName(); 258 | auto userClassName = genUserClassName(); 259 | auto stubClassName = genStubClassName(); 260 | auto& serviceName = serviceInfo_.name; 261 | 262 | auto bindings = genStubProcedureBindings(); 263 | bindings.append(genStubNotifyBindings()); 264 | 265 | auto definitions = genStubProcedureDefinitions(); 266 | definitions.append(genStubNotifyDefinitions()); 267 | 268 | return serviceStubTemplate(macroName, 269 | userClassName, 270 | stubClassName, 271 | serviceName, 272 | bindings, 273 | definitions); 274 | } 275 | 276 | std::string ServiceStubGenerator::genMacroName() 277 | { 278 | std::string result = serviceInfo_.name; 279 | for (char& c: result) 280 | c = static_cast(toupper(c)); 281 | return result.append("SERVICESTUB"); 282 | } 283 | 284 | std::string ServiceStubGenerator::genUserClassName() 285 | { 286 | return serviceInfo_.name + "Service"; 287 | } 288 | 289 | std::string ServiceStubGenerator::genStubClassName() 290 | { 291 | return serviceInfo_.name + "ServiceStub"; 292 | } 293 | 294 | std::string ServiceStubGenerator::genStubProcedureBindings() 295 | { 296 | std::string result; 297 | for (auto& p: serviceInfo_.rpcReturn) { 298 | auto procedureName = p.name; 299 | auto stubClassName = genStubClassName(); 300 | auto stubProcedureName = genStubGenericName(p); 301 | auto procedureParams = genGenericParams(p); 302 | 303 | auto binding = stubProcedureBindTemplate( 304 | procedureName, 305 | stubClassName, 306 | stubProcedureName, 307 | procedureParams); 308 | result.append(binding); 309 | result.append("\n"); 310 | } 311 | return result; 312 | } 313 | 314 | 315 | std::string ServiceStubGenerator::genStubProcedureDefinitions() 316 | { 317 | std::string result; 318 | for (auto& r: serviceInfo_.rpcReturn) { 319 | auto procedureName = r.name; 320 | auto stubProcedureName = genStubGenericName(r); 321 | 322 | if (r.params.getSize() > 0) { 323 | auto paramsFromJsonArray = genParamsFromJsonArray(r); 324 | auto paramsFromJsonObject = genParamsFromJsonObject(r); 325 | auto procedureArgs = genGenericArgs(r); 326 | auto define = stubProcedureDefineTemplate( 327 | paramsFromJsonArray, 328 | paramsFromJsonObject, 329 | stubProcedureName, 330 | procedureName, 331 | procedureArgs); 332 | 333 | result.append(define); 334 | result.append("\n"); 335 | } 336 | else { 337 | auto define = stubProcedureDefineTemplate( 338 | stubProcedureName, 339 | procedureName); 340 | 341 | result.append(define); 342 | result.append("\n"); 343 | } 344 | } 345 | return result; 346 | } 347 | 348 | std::string ServiceStubGenerator::genStubNotifyBindings() 349 | { 350 | std::string result; 351 | for (auto& p: serviceInfo_.rpcNotify) { 352 | auto notifyName = p.name; 353 | auto stubClassName = genStubClassName(); 354 | auto stubNotifyName = genStubGenericName(p); 355 | auto notifyParams = genGenericParams(p); 356 | 357 | auto binding = stubNotifyBindTemplate( 358 | notifyName, 359 | stubClassName, 360 | stubNotifyName, 361 | notifyParams); 362 | result.append(binding); 363 | result.append("\n"); 364 | } 365 | return result; 366 | } 367 | 368 | std::string ServiceStubGenerator::genStubNotifyDefinitions() 369 | { 370 | std::string result; 371 | for (auto& r: serviceInfo_.rpcNotify) { 372 | auto notifyName = r.name; 373 | auto stubNotifyName = genStubGenericName(r); 374 | 375 | if (r.params.getSize() > 0) { 376 | auto paramsFromJsonArray = genParamsFromJsonArray(r); 377 | auto paramsFromJsonObject = genParamsFromJsonObject(r); 378 | auto notifyArgs = genGenericArgs(r); 379 | auto define = stubNotifyDefineTemplate( 380 | paramsFromJsonArray, 381 | paramsFromJsonObject, 382 | stubNotifyName, 383 | notifyName, 384 | notifyArgs); 385 | 386 | result.append(define); 387 | result.append("\n"); 388 | } 389 | else { 390 | auto define = stubNotifyDefineTemplate( 391 | stubNotifyName, 392 | notifyName); 393 | 394 | result.append(define); 395 | result.append("\n"); 396 | } 397 | } 398 | return result; 399 | } 400 | 401 | template 402 | std::string ServiceStubGenerator::genStubGenericName(const Rpc& r) 403 | { 404 | return r.name + "Stub"; 405 | } 406 | 407 | template 408 | std::string ServiceStubGenerator::genGenericParams(const Rpc& r) 409 | { 410 | std::string result; 411 | 412 | for (auto& m: r.params.getObject()) { 413 | std::string field = "\"" + m.key.getString() + "\""; 414 | std::string type = [&]() { 415 | switch (m.value.getType()) { 416 | case json::TYPE_BOOL: 417 | return "json::TYPE_BOOL"; 418 | case json::TYPE_INT32: 419 | return "json::TYPE_INT32"; 420 | case json::TYPE_INT64: 421 | return "json::TYPE_INT64"; 422 | case json::TYPE_DOUBLE: 423 | return "json::TYPE_DOUBLE"; 424 | case json::TYPE_STRING: 425 | return "json::TYPE_STRING"; 426 | case json::TYPE_OBJECT: 427 | return "json::TYPE_OBJECT"; 428 | case json::TYPE_ARRAY: 429 | return "json::TYPE_ARRAY"; 430 | default: 431 | assert(false && "bad value type"); 432 | return "bad type"; 433 | }; 434 | }(); 435 | result.append(", \n").append(field); 436 | result.append(", ").append(type); 437 | } 438 | return result; 439 | } 440 | 441 | template 442 | std::string ServiceStubGenerator::genGenericArgs(const Rpc& r) 443 | { 444 | std::string result; 445 | for (auto& m: r.params.getObject()) { 446 | auto arg = m.key.getString(); 447 | result.append(arg); 448 | result.append(", "); 449 | } 450 | return result; 451 | } 452 | 453 | template 454 | std::string ServiceStubGenerator::genParamsFromJsonArray(const Rpc& r) 455 | { 456 | std::string result; 457 | int index = 0; 458 | for (auto& m: r.params.getObject()) { 459 | std::string line = argsDefineTemplate( 460 | m.key.getString(), 461 | std::to_string(index), 462 | m.value.getType()); 463 | index++; 464 | result.append(line); 465 | result.append("\n"); 466 | } 467 | return result; 468 | } 469 | 470 | template 471 | std::string ServiceStubGenerator::genParamsFromJsonObject(const Rpc& r) 472 | { 473 | std::string result; 474 | for (auto& m: r.params.getObject()) { 475 | std::string index = "\"" + m.key.getString() + "\""; 476 | std::string line = argsDefineTemplate( 477 | m.key.getString(), 478 | index, 479 | m.value.getType()); 480 | result.append(line); 481 | result.append("\n"); 482 | } 483 | return result; 484 | } -------------------------------------------------------------------------------- /jrpc/stub/ServiceStubGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-23. 3 | // 4 | 5 | #ifndef JRPC_SERVICESTUBGENERATOR_H 6 | #define JRPC_SERVICESTUBGENERATOR_H 7 | 8 | #include 9 | 10 | namespace jrpc 11 | { 12 | 13 | class ServiceStubGenerator: public StubGenerator 14 | { 15 | public: 16 | explicit 17 | ServiceStubGenerator(json::Value& proto): 18 | StubGenerator(proto) 19 | {} 20 | 21 | std::string genStub() override; 22 | std::string genStubClassName() override; 23 | 24 | private: 25 | std::string genMacroName(); 26 | std::string genUserClassName(); 27 | std::string genStubProcedureBindings(); 28 | std::string genStubProcedureDefinitions(); 29 | std::string genStubNotifyBindings(); 30 | std::string genStubNotifyDefinitions(); 31 | 32 | template 33 | std::string genStubGenericName(const Rpc& r); 34 | template 35 | std::string genGenericParams(const Rpc& r); 36 | template 37 | std::string genGenericArgs(const Rpc& r); 38 | 39 | template 40 | std::string genParamsFromJsonArray(const Rpc& r); 41 | template 42 | std::string genParamsFromJsonObject(const Rpc& r); 43 | }; 44 | 45 | } 46 | 47 | 48 | 49 | #endif //JRPC_SERVICESTUBGENERATOR_H 50 | -------------------------------------------------------------------------------- /jrpc/stub/StubGenerator.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-23. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | using namespace jrpc; 11 | 12 | namespace 13 | { 14 | 15 | void expect(bool result, const char* errMsg) 16 | { 17 | if (!result) { 18 | throw StubException(errMsg); 19 | } 20 | } 21 | 22 | } 23 | 24 | void StubGenerator::parseProto(json::Value& proto) 25 | { 26 | expect(proto.isObject(), 27 | "expect object"); 28 | expect(proto.getSize() == 2, 29 | "expect 'name' and 'rpc' fields in object"); 30 | 31 | auto name = proto.findMember("name"); 32 | 33 | expect(name != proto.memberEnd(), 34 | "missing service name"); 35 | expect(name->value.isString(), 36 | "service name must be string"); 37 | serviceInfo_.name = name->value.getString(); 38 | 39 | auto rpc = proto.findMember("rpc"); 40 | expect(rpc != proto.memberEnd(), 41 | "missing service rpc definition"); 42 | expect(rpc->value.isArray(), 43 | "rpc field must be array"); 44 | 45 | size_t n = rpc->value.getSize(); 46 | for (size_t i = 0; i < n; i++) { 47 | parseRpc(rpc->value[i]); 48 | } 49 | } 50 | 51 | void StubGenerator::parseRpc(json::Value& rpc) 52 | { 53 | expect(rpc.isObject(), 54 | "rpc definition must be object"); 55 | 56 | auto name = rpc.findMember("name"); 57 | expect(name != rpc.memberEnd(), 58 | "missing name in rpc definition"); 59 | expect(name->value.isString(), 60 | "rpc name must be string"); 61 | 62 | auto params = rpc.findMember("params"); 63 | bool hasParams = params != rpc.memberEnd(); 64 | if (hasParams) { 65 | validateParams(params->value); 66 | } 67 | 68 | auto returns = rpc.findMember("returns"); 69 | bool hasReturns = returns != rpc.memberEnd(); 70 | if (hasReturns) { 71 | validateReturns(returns->value); 72 | } 73 | 74 | auto paramsValue = hasParams ? 75 | params->value : 76 | json::Value(json::TYPE_OBJECT); 77 | 78 | if (hasReturns) { 79 | RpcReturn r(name->value.getString(), paramsValue, returns->value); 80 | serviceInfo_.rpcReturn.push_back(r); 81 | } 82 | else { 83 | RpcNotify r(name->value.getString(), paramsValue); 84 | serviceInfo_.rpcNotify.push_back(r); 85 | } 86 | } 87 | 88 | void StubGenerator::validateParams(json::Value& params) 89 | { 90 | std::unordered_set set; 91 | 92 | for (auto& p: params.getObject()) { 93 | 94 | auto key = p.key.getStringView(); 95 | auto unique = set.insert(key).second; 96 | expect(unique, "duplicate param name"); 97 | 98 | switch (p.value.getType()) { 99 | case json::TYPE_NULL: 100 | expect(false, "bad param type"); 101 | break; 102 | default: 103 | break; 104 | } 105 | } 106 | } 107 | 108 | void StubGenerator::validateReturns(json::Value& returns) 109 | { 110 | switch (returns.getType()) { 111 | case json::TYPE_NULL: 112 | case json::TYPE_ARRAY: 113 | expect(false, "bad returns type"); 114 | break; 115 | case json::TYPE_OBJECT: 116 | validateParams(returns); 117 | break; 118 | default: 119 | break; 120 | } 121 | } -------------------------------------------------------------------------------- /jrpc/stub/StubGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-23. 3 | // 4 | 5 | #ifndef JRPC_STUBGENERATOR_H 6 | #define JRPC_STUBGENERATOR_H 7 | 8 | #include 9 | 10 | namespace jrpc 11 | { 12 | 13 | class StubGenerator 14 | { 15 | public: 16 | explicit 17 | StubGenerator(json::Value& proto) 18 | { 19 | parseProto(proto); 20 | } 21 | virtual ~StubGenerator() = default; 22 | 23 | public: 24 | virtual std::string genStub() = 0; 25 | virtual std::string genStubClassName() = 0; 26 | 27 | protected: 28 | struct RpcReturn 29 | { 30 | RpcReturn(const std::string& name_, 31 | json::Value& params_, 32 | json::Value& returns_): 33 | name(name_), 34 | params(params_), 35 | returns(returns_) 36 | {} 37 | 38 | std::string name; 39 | mutable json::Value params; 40 | mutable json::Value returns; 41 | }; 42 | 43 | struct RpcNotify 44 | { 45 | RpcNotify(const std::string& name_, 46 | json::Value& params_): 47 | name(name_), 48 | params(params_) 49 | {} 50 | 51 | std::string name; 52 | mutable json::Value params; 53 | }; 54 | 55 | struct ServiceInfo 56 | { 57 | std::string name; 58 | std::vector rpcReturn; 59 | std::vector rpcNotify; 60 | }; 61 | 62 | ServiceInfo serviceInfo_; 63 | 64 | private: 65 | void parseProto(json::Value& proto); 66 | void parseRpc(json::Value& rpc); 67 | void validateParams(json::Value& params); 68 | void validateReturns(json::Value& returns); 69 | }; 70 | 71 | inline void replaceAll(std::string& str, const std::string& from, const std::string& to) 72 | { 73 | while (true) { 74 | size_t i = str.find(from); 75 | if (i != std::string::npos) { 76 | str.replace(i, from.size(), to); 77 | } 78 | else return; 79 | } 80 | } 81 | 82 | } 83 | 84 | #endif //JRPC_STUBGENERATOR_H 85 | -------------------------------------------------------------------------------- /jrpc/stub/main.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 18-1-24. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace jrpc; 15 | 16 | static void usage() 17 | { 18 | fprintf(stderr, 19 | "usage: stub_generator <-c/s> [-o] [-i input]\n"); 20 | exit(1); 21 | } 22 | 23 | static void writeToFile(StubGenerator& generator, bool outputToFile) 24 | { 25 | FILE* output; 26 | if (!outputToFile) { 27 | output = stdout; 28 | } 29 | else { 30 | std::string outputFileName = generator.genStubClassName() + ".h"; 31 | output = fopen(outputFileName.c_str(), "w"); 32 | if (output == nullptr) { 33 | perror("error"); 34 | exit(1); 35 | } 36 | } 37 | 38 | auto stubString = generator.genStub(); 39 | fputs(stubString.c_str(), output); 40 | } 41 | 42 | static std::unique_ptr 43 | makeGenerator(bool serverSide, json::Value& proto) 44 | { 45 | if (serverSide) 46 | return std::make_unique(proto); 47 | else 48 | return std::make_unique(proto); 49 | } 50 | 51 | static void genStub(FILE* input, bool serverSide, bool outputToFile) 52 | { 53 | json::FileReadStream is(input); 54 | json::Document proto; 55 | auto err = proto.parseStream(is); 56 | if (err != json::PARSE_OK) { 57 | fprintf(stderr, "%s\n", json::parseErrorStr(err)); 58 | exit(1); 59 | } 60 | 61 | try { 62 | auto generator = makeGenerator(serverSide, proto); 63 | writeToFile(*generator, outputToFile); 64 | } 65 | catch (StubException& e) { 66 | fprintf(stderr, "input error: %s\n", e.what()); 67 | exit(1); 68 | } 69 | } 70 | 71 | int main(int argc, char** argv) 72 | { 73 | bool serverSide = false; 74 | bool clientSide = false; 75 | bool outputToFile = false; 76 | const char* inputFileName = nullptr; 77 | 78 | int opt; 79 | while ((opt = getopt(argc, argv, "csi:o")) != -1) { 80 | switch (opt) { 81 | case 'c': 82 | clientSide = true; 83 | break; 84 | case 's': 85 | serverSide = true; 86 | break; 87 | case 'o': 88 | outputToFile = true; 89 | break; 90 | case 'i': 91 | inputFileName = optarg; 92 | break; 93 | default: 94 | fprintf(stderr, "unknown flag %c\n", opt); 95 | usage(); 96 | } 97 | } 98 | if (!serverSide && !clientSide) { 99 | serverSide = clientSide = true; 100 | } 101 | 102 | FILE* input = stdin; 103 | if (inputFileName != nullptr) { 104 | input = fopen(inputFileName, "r"); 105 | if (input == nullptr) { 106 | perror("error"); 107 | exit(1); 108 | } 109 | } 110 | 111 | try { 112 | if (serverSide) { 113 | genStub(input, true, outputToFile); 114 | rewind(input); 115 | } 116 | if (clientSide) { 117 | genStub(input, false, outputToFile); 118 | } 119 | } 120 | catch (StubException& e) { 121 | fprintf(stderr, "input error: %s\n", e.what()); 122 | exit(1); 123 | } 124 | } -------------------------------------------------------------------------------- /jrpc/util.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by frank on 17-12-31. 3 | // 4 | 5 | #ifndef JRPC_UTIL_H 6 | #define JRPC_UTIL_H 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace jrpc 26 | { 27 | 28 | using namespace std::literals::string_view_literals; 29 | using namespace std::literals::chrono_literals; 30 | 31 | using std::placeholders::_1; 32 | using std::placeholders::_2; 33 | using std::placeholders::_3; 34 | using std::placeholders::_4; 35 | 36 | using ev::EventLoop; 37 | using ev::TcpConnection; 38 | using ev::TcpServer; 39 | using ev::TcpClient; 40 | using ev::InetAddress; 41 | using ev::TcpConnectionPtr; 42 | using ev::noncopyable; 43 | using ev::Buffer; 44 | using ev::ConnectionCallback; 45 | using ev::ThreadPool; 46 | using ev::CountDownLatch; 47 | 48 | typedef std::function RpcDoneCallback; 49 | 50 | class UserDoneCallback 51 | { 52 | public: 53 | UserDoneCallback(json::Value &request, 54 | const RpcDoneCallback &callback) 55 | : request_(request), 56 | callback_(callback) 57 | {} 58 | 59 | 60 | void operator()(json::Value &&result) const 61 | { 62 | json::Value response(json::TYPE_OBJECT); 63 | response.addMember("jsonrpc", "2.0"); 64 | response.addMember("id", request_["id"]); 65 | response.addMember("result", result); 66 | callback_(response); 67 | } 68 | 69 | private: 70 | mutable json::Value request_; 71 | RpcDoneCallback callback_; 72 | }; 73 | 74 | } 75 | 76 | #endif //JRPC_UTIL_H 77 | -------------------------------------------------------------------------------- /rpc_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guangqianpeng/jrpc/1140892d6530c90ce94e3fb69618b545028be530/rpc_img.png --------------------------------------------------------------------------------