为什么我的 gRPC 服务器在与 C++ 中的 REST 服务器一起启动时会遇到“握手工厂创建失败,并出现 TSI_OUT_OF_RESOURCES”错误?

问题描述 投票:0回答:1

我有一个文件尝试同时运行 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

复制上述错误

正在使用的库:

  • Openssl 3.2.1
  • grpc 1.51.1
  • protobuf 3.21.12
  • 加密8.9.0
  • 提升1.71.0
  • cprestsdk 2.10.15-1

虚拟 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 请求?

任何见解或建议将不胜感激。谢谢!

openssl grpc grpc-c++
1个回答
0
投票

在处理安全 gRPC 通道和 REST 服务器时,使用 OpenSSL 3.x 或更高版本时会出现识别证书文件的问题。这与 REST 服务器形成对比,无论使用什么 OpenSSL 版本,REST 服务器都可以无缝处理 HTTP 和 HTTPS 协议。

为了确保 gRPC 和 REST 服务器的稳定性,最可靠的方法是将 OpenSSL 版本降级到 1.1.1n。通过这样做,您可以在与 cpprestsdk、openssl 和 gRPC 一起使用时确保 HTTP 和 HTTPS 协议的兼容性和正确的功能。

值得注意的是,虽然此解决方案可以解决眼前的问题,但监控 OpenSSL 和相关库的更新和进步对于未来的兼容性改进或安全增强至关重要。

© www.soinside.com 2019 - 2024. All rights reserved.