How to start writing a microservice in C ++

In this article, I will rely on the use of libevent within debian + gcc + cmake , but on other unix-like operating systems, difficulties should not arise (for windows, you will need to build from sources and refine the FindLibEvent.cmake file)





Foreword

I have been developing microservices for about 3 years, but I did not have an initial understanding of a suitable technology stack. Tried many different approaches (some of which were OpenDDS and apache-thrift ) but ended up settling on RestApi .





RestApi communicates via HTTP requests, which in turn represent the data structure of headers and request bodies transmitted over a socket. The first thing I noticed was boost / asio, which provides tcp sockets, but there are difficulties with the amount of development:





  • It is necessary to write the correct reception of data over the socket





  • Self-written header parsing





  • Self-written parsing of GET parameters





  • Path routing





The second in line was POCO (POcket COmponents), which has a higher-level HTTP server, but it still had a problem with a bunch of self-written functionality. In addition, this tool is a little more heavyweight and provides functionality that may not be required (it overloads our microservices a little). POCO is geared towards other tasks than microservices.





Therefore, further on, let's talk about libevent , which I ended up with.





Why libevent?

  • Lightweight





  • Quick





  • Stable





  • Cross-platform





  • Preinstalled on most unix-like OSes out of the box





  • Used by many developers (it is easier to find employees who are familiar with this technology)





  • There is a built-in router (router)





libevent . - . "" , C++ - ( ).





, ( Valgrind).





libevent libevent-dev unix- .





, dpkg -l | grep event .





, FindLibEvent.cmake ( _/cmake_modules)





#           ${LIBEVENT_INCLUDE_DIR}
find_path(LIBEVENT_INCLUDE_DIR event.h
  PATHS
    /usr/local
    /opt
  PATH_SUFFIXES
    include
)

#        ${LIBEVENT_LIB}
find_library(LIBEVENT_LIB
  NAMES
    event
  PATHS
    /usr/local
    /opt
  PATH_SUFFIXES
    lib
    lib64
)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
  LIBEVENT_LIB
  LIBEVENT_INCLUDE_DIR
)
      
      



( _/imported/libevent.cmake)





find_package(LibEvent REQUIRED) #   FindLibEvent.cmake
add_library(libevent STATIC IMPORTED GLOBAL) #    target      

#   target-         FindLibEvent.cmake
set_target_properties(libevent PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${LIBEVENT_INCLUDE_DIR})
#   target-      FindLibEvent.cmake
set_target_properties(libevent PROPERTIES IMPORTED_LOCATION ${LIBEVENT_LIB})
      
      



libevent cmake- .





, 1





target_link_libraries(${PROJECT_NAME}
  PUBLIC
    libevent
)
      
      



, FindLibEvent.cmake





find_package(LibEvent REQUIRED)

target_link_libraries(${PROJECT_NAME}
  PUBLIC
    ${LIBEVENT_LIB}
)

target_include_directories(${PROJECT_NAME}
  PUBLIC
    ${LIBEVENT_INCLUDE_DIR}
)
      
      



HTTP ,





//   , :
// *   
// *      
// *     HTTP(,   .)
#include <evhttp.h>

//     
auto listener = std::make_shared<event_base, decltype(&event_base_free)>(event_base_new(),           &event_base_free);
//  HTTP    
auto server   = std::make_shared<evhttp,     decltype(&evhttp_free)>    (evhttp_new(listener.get()), &evhttp_free);

//  
//         
evhttp_set_gencb(server.get(),             [](evhttp_request*, void*) {}, nullptr);
//      
evhttp_set_cb   (server.get(), "/my_path", [](evhttp_request*, void*) {}, nullptr);

//   
return event_base_dispatch(listener.get());
      
      



Now our server can accept requests, but any server must respond to the client application. For this, we generate responses in the handlers.





//     
auto buffer = std::make_shared<evbuffer, decltype(&evbuffer_free)>(evbuffer_new(), &evbuffer_free);
evbuffer_add(buffer, msg.c_str(), msg.length()); //    
evhttp_send_reply(request, HTTP_OK, "", buffer); //  
      
      



We have completed the full-fledged communication at our server, now let's talk about getting useful information from client requests.





The first step is to parse the GET parameters. These are the parameters that are passed in the request URI (for example http://www.hostname.ru ? Key = value )





struct evkeyvalq params;
evhttp_parse_query(request->uri, &params); //  GET 

//      GET-   
std::string value = evhttp_find_header(&params, "key");

//      GET-
for (auto it = params.tqh_first; it != nullptr; it = it->next.tqe_next)
  std::cout << it->key << ":" << it->value << std::endl;

//     
evhttp_clear_headers(&params);
      
      



Next, you need to get the request body





auto input = request->input_buffer; //      

//     ,       
auto length = evbuffer_get_length(input);
char* data = new char[length];

evbuffer_copyout(input, data, length); //   
std::string body(data, length); //     
delete[] data; //   

return body;
      
      



Attention Callback functions do not support interrupts (capturing values ​​by lambda functions), therefore, only static members and methods can be used inside callbacks!








All Articles