Como começar a escrever um microsserviço em C ++

Neste artigo, vou contar com o uso de libevent dentro do debian + gcc + cmake , mas em outros sistemas operacionais tipo unix, não deve haver dificuldades (para Windows, você precisará compilar a partir dos fontes e modificar o FindLibEvent.cmake Arquivo)





Prefácio

Tenho desenvolvido microsserviços há cerca de 3 anos, mas não tinha um entendimento inicial de uma pilha de tecnologia adequada. Tentei muitas abordagens diferentes (algumas das quais eram OpenDDS e apache-thrift ), mas acabei optando pelo RestApi .





RestApi se comunica por meio de solicitações HTTP, que por sua vez representam a estrutura de dados de cabeçalhos e corpos de solicitação transmitidos por meio de um soquete. A primeira coisa que notei foi boost / asio, que fornece sockets tcp, mas há dificuldades com a quantidade de desenvolvimento:





  • É necessário escrever a recepção correta de dados no soquete





  • Análise de cabeçalho autoescrita





  • Análise autoescrita de parâmetros GET





  • Roteamento de caminho





O segundo na fila era POCO (POcket COmponents), que tem um servidor HTTP de nível superior, mas ainda tinha um problema com várias funcionalidades de escrita própria. Além disso, essa ferramenta é um pouco mais pesada e fornece funcionalidades que podem não ser necessárias (sobrecarrega um pouco nossos microsserviços). O POCO é voltado para outras tarefas além dos microsserviços.





Portanto, vamos falar mais sobre o libevent no qual parei.





Por que libevent?

  • Leve





  • Rápido





  • Estábulo





  • Plataforma cruzada





  • Pré-instalado na maioria dos sistemas operacionais tipo Unix fora da caixa





  • Usado por muitos desenvolvedores (é mais fácil encontrar funcionários familiarizados com esta tecnologia)





  • Há um roteador embutido (roteador)





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



Agora nosso servidor pode aceitar solicitações, mas qualquer servidor deve responder ao aplicativo cliente. Para isso, geramos respostas nos manipuladores.





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



Concluímos a comunicação completa em nosso servidor, agora vamos falar sobre como obter informações úteis de solicitações de clientes.





A primeira etapa é analisar os parâmetros GET. Estes são os parâmetros que são transmitidos no URI de solicitação (por exemplo 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);
      
      



Em seguida, você precisa obter o corpo da solicitação





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;
      
      



Atenção As funções de retorno de chamada não oferecem suporte a interrupções (captura de valores com funções lambda), portanto, apenas membros e métodos estáticos podem ser usados ​​em retornos de chamada!








All Articles