我有一个文件尝试同时运行 REST 和 gRPC 服务器,应该成功处理 http 和 https 请求。
但是,当尝试使用 HTTPS 请求测试服务器时,我遇到以下错误:
REST Server started
Listening for requests at: https://localhost:8080/
E0221 10:21:11.667654416 53391 ssl_transport_security.cc:2258] Could not create ssl context.
E0221 10:21:11.667749341 53391 ssl_security_connector.cc:270] Handshaker factory creation failed with TSI_OUT_OF_RESOURCES.
E0221 10:21:11.667887612 53391 chttp2_server.cc:1045] UNKNOWN:Unable to create secure server with credentials of type Ssl {file:"/opt/vcpkg/buildtrees/grpc/src/v1.51.1-1066d25324.clean/src/core/ext/transport/chttp2/server/chttp2_server.cc", file_line:1032, created_time:"2024-02-21T10:21:11.667802229+00:00"}
Failed to start gRPC server
复制上述错误
正在使用的库:
虚拟 gRPC 服务器的原型文件:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
gRPC 和 REST 服务器代码:
#include <fstream>
#include <sstream>
#include <iostream>
#include <memory>
#include <string>
#include <cpprest/http_listener.h>
#include <cpprest/uri.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <cryptopp/cryptlib.h>
#include <cryptopp/config_int.h>
#include <cryptopp/secblock.h>
#include <cryptopp/smartptr.h>
#include <cryptopp/osrng.h>
#include <cryptopp/aes.h>
#include <cryptopp/sha.h>
#include <grpcpp/grpcpp.h>
#include "test.grpc.pb.h"
#include "test.pb.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "shares.grpc.pb.h"
#include "shares.pb.h"
#include <thread>
using web::http::http_request;
using web::http::http_response;
using web::http::methods;
using web::http::status_codes;
using web::http::experimental::listener::http_listener;
using web::http::experimental::listener::http_listener_config;
using web::uri_builder;
using grpc::Channel;
using grpc::ClientContext;
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using helloworld::Greeter;
using helloworld::HelloRequest;
using helloworld::HelloResponse;
class GreeterServiceImpl final : public Greeter::Service {
Status SayHello(ServerContext* context, const HelloRequest* request,
HelloResponse* reply) override {
std::string prefix("Hello ");
reply->set_message(prefix + request->name());
return Status::OK;
}
};
std::string read_keycert(const std::string& filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Error opening file: " << filepath << std::endl;
return "";
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
void RunGrpcServer() {
std::string server_address("localhost:50052");
GreeterServiceImpl service;
ServerBuilder builder;
grpc::SslServerCredentialsOptions::PemKeyCertPair pkcp;
pkcp.private_key = read_keycert("test-private-key.pem");
pkcp.cert_chain = read_keycert("test-certificate.pem");
grpc::SslServerCredentialsOptions ssl_opts;
ssl_opts.pem_root_certs="";
ssl_opts.pem_key_cert_pairs.push_back(pkcp);
auto channel_creds = grpc::SslServerCredentials(ssl_opts);
builder.AddListeningPort(server_address, channel_creds);
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
if (server) {
std::cout << "gRPC Server listening on " << server_address << std::endl;
server->Wait(); // Wait for the server to shut down
} else {
std::cerr << "Failed to start gRPC server" << std::endl;
}
}
void RunRestServer() {
utility::string_t address = U("https://localhost:8080");
uri_builder uri(address);
auto addr = uri.to_uri().to_string();
http_listener_config config;
config.set_ssl_context_callback([](boost::asio::ssl::context& ctx){
ctx.set_options(boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::no_sslv3
| boost::asio::ssl::context::tlsv12);
ctx.use_certificate_chain_file("test-certificate.crt");
ctx.use_private_key_file("test-private-key.key", boost::asio::ssl::context::pem);
});
http_listener listener(addr, config);
listener.support(methods::GET, [](http_request request) {
request.reply(status_codes::OK, "Hello from secure REST server!");
});
try {
listener.open().then([]() {
std::cout << "REST Server started" << std::endl;
}).wait();
std::cout << "Listening for requests at: " << addr << std::endl;
std::thread([=]() {
while (true);
}).detach();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
int main() {
std::thread grpc_server_thread(RunGrpcServer);
std::thread rest_server_thread(RunRestServer);
grpc_server_thread.join();
rest_server_thread.join();
return 0;
}
使用 CMake 构建上述代码
CMakeList 还包含生成证书的代码:
find_package(cpprestsdk REQUIRED)
find_package(Boost REQUIRED program_options)
find_package(Catch2 CONFIG REQUIRED)
find_package(cryptopp CONFIG REQUIRED)
find_package(Protobuf CONFIG REQUIRED)
find_package(gRPC CONFIG REQUIRED)
find_package(OpenSSL REQUIRED)
# generate keys and certificate for grpc with https unit tests
add_custom_target(
keys ALL
COMMAND openssl ecparam -genkey -name prime256v1 -noout -out test-private-key.pem
COMMAND openssl ec -in test-private-key.pem -pubout -out test-public-key.pem
COMMAND openssl req -new -x509 -sha256 -key test-private-key.pem -subj /CN=localhost -out test-certificate.pem
COMMENT "Generating keys and certificate for https unit tests"
BYPRODUCTS test-private-key.pem test-public-key.pem test-certificate.pem
)
# Proto file
get_filename_component(test_proto "protos/test.proto" ABSOLUTE)
get_filename_component(test_proto_path "${test_proto}" PATH)
set(VCPKG_TOOL_PATH "/opt/vcpkg/installed/x64-linux/tools")
# Generated files will be in the corresponding build directory
set(proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/test.pb.cc")
set(proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/test.pb.h")
set(grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/test.grpc.pb.cc")
set(grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/test.grpc.pb.h")
# Generating protobuf files for hello-world testing
add_custom_command(
OUTPUT "${proto_srcs}" "${proto_hdrs}"
COMMAND ${VCPKG_TOOL_PATH}/protobuf/protoc
ARGS --cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
-I "${test_proto_path}"
"${test_proto}"
DEPENDS "${test_proto}"
)
# Generating gRPC files
add_custom_command(
OUTPUT "${grpc_srcs}" "${grpc_hdrs}"
COMMAND ${VCPKG_TOOL_PATH}/protobuf/protoc
ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
--plugin=protoc-gen-grpc="${VCPKG_TOOL_PATH}/grpc/grpc_cpp_plugin"
-I "${test_proto_path}"
"${test_proto}"
DEPENDS "${proto_srcs}"
)
include_directories(${CMAKE_CURRENT_BINARY_DIR}) # Add the directory containing generated files
include_directories(/opt/vcpkg/installed/x64-linux/include/grpc++
/opt/vcpkg/installed/x64-linux/include/grpcpp
/opt/vcpkg/installed/x64-linux/include/grpc )
add_executable(grpc_test test/grpc.cpp ${proto_hdrs} ${proto_srcs} ${grpc_srcs} ${grpc_hdrs})
target_link_libraries(grpc_test PRIVATE protobuf::libprotobuf gRPC::grpc++ gRPC::grpc++_reflection
ssl ${Boost_LIBRARIES} crypto cryptopp::cryptopp yaml-cpp stdc++fs pthread gmp cpprest)
现在您可以在项目构建目录中运行:
./grpc_test
这应该会复制上面给出的错误!
PS:所有这些证书都位于正确的位置,并且应该已被服务器找到!
如何排查并解决此问题,使我的服务器能够有效处理 HTTPS 请求?
任何见解或建议将不胜感激。谢谢!
在处理安全 gRPC 通道和 REST 服务器时,使用 OpenSSL 3.x 或更高版本时会出现识别证书文件的问题。这与 REST 服务器形成对比,无论使用什么 OpenSSL 版本,REST 服务器都可以无缝处理 HTTP 和 HTTPS 协议。
为了确保 gRPC 和 REST 服务器的稳定性,最可靠的方法是将 OpenSSL 版本降级到 1.1.1n。通过这样做,您可以在与 cpprestsdk、openssl 和 gRPC 一起使用时确保 HTTP 和 HTTPS 协议的兼容性和正确的功能。
值得注意的是,虽然此解决方案可以解决眼前的问题,但监控 OpenSSL 和相关库的更新和进步对于未来的兼容性改进或安全增强至关重要。