Comment commencer à écrire un microservice en C ++

Dans cet article, je vais m'appuyer sur l'utilisation de libevent dans debian + gcc + cmake , mais sur d'autres systèmes d'exploitation de type Unix, il ne devrait y avoir aucune difficulté (pour Windows, vous devrez construire à partir des sources et modifier FindLibEvent.cmake déposer)





Préface

Je développe des microservices depuis environ 3 ans, mais je n'avais pas une compréhension initiale d'une pile technologique appropriée. J'ai essayé de nombreuses approches différentes (dont certaines étaient OpenDDS et apache- thrift ) mais ont fini par s'installer sur RestApi .





RestApi communique via des requêtes HTTP, qui représentent à leur tour la structure de données des en-têtes et des corps de requête transmis via un socket. La première chose que j'ai remarquée a été boost / asio, qui fournit des sockets TCP, mais il y a des difficultés avec la quantité de développement:





  • Il est nécessaire d'écrire la réception correcte des données sur le socket





  • Analyse d'en-tête auto-écrite





  • Analyse auto-écrite des paramètres GET





  • Routage de chemin





La deuxième ligne était POCO (POCKET Fabrications), qui dispose d' un serveur HTTP plus haut niveau, mais il y avait encore un problème avec un tas de fonctionnalités écrites auto. De plus, cet outil est un peu plus lourd et fournit des fonctionnalités qui ne sont peut-être pas nécessaires (il surcharge un peu nos microservices). POCO est orienté vers d'autres tâches que les microservices.





Par conséquent, parlons plus loin de libevent sur lequel je me suis arrêté.





Pourquoi libevent?

  • Poids léger





  • Vite-fait





  • Stable





  • Multiplateforme





  • Préinstallé sur la plupart des systèmes d'exploitation de type Unix prêt à l'emploi





  • Utilisé par de nombreux développeurs (il est plus facile de trouver des employés familiarisés avec cette technologie)





  • Il y a un routeur intégré (routeur)





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());
      
      



Maintenant, notre serveur peut accepter les demandes, mais n'importe quel serveur doit répondre à l'application cliente. Pour cela, nous générons des réponses dans les gestionnaires.





//     
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); //  
      
      



Nous avons terminé la communication à part entière sur notre serveur, parlons maintenant de l'obtention d'informations utiles à partir des demandes des clients.





La première étape consiste à analyser les paramètres GET. Ce sont les paramètres qui sont passés dans l'URI de la requête (par exemple 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);
      
      



Ensuite, vous devez obtenir le corps de la requête





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 Les fonctions de rappel ne prennent pas en charge les interruptions (capture de valeurs avec des fonctions lambda), par conséquent, seuls les membres et méthodes statiques peuvent être utilisés dans les callbacks!








All Articles