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, ¶ms); // GET
// GET-
std::string value = evhttp_find_header(¶ms, "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(¶ms);
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!