From 329a6b273cc04a22193abf8fb6f41f6dca1ca165 Mon Sep 17 00:00:00 2001 From: Nils Carlson Date: Tue, 29 Mar 2022 20:13:00 +0000 Subject: [PATCH] Add grpc server outline Only handles unary grpcs. Only success implemented this far. --- CMakeLists.txt | 5 + examples/CMakeLists.txt | 49 +++-- examples/grpc-sv.cc | 52 ++++++ examples/helloworld.proto | 38 ++++ src/includes/nghttp2/asio_grpc_server.h | 227 ++++++++++++++++++++++++ 5 files changed, 359 insertions(+), 12 deletions(-) create mode 100644 examples/grpc-sv.cc create mode 100644 examples/helloworld.proto create mode 100644 src/includes/nghttp2/asio_grpc_server.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f25ff6d..b827ce49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,6 +242,10 @@ set(HAVE_JEMALLOC ${JEMALLOC_FOUND}) if(ENABLE_ASIO_LIB) find_package(Boost 1.54.0 REQUIRED system thread) + + if(ENABLE_GRPC) + find_package(Protobuf REQUIRED) + endif() endif() # libbpf (for bpf) @@ -565,6 +569,7 @@ message(STATUS "summary of build options: Applications: ${ENABLE_APP} HPACK tools: ${ENABLE_HPACK_TOOLS} Libnghttp2_asio:${ENABLE_ASIO_LIB} + gRPC: ${ENABLE_GRPC} Examples: ${ENABLE_EXAMPLES} Python bindings:${ENABLE_PYTHON_BINDINGS} Threading: ${ENABLE_THREADS} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7f700b7f..5c4b6c80 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -36,22 +36,47 @@ if(ENABLE_EXAMPLES) ) if(ENABLE_ASIO_LIB) + set(cc_includes + ${OPENSSL_INCLUDE_DIRS} + ${Boost_INCLUDE_DIRS} + ) + set(cc_libraries + nghttp2 + nghttp2_asio + ${JEMALLOC_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${Boost_LIBRARIES} + ${APP_LIBRARIES} + ) foreach(name asio-sv asio-sv2 asio-cl asio-cl2) add_executable(${name} ${name}.cc $ $ ) - target_include_directories(${name} PRIVATE - ${OPENSSL_INCLUDE_DIRS} - ${Boost_INCLUDE_DIRS} - ) - target_link_libraries(${name} - nghttp2 - nghttp2_asio - ${JEMALLOC_LIBRARIES} - ${OPENSSL_LIBRARIES} - ${Boost_LIBRARIES} - ${APP_LIBRARIES} - ) + target_include_directories(${name} PRIVATE ${cc_includes} ) + target_link_libraries(${name} ${cc_libraries} ) endforeach() endif() + + if(ENABLE_ASIO_LIB AND ENABLE_GRPC) + MESSAGE("GENERATING protobuggg!") + protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS helloworld.proto) + foreach(name grpc-sv) + add_executable(${name} ${name}.cc + $ + $ + ${PROTO_SRCS} + ${PROTO_HDRS} + ) + target_include_directories(${name} PRIVATE + ${cc_includes} + ${Protobuf_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} + ) + target_link_libraries(${name} ${cc_libraries} ${Protobuf_LIBRARIES}) + endforeach() + else() + MESSAGE("NOtdsdsda") + endif() + + endif() diff --git a/examples/grpc-sv.cc b/examples/grpc-sv.cc new file mode 100644 index 00000000..be1f9fcb --- /dev/null +++ b/examples/grpc-sv.cc @@ -0,0 +1,52 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 Nils Carlson + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include "helloworld.pb.h" + +using namespace nghttp2::asio_http2::server; +using namespace nghttp2::asio_grpc::server; + +using SayHello_request = grpc_unary_request; +using SayHello_response = grpc_unary_response; +using SayHello_handler = grpc_unary_handler; + +int main(int , char *[]) { + boost::system::error_code ec; + http2 server; + + server.handle("/helloworld.Greeter/SayHello", SayHello_handler{[](const SayHello_request &&request, SayHello_response &&response ) + { + + auto hello = request.get_message(); + std::cerr << "Got message with name :" << hello.name() << "\n"; + response.getMessage().set_message( "Hello " + hello.name() ); + response.write_response(); + }}); + if (server.listen_and_serve(ec, "localhost", "3000")) { + std::cerr << "error: " << ec.message() << std::endl; + } +} diff --git a/examples/helloworld.proto b/examples/helloworld.proto new file mode 100644 index 00000000..be878ce2 --- /dev/null +++ b/examples/helloworld.proto @@ -0,0 +1,38 @@ +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/src/includes/nghttp2/asio_grpc_server.h b/src/includes/nghttp2/asio_grpc_server.h new file mode 100644 index 00000000..5651d282 --- /dev/null +++ b/src/includes/nghttp2/asio_grpc_server.h @@ -0,0 +1,227 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 Nils Carlson + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_ASIO_GRPC_SERVER_H +#define NGHTTP2_ASIO_GRPC_SERVER_H + +#include +#include + +namespace nghttp2 { + +namespace asio_grpc { + +namespace server { + +extern "C" { +struct __attribute__((packed)) grpc_header { + uint8_t flags; + uint32_t length; +}; +} + +template< typename T, typename U> class grpc_unary_request; +template< typename U> class grpc_unary_response; + +template using on_unary_grpc = std::function< void(const grpc_unary_request< T, U > &&, grpc_unary_response< U > &&) >; + +class grpc_unary_request_impl +{ + + +}; + +template< typename T, typename U> class grpc_unary_request +{ +public: + grpc_unary_request( const on_unary_grpc< T, U> &on_unary_grpc, + const ::nghttp2::asio_http2::server::request &request ) : + on_unary_grpc_{ on_unary_grpc }, + request_{ request } + { + } + + void receive_message( grpc_unary_request< T, U > &&grpc_unary_request, + grpc_unary_response< U > &&grpc_unary_response ) + { + grpc_header header = { 0, 0 }; + std::string buffer; + bool have_header = false; + + grpc_unary_request.request_.on_data([header, buffer, have_header, + grpc_unary_request = std::move(grpc_unary_request), + grpc_unary_response = std::move(grpc_unary_response)] (const uint8_t *data, std::size_t len) mutable + { + if (len == 0) + { + return; + } + + std::copy_n(data, len, std::back_inserter(buffer)); + + if (!have_header && buffer.size() >= sizeof (header)) + { + memcpy(&header, buffer.data(), sizeof (header)); + header.length = ntohl(header.length); + have_header = true; + } + + if (have_header && buffer.size() >= (header.length + sizeof (header) )) + { + auto parsed = grpc_unary_request.get_message().ParseFromArray( buffer.data() + sizeof (header), header.length ); + if (!parsed) + { + // error! + } + buffer.erase(0, sizeof(header)); + have_header = false; + + if (parsed) + { + grpc_unary_request.on_unary_grpc_( std::move( grpc_unary_request ), std::move( grpc_unary_response ) ); + } + + } + }); + } + + T &get_message() + { + return message_; + } + + const T &get_message() const + { + return message_; + } + +private: + T message_; + on_unary_grpc< T, U> on_unary_grpc_; + const ::nghttp2::asio_http2::server::request &request_; +}; + + +template< typename U> class grpc_unary_response +{ +public: + grpc_unary_response( const ::nghttp2::asio_http2::server::response &response ) : + response_{ response } + {} + + void write_response( ) + { + ::nghttp2::asio_http2::header_map grpc_response_header; + ::nghttp2::asio_http2::header_value grpc_status_value = { "0", 0 }; + grpc_response_header.insert({ "content-type", {"application/grpc", 0} }); + response_.write_head(200, grpc_response_header); + + auto encoded = message_.SerializeAsString(); + if (encoded.empty()) + { + std::cerr << "Encoding gailed\n"; + // error! + } + + std::vector buf; + buf.resize(sizeof (grpc_header) + encoded.size() ); + std::cerr << "buf size: " << buf.size() << "\n"; + + grpc_header *header = reinterpret_cast(buf.data()); + + header->flags = 0; + header->length = htonl( encoded.size() ); + + std::copy_n(encoded.data(), encoded.size(), buf.data() + sizeof(grpc_header )); + + std::cerr << "message size: " << ntohl(reinterpret_cast(buf.data())->length) << "\n"; + + std::size_t count = 0; + response_.end([=](uint8_t *data, std::size_t len, uint32_t *data_flags) mutable { + + + *data_flags |= NGHTTP2_DATA_FLAG_EOF | NGHTTP2_DATA_FLAG_NO_END_STREAM; + + std::cerr << "len " << len << " bytes\n"; + std::cerr << "count " << count << " bytes\n"; + + std::size_t to_copy = std::min(buf.size() - count, len ); + std::copy_n( buf.data() + count, to_copy, data ); + std::cerr << "Copied " << to_copy << " bytes\n"; + + count += to_copy; + + if (count == buf.size()) + { + ::nghttp2::asio_http2::header_map grpc_response_trailer; + grpc_response_trailer.insert({ "grpc-status", {"0", 0} }); + response_.write_trailer(grpc_response_trailer); + + std::cerr << "Wrote all data!\n"; + } + return to_copy; + }); + + } + + U &getMessage() + { + return message_; + } + +private: + U message_; + const ::nghttp2::asio_http2::server::response &response_; +}; + + +template +class grpc_unary_handler +{ +public: + + grpc_unary_handler(on_unary_grpc &&on_request ) : + on_request_{on_request} + { + } + + void operator()( const ::nghttp2::asio_http2::server::request &req, const ::nghttp2::asio_http2::server::response &res ) const + { + grpc_unary_request request{ on_request_, req }; + grpc_unary_response response{ res }; + + request.receive_message(std::move(request), std::move(response)); + } + +private: + on_unary_grpc< T, U > on_request_; +}; + +} // namespace client + +} // namespace asio_grpc + +} // namespace nghttp2 + +#endif // NGHTTP2_ASIO_HTTP2_CLIENT_H