tRPC-Cpp supports tRPC protocol by default, and also supports HTTP protocol. Users can send Plain Text or JSON data in HTTP requests to access standard HTTP services, or send protobuf data in requests to access RPC services. This allows an RPC service to support both tRPC and HTTP protocols at the same time, and currently supports accessing tRPC services through HTTP clients.
This article introduces how to access HTTP services based on tRPC-Cpp (referred to as tRPC below), and developers can learn the following:
- Accessing standard HTTP services
- Quick start: Use an HTTP client to access HTTP services.
- Basic usage: Common interfaces for handling requests and responses.
- Advanced usage: Send HTTPS requests; request message compression, response message decompression; large file upload and download.
- Accessing HTTP RPC services
- FAQ
Before getting started, you can try running the sample code to get a hands-on experience of how an HTTP service works.
Example: http
Go to the main directory of the tRPC code repository and run the following command.
sh examples/features/http/run.sh
The content of the output from the client program is as follows:
# Here are some excerpts from the output of the process:
response content: hello world!
response content: {"msg":"hello world!"}
response content: {"msg": "hello world!", "age": 18, "height": 180}
# Test case execution status (1: success, 0: failure)
name: GET string, ok: 1
name: GET json, ok: 1
name: GET http response, ok: 1
name: GET http response(not found), ok: 1
name: HEAD string, ok: 1
name: POST string, ok: 1
name: POST json, ok: 1
name: POST string, then wait http response, ok: 1
name: UNARY invoking, ok: 1
final result of http calling: 1
Here's a quick look at the client's output: it sends GET, HEAD, POST
requests and receives their corresponding
responses. In addition, a Unary RPC was successfully executed.
Next, let's take a look at the steps to access an HTTP service.
- Get the
HttpServiceProxyPtr
objectproxy
: useGetTrpcClient()->GetProxy<HttpServiceProxy>(...)
. - Create the
ClientContextPtr
objectcontext
: useMakeClientContext(proxy)
. - Call the expected API to access and check the return code, such as
GetString
orPostString
(we can choose to use synchronous or asynchronous interfaces based on your business scenario).
Below is an example of accessing GetString
using coroutines.
Example: http_client.cc
-
Get the
HttpServiceProxyPtr
objectproxy
: useGetTrpcClient()->GetProxy<HttpServiceProxy>(...)
std::string service_name{"http_client_xx"}; ::trpc::ServiceProxyOption option; // ... The initialization process of option is omitted here. // The proxy obtained here can be used elsewhere without having to obtain it every time. auto proxy = ::trpc::GetTrpcClient()->GetProxy<::trpc::http::HttpServiceProxy>(servie_name, option);
There are three ways to create an
HttpServiceProxyPtr
:-1- Set the
ServiceProxyOption
object.-2- Use a YAML configuration file to specify the service configuration item. (Recommended)
-3-
ServiceProxyOption
initialized by callback defined by user + YAML configuration.Reference to client_guide
-
Create the
ClientContextPtr
objectcontext
: useMakeClientContext(proxy)
.auto ctx = ::trpc::MakeClientContext(proxy);
-
Invoke
GetString
// ... auto proxy = ::trpc::GetTrpcClient()->GetProxy<::trpc::http::HttpServiceProxy>(servie_name, option); auto ctx = ::trpc::MakeClientContext(proxy); // rsp_str stores the body of response. std::string rsp_str; auto status = proxy->GetString(ctx, "http://example.com/foo", &rsp_str); if (!status.OK()) { // Error ... } else { // Ok ... } // ...
HttpServiceProxy
provides interfaces such as Head Get Post Put Delete Options
, and for convenience, it also
encapsulates some interfaces for obtaining specific objects, such as commonly used String
and JSON
objects.
These interfaces are generally suitable for simple scenarios, such as GET or POST a String or JSON.
For different programming models, synchronous and asynchronous interfaces are provided. Synchronous interfaces are
recommended for fiber
coroutine mode, and asynchronous interfaces are recommended for merge or seprate
thread model.
Below is a quick overview of the interface list.
Regarding interface naming issues: some interface names may not be elegant and may increase understanding costs. The documentation will try to clarify as much as possible.
The Get interface obtains a JSON/string from the server and provides synchronous and asynchronous calling interfaces.
Note: The Get2 below is just an interface name and does not use the HTTP2 protocol.
Class/Object | Interface Name | Functionality | Parameters | Return Value | Remarks |
---|---|---|---|---|---|
HttpServiceProxy | Status Get(const ClientContextPtr& context, const std::string& url, rapidjson::Document* json) | Obtains a JSON response message using GET | context client context url resource location json stores the response message |
Status | Synchronous interface |
HttpServiceProxy | Status GetString(const ClientContextPtr& context, const std::string& url, std::string* rspStr) | Obtains a string response message using GET | context client context url resource location rspStr stores the response message |
Status | Synchronous interface |
HttpServiceProxy | Status Get2(const ClientContextPtr& context, const std::string& url, HttpResponse* rsp) | Obtains an HTTP response using GET | context client context url resource location rsp stores the HTTP response |
Status | Synchronous interface |
HttpServiceProxy | Future<rapidjson::Document> AsyncGet(const ClientContextPtr& context, const std::string& url) | Obtains a JSON response message using GET asynchronously | context client context url resource location |
Future<rapidjson::Document> | Asynchronous interface |
HttpServiceProxy | Future<std::string> AsyncGetString(const ClientContextPtr& context, const std::string& url) | Obtains a string response message using GET asynchronously | context client context url resource location |
Future<std::string> | Asynchronous interface |
HttpServiceProxy | Future<HttpResponse> AsyncGet2(const ClientContextPtr& context, const std::string& url) | Obtains an HTTP response using GET asynchronously | context client context url resource location |
Future<HttpResponse> | Asynchronous interface |
Translation:
The Post interface supports sending a JSON/string request to the server and then obtaining a JSON/string response from the server.
Note: The Post2 below is just an interface name and does not use the HTTP2 protocol.
Class/Object | Interface Name | Functionality | Parameters | Return Value | Remarks |
---|---|---|---|---|---|
HttpServiceProxy | Status Post(const ClientContextPtr& context, const std::string& url, const rapidjson::Document& data, rapidjson::Document* rsp) | Sends a JSON request message using POST and obtains a JSON response message | context client context url resource location data request message rsp stores the response message |
Status | Synchronous interface |
HttpServiceProxy | Status Post(const ClientContextPtr& context, const std::string& url, const std::string& data, std::string* rsp) | Sends a string request message using POST and obtains a string response message | context client context url resource location data request message rsp stores the response message |
Status | Synchronous interface |
HttpServiceProxy | Status Post(const ClientContextPtr& context, const std::string& url, const std::string&& data, std::string* rsp) | Sends a string request message using POST and obtains a string response message | context client context url resource location data request message rsp stores the response message |
Status | Synchronous interface, performance optimization interface |
HttpServiceProxy | Status Post2(const ClientContextPtr& context, const std::string& url, const std::string& data, HttpResponse* rsp) | Sends a string request message using POST and obtains an HTTP response | context client context url resource location data request message rsp stores the response message |
Status | Synchronous interface |
HttpServiceProxy | Status Post2(const ClientContextPtr& context, const std::string& url, std::string&& data, HttpResponse* rsp) | Sends a string request message using POST and obtains an HTTP response | context client context url resource location data request message rsp stores the response message |
Status | Synchronous interface, performance optimization interface |
HttpServiceProxy | Status Post(const ClientContextPtr& context, const std::string& url, NoncontiguousBuffer&& data, NoncontiguousBuffer* body) | Sends a string request message using POST and obtains a string response message | context client context url resource location data request message body stores the response message |
Status | Synchronous interface, performance optimization interface |
HttpServiceProxy | Future<rapidjson::Document> AsyncPost(const ClientContextPtr& context, const std::string& url, const rapidjson::Document& data) | Sends a JSON request message using POST and obtains a JSON response message asynchronously | context client context url resource location data request message |
Future<rapidjson::Document> | Asynchronous interface |
HttpServiceProxy | Future<std::string> AsyncPost(const ClientContextPtr& context, const std::string& url, const std::string& data) | Sends a string request message using POST and obtains a string response message asynchronously | context client context url resource location data request message |
Future<std::string> | Asynchronous interface |
HttpServiceProxy | Future<std::string> AsyncPost(const ClientContextPtr& context, const std::string& url, std::string&& data) | Sends a string request message using POST and obtains a string response message asynchronously | context client context url resource location data request message |
Future<std::string> | Asynchronous interface, performance optimization interface |
HttpServiceProxy | Future<NoncontiguousBuffer> AsyncPost(const ClientContextPtr& context, const std::string& url, NoncontiguousBuffer&& data) | Sends a string request message using POST and obtains a string response message asynchronously | context client context url resource location data request message |
Future<NoncontiguousBuffer> | Asynchronous interface, performance optimization interface |
HttpServiceProxy | Future<HttpResponse> AsyncPost2(const ClientContextPtr& context, const std::string& url, const std::string& data) | Sends a string request message using POST and obtains an HTTP response | context client context url resource location data request message |
Future<HttpResponse> | Asynchronous interface |
HttpServiceProxy | Future<HttpResponse> AsyncPost2(const ClientContextPtr& context, const std::string& url, const std::string&& data) | Sends a string request message using POST and obtains an HTTP response | context client context url resource location data request message |
Future<HttpResponse> | Asynchronous interface, performance optimization interface |
Note: The Put2 below is just an interface name and does not use the HTTP2 protocol.
/// @brief Gets an HTTP response of an HTTP HEAD method request from HTTP server.
Status Head(const ClientContextPtr& context, const std::string& url, HttpResponse* rsp);
/// @brief Gets an HTTP response of an HTTP OPTIONS method request from HTTP server.
Status Options(const ClientContextPtr& context, const std::string& url, HttpResponse* rsp);
/// @brief Puts a JSON as the request body to HTTP server, then gets the JSON content of response.
Status Put(const ClientContextPtr& context, const std::string& url, const rapidjson::Document& data, rapidjson::Document* rsp);
/// @brief Puts a string as the request body to HTTP server, then gets the string content of response.
Status Put(const ClientContextPtr& context, const std::string& url, const std::string& data, std::string* rsp_str);
Status Put(const ClientContextPtr& context, const std::string& url, std::string&& data, std::string* rsp_str);
Status Put(const ClientContextPtr& context, const std::string& url, ::trpc::NoncontiguousBuffer&& data,::trpc::NoncontiguousBuffer* body);
/// @brief Puts a string as the request body to HTTP server, then gets the HTTP response.
Status Put2(const ClientContextPtr& context, const std::string& url, const std::string& data, HttpResponse* rsp);
Status Put2(const ClientContextPtr& context, const std::string& url, std::string&& data, HttpResponse* rsp);
/// @brief Sends an HTTP PATCH request with a string as the request body to HTTP server, then gets the HTTP response.
Status Patch(const ClientContextPtr& context, const std::string& url, const std::string& data, HttpResponse* rsp);
Status Patch(const ClientContextPtr& context, const std::string& url, std::string&& data, HttpResponse* rsp);
/// @brief Sends an HTTP DELETE request with a string as the request body to HTTP server, then gets the HTTP response.
Status Delete(const ClientContextPtr& context, const std::string& url, const std::string& data, HttpResponse* rsp);
Status Delete(const ClientContextPtr& context, const std::string& url, std::string&& data, HttpResponse* rsp);
// The corresponding asynchronous interface is as follows:
Future<HttpResponse> AsyncHead(const ClientContextPtr& context, const std::string& url);
Future<HttpResponse> AsyncOptions(const ClientContextPtr& context, const std::string& url);
Future<rapidjson::Document> AsyncPut(const ClientContextPtr& context, const std::string& url, const rapidjson::Document& data);
Future<std::string> AsyncPut(const ClientContextPtr& context, const std::string& url, const std::string& data);
Future<std::string> AsyncPut(const ClientContextPtr& context, const std::string& url, std::string&& data);
Future<::trpc::NoncontiguousBuffer> AsyncPut(const ClientContextPtr& context, const std::string& url, ::trpc::NoncontiguousBuffer&& data);
Future<HttpResponse> AsyncPut2(const ClientContextPtr& context, const std::string& url, const std::string& data);
Future<HttpResponse> AsyncPut2(const ClientContextPtr& context, const std::string& url, std::string&& data);
Future<HttpResponse> AsyncPatch(const ClientContextPtr& context, const std::string& url, const std::string& data);
Future<HttpResponse> AsyncPatch(const ClientContextPtr& context, const std::string& url, std::string&& data);
Future<HttpResponse> AsyncDelete(const ClientContextPtr& context, const std::string& url, const std::string& data);
Future<HttpResponse> AsyncDelete(const ClientContextPtr& context, const std::string& url, std::string&& data);
tRPC-Cpp has not yet implemented the HTTP CONNECT related logic.
The synchronous interface provided by HttpServiceProxy
returns a Status
object. It is customary to use status.OK()
to quickly determine whether the access is successful or failed, and then proceed to the next step of processing.
Class/Object | Interface Name | Function | Parameters | Return Value |
---|---|---|---|---|
Status | bool Status::OK() | Determine if the interface call was successful | void | bool, true: success, false: failure |
Status | std::string ToString() const | Return error code and error message in character format | void | std::string |
tRPC currently supports HTTP/1.1
and HTTP 1.0
, with HTTP/1.1
being the default. HTTP/2.0
is not yet supported,
but is currently being prepared for.
If you want to use HTTP 1.0
, you can set it using the following method.
request.SetVersion("1.0");
When parsing a URL, tRPC sets the host:port
field in the URL as the Host
field in the HTTP request header.
Note:
The host
field (Domain) in the URL is not used for DNS queries, and the host:port
field (IP:Port) is not used for
creating TCP connections. They are only used for filling in the HTTP protocol.
Limitation: A single HttpServiceProxy
object instance does not support accessing dynamic URLs. For example, it does
not support dynamically accessing different sites in an HTTP proxy forwarding scenario.
Solution: Create different HttpServiceProxy instances for different sites.
The HTTP client selects the target IP:Port through two configuration items: target: ip:port
and selector: $naming
.
Common methods:
- Direct connection.
selector: direct
, configure a single IP:Port:target: ip:port
, or a list of IP: Ports:target: ip:port,ip:port,ip:port,...
. - DNS resolution.
selector: domain
,target: github.com:80
(Note: the target here is in IP:Port format and needs to specify the specific port). - Other naming services.
selector: $naming_plugin
,target: $name
.
How to fill in URL parameters?
In general, the URL can be filled in according to the standard format. The "host:port" field can be filled in using placeholders, and "host" and "port" can be filled in with the values you want to send to the server. For example:
- Fill in
xx.example.com
, complete URL:http://xx.example.com/to/path
, and the HTTP request header will be filled in with:Host: xx.example.com
. - Fill in
xx.example.com:8080
, complete URL:http://xx.example.com:8080/to/path
, and the HTTP request header will be filled in with:Host: xx.example.com:8080
.
There is no direct entry to set HTTP Request Header in the Get/Post interface. If you want to set the request header, you can use the interface provided by ClientContext to set it.
Class/Object | Interface Name | Function | Parameters | Return Value |
---|---|---|---|---|
ClientContext | void SetHttpHeader(const std::string& h, const std::string& v) | Set HTTP request header | h: Request header name Request header value |
void |
ClientContext | const std::string& GetHttpHeader(const std::string& h) | Get the corresponding value of the specified HTTP request header | h: Request header name | const std::string& |
ClientContext | const auto &GetHttpHeaders(); | Get the list of HTTP request headers | void | google::protobuf::Map<std::string, std::string> |
In the interface methods such as Get Post
mentioned in the previous chapters, generally
only Client Context
+ URL
+ Request Body
parameters are passed, and no request header related parameters are
provided.
If you need to:
- Set more parameters for the request object.
- Get more parameters for the response object.
It is recommended to use the HttpUnaryInvoke
interface, which can pass the complete request object or get the complete
response object.
Status HttpUnaryInvoke(const ClientContextPtr& context, const HttpRequest& req, HttpRespnose* rsp);
Future<HttpResponse> AsyncHttpUnaryInvoke(const ClientContextPtr& context, const HttpRequest& req);
To use this interface, the caller needs to complete the following operations:
- Set the
HTTP Method
. - Set the
URL
(Request-URI of the relative path, such as: "/hello?name=tom"). - It is recommended to set the
Host
header (some reverse proxy servers may need this value for forwarding, such as nginx).
For example:
bool HttpPost(const HttpServiceProxyPtr& proxy) {
auto ctx = ::trpc::MakeClientContext(proxy);
::trpc::http::Request http_req;
http_req.SetMethod("POST");
http_req.SetUrl("/foo");
std::string req_content = R"({"msg": "hello world!"})";
http_req.SetHeader("Content-Type", "application/json");
http_req.SetHeader("Content-Length", std::to_string(req_content.size()));
http_req.SetContent(req_content);
::trpc::http::Response http_rsp;
auto status = proxy->HttpUnaryInvoke<::trpc::http::Request, ::trpc::http::Response>(ctx, http_req, &http_rsp);
if (!status.OK()) {
TRPC_FMT_ERROR("status: {}", status.ToString());
return false;
}
// ...
return true;
}
tRPC does not automatically compress or decompress message bodies, mainly for the following reasons:
- Flexibility: allowing users to handle this operation themselves will be more flexible.
- Compression and decompression code is not very complex. tRPC provides compression and decompression tools, currently supporting gzip, lz4, snappy, zlib, and more. compressor
HTTPS is short for HTTP over SSL and can be enabled through the following steps:
-
Enable SSL compilation options when compiling the code.
When using
bazel build
, add the--define trpc_include_ssl=true
compilation parameter. Note: We can also add it to the.bazelrc
file.Note: tRPC-Cpp supports HTTPS based on OpenSSL. Please make sure that OpenSSL is correctly installed in the compilation and runtime environment.
// e.g. bazel build --define trpc_include_ssl=true //https/http_client/...
-
Set SSL-related configurations in the configuration file. The specific configuration items are as follows:
Name Function Value Range Default Value Optional/Required Description ciphers Encryption suite Unlimited null required
If SSL is enabled but not set correctly, the service will fail to start. enable Whether to enable SSL {true, false} false optional It is recommended to specify the configuration item explicitly with clear intent. sni_name Set the SNI extension field in the SSL protocol, or simply set the Host in SSL Unlimited null optional It is recommended to set it to the Host value in the URL, which is generally understood as the Host value in HTTP, that is, the domain name. ca_cert_path CA certificate path Unlimited, xx/path/to/ca.pem null optional Used more in self-signed certificate scenarios. cert_path Certificate path Unlimited, xx/path/to/server.pem null optional Required for mutual authentication, invalid in other cases. private_key_path Private key path Unlimited, xx/path/to/server.key null optional Required for mutual authentication, invalid in other cases. protocols SSL protocol version {SSLv2, SSLv3, TLSv1, TLSv1.1, TLSv1.2} TLSv1.1 + TLSv1.2 optional - insecure Whether to verify the legality of the other party's certificate {true, false} false optional By default, the legality of the other party's certificate is verified. In the debugging scenario, self-signed certificates are generally used, and the certificate may not pass the verification. Setting this parameter to true can skip the certificate verification process. For example:
# @file: trpc_cpp.yaml # ... client: service: - name: http_client selector_name: direct target: 127.0.0.1:24756 protocol: http network: tcp conn_type: long ## <-- New SSL configuration items. ssl: enable: true # Optional configuration (defaults to false, indicating that SSL is disabled). ciphers: HIGH:!aNULL:!kRSA:!SRP:!PSK:!CAMELLIA:!RC4:!MD5:!DSS # Requireda. # sni_name: www.xxops.com # Optional. # ca_cert_path: ./https/cert/xxops-com-chain.pem # Optional. # cert_path: xx_cert.pem # Optional, but it's required for mutual authentication. # private_key_path: xx_key.pem # Optional, but it's required for mutual authentication. # insecure: true # Optional. (default to false, disable insecure mode) # protocols: # Optional. # - SSLv2 # - SSLv3 # - TLSv1 # - TLSv1.1 # - TLSv1.2 ## --> New SSL configuration items. # ...
tRPC-Cpp has filtered HTTP response codes:
- When the response code is 2xx, the caller can get the corresponding response message.
- When the response code is not 2xx, the caller can only get the response code but not the response message.
Solution: Override CheckResponse(...)
and return true
directly. For example:
class MyHttpServiceProxy : public ::trpc::http::HttpSeriveProxy {
public:
bool CheckHttpResponse(const ClientContextPtr& context, const ProtocolPtr& http_rsp) override { return true; }
};
In HTTP services, there are scenarios where large files need to be read or sent. Reading the entire file into memory is inefficient and can cause high memory pressure, making it impractical for uploading large files. tRPC-Cpp provides a set of HTTP stream reading/writing data chunk interfaces that can be used to receive/send large files in chunks.
- For large files with known length, set
Content-Length: $length
and send them in chunks (or use chunked transfer encoding if the recipient supports it). - For large files with unknown length, set
Transfer-Encoding: chunked
and send them in chunks.
For more details, please refer to the documentation http upload download.
Example: client.cc
If a tRPC RPC service supports client access using the HTTP
protocol on the server side, the client can also access
this RPC service using the HTTP
protocol.
- Call
UnaryInvoke(...)
. - Set the configuration item
protocol: trpc_over_http
.
// Template parameters: protobuf Message.
template <class RequestMessage, class ResponseMessage>
Status UnaryInvoke(const ClientContextPtr& context, const RequestMessage& req, ResponseMessage* rsp);
template <class RequestMessage, class ResponseMessage>
Future<ResponseMessage> AsyncUnaryInvoke(const ClientContextPtr& context, const RequestMessage& req);
For example:
bool HttpRpcCall(const HttpServiceProxyPtr& proxy) {
auto ctx = ::trpc::MakeClientContext(proxy);
ctx->SetTimeout(5000);
// Set RPC function name.
ctx->SetFuncName("/trpc.test.helloworld.Greeter/SayHello");
::trpc::test::helloworld::HelloRequest req;
::trpc::test::helloworld::HelloReply rsp;
req.set_msg("hello world!");
auto status =
proxy->UnaryInvoke<::trpc::test::helloworld::HelloRequest, ::trpc::test::helloworld::HelloReply>(ctx, req, &rsp);
if (!status.OK()) {
TRPC_FMT_ERROR("status: {}", status.ToString());
return false;
}
// ...
return true;
}
If the server wants to pass the tRPC protocol trans_info
through the HTTP protocol, how does the client handle it?
Treat trpc-trans-info: {"k1": "v1", "k2": "v2"}
as an HTTP header, where the HTTP header name
is trpc-trans-info
and the value {"k1": "v1", "k2": "v2"}
is a JSON string.
Use ClientContext::SetHttpHeader('trpc-trans-info', 'json_string')
to set it.
Similar to the following curl command line:
curl -H 'trpc-trans-info: {"k1": "v1", "k2": "v2" }' -T xx.seriealized.pb $url
If the Key-Value Pairs to be passed contain binary data, how should they be handled?
We can try to Base64 decode/encode the binary data, and the client and server agree to ensure that the data can be transmitted correctly.
If you only need to get the status code of 2xx, you can use the interface that returns HttpResponse*
.
If you need to get the status code of non-2xx, please override the CheckHttpResponse(...)
method.
Yes, it is supported. You need to:
- Set
target
toxx.example.com:8080
. - Set
selector_name
todomain
.
- If the
fiber
coroutine is used, the synchronous interface execution process is a synchronous call, which is executed asynchronously and does not block the thread. - If the
merge or separate
thread model is used, the synchronous interface call will block the caller thread, but the network-related operations are executed asynchronously.
Refer to the following command (replace your own data + IP:Port + RPC method name):
## http_rpc_hello_request.pb is the serialized content of the PB message.
curl -T http_rpc_hello_request.pb -H "Content-Type:application/pb" 'http://127.0.0.1:24756/trpc.test.httpserver.Greeter/SayHello'
## Send JSON data
curl -T http_rpc_hello_request.json -H "Content-Type:application/json" 'http://127.0.0.1:24756/trpc.test.httpserver.Greeter/SayHello'
When using a self-signed certificate for debugging, how to handle the client certificate verification failure?
ssl configuration item: insecure
: whether to verify the legality of the other party's certificate. By default, the
legality of the other party's certificate is verified. In the debugging scenario, when using a self-signed certificate,
you can set this parameter to true to skip the certificate verification process.
When calling interfaces such as HttpUnaryInvoke
, an error is prompted: ... err=unmatched codec ...
, how to handle it?
Try to set the codec configuration item used by HttpServiceProxy: protocol: http
.
When calling an tRPC RPC service through HTTP, if the called party sets an alias through @alias, how to set the URL Path?
Replace the parameter value: client_context->SetFuncName("${your-alias-name}")
.