
Looking through Habr from time to time, I occasionally meet posts on the topic of creating my own web server in C ++ or in another language. Since C ++ from programming languages ​​is of more interest to me, I read this blog the most. If you look through it, you can easily find how to write your web server "on sockets", using boost.asio or something else. Some time ago, I also published my post about creating such an http-server as an example of solving a test task. But without limiting myself to this, and for the sake of interest, I made comparisons with libevent and boost.asio developments. And the test task as such refused to perform.
For myself, somehow in my work, I considered libevent and libev. Each has its own advantages. If there is a desire or need for an early development of a small http-server, then libevent is of great interest to me, and with some innovations in C ++ 11, the code becomes much more compact and allows you to create a basic http-server in less than 40 lines.
The material of the post may be useful to those who are not familiar with libevent and there is a need to create their own http-server soon, as well as the material may interest people who have no such need yet and even if they already had experience in creating such a thing, it’s interesting to know their opinions and experiences. And since the post does not contain anything fundamentally new, it can be used as material for starting work in this direction, and therefore I will try to put the mark “teaching material”.
What libevent is different from, for example, libev and boost.asio, is that it has its own built-in http-server, and some abstraction for working with buffers. And also has a considerable set of auxiliary functions. You can disassemble the HTTP protocol yourself by writing a simple state machine or some other method. When working with libevent, this is all already there. This is such a nice bun, but you can go down to a lower level and write your own HTTP parser, while doing work with sockets on libevent. I liked the level of detail in the library because if you want to do something quickly, you can find a higher-level interface in it, which is usually less flexible. With the emergence of large needs, you can gradually go down level by level lower and lower. The library allows you to do many things: asynchronous I / O, work with the network, work with timers, rpc, ect; You can use it to create both server and client software.
')
What for?
Creating your own small http-server can be due to each of his own needs, the desire or unwillingness to use full-featured ready-made servers for one reason or another. Suppose you have some server software that works on some of its own protocol and solves some problems, and you have a need to issue some API for this software via the HTTP protocol. Perhaps just a few small functions for setting up the server and getting its current state via HTTP. For example, organizing the processing of GET requests with parameters and give a small xml with the answer or in some other format. In this case, you can create your own http-server with a small effort, which will be the interface for your main server software. In addition, if there is a need to create your own small specific service for the distribution of a set of files, or even create your own web application, you can also use such a self-written small server. In general, you can use both to build self-sufficient server software, and to create support services within larger systems.
Simple http server in less than 40 lines
To create a simple single-threaded http-server using libevent, you need to follow these several simple steps:
- Initialize the global library object using the event_init function. This function can only be used for single-threaded processing. For multi-threaded work on each thread must be created its own object (about this below).
- The creation of the http server itself is performed by the evhttp_start function in the case of a single-threaded server with a global event handling object. The object created with evhttp_start at the end should be deleted with evhttp_free.
- To respond to incoming requests, you need to set up a callback function using evhttp_set_gencb.
- Then you can start the event loop with the event_dispatch function. This function is also designed to work in one thread with a global object.
- When processing a request, you can get a buffer for a response with the evhttp_request_get_output_buffer function. Add some content to this buffer. For example, to send a string, you can use the evbuffer_add_printf function, and to send a file with the evbuffer_add_file function. After that, the response to the request should be sent, and this can be done using evhttp_send_reply.
Single-threaded server code in less than 40 lines:#include <memory> #include <cstdint> #include <iostream> #include <evhttp.h> int main() { if (!event_init()) { std::cerr << "Failed to init libevent." << std::endl; return -1; } char const SrvAddress[] = "127.0.0.1"; std::uint16_t SrvPort = 5555; std::unique_ptr<evhttp, decltype(&evhttp_free)> Server(evhttp_start(SrvAddress, SrvPort), &evhttp_free); if (!Server) { std::cerr << "Failed to init http server." << std::endl; return -1; } void (*OnReq)(evhttp_request *req, void *) = [] (evhttp_request *req, void *) { auto *OutBuf = evhttp_request_get_output_buffer(req); if (!OutBuf) return; evbuffer_add_printf(OutBuf, "<html><body><center><h1>Hello Wotld!</h1></center></body></html>"); evhttp_send_reply(req, HTTP_OK, "", OutBuf); }; evhttp_set_gencb(Server.get(), OnReq, nullptr); if (event_dispatch() == -1) { std::cerr << "Failed to run messahe loop." << std::endl; return -1; } return 0; }
It turned out less than 40 lines that can handle http requests, returning the string “Hello World”, and if you replace the evbuffer_add_printf function with evbuffer_add_file, you can send files. You can call such a server basic configuration. Any car dealer or realtor for the most part dreams that their cars and apartments never and under no circumstances leave as standard, but with additional options. But whether the consumer needs such options and to what extent ...
What this basic equipment can provide for speed can be checked using the
ab utility for * nix systems with a small variation of parameters.
ab -c 1000 -k -r -t 10 http://127.0.0.1.15555/Server Software:
Server Hostname: 127.0.0.1
Server Port: 5555
Document Path: /
Document Length: 64 bytes
Concurrency Level: 1000
Time taken for tests: 2.289 seconds
Complete requests: 50000
Failed requests: 0
Write errors: 0
Keep-Alive requests: 50000
Total transferred: 8500000 bytes
HTML transferred: 3200000 bytes
Requests per second: 21843.76 [# / sec] (mean)
Time per request: 45.780 [ms] (mean)
Time per request: 0.046 [ms] (mean, across all concurrent requests)
Transfer rate: 3626.41 [Kbytes / sec] received
Connection Times (ms)
min mean [± sd] median max
Connect: 0 3 48.6 0 1001
Processing: 17 42 9.0 43 93
Waiting: 17 42 9.0 43 93
Total: 19 45 49.7 43 1053
ab -c 1000 -r -t 10 http://127.0.0.1.15555/Server Software:
Server Hostname: 127.0.0.1
Server Port: 5555
Document Path: /
Document Length: 64 bytes
Concurrency Level: 1000
Time taken for tests: 5.004 seconds
Complete requests: 50000
Failed requests: 0
Write errors: 0
Total transferred: 6300000 bytes
HTML transferred: 3200000 bytes
Requests per second: 9992.34 [# / sec] (mean)
Time per request: 100.077 [ms] (mean)
Time per request: 0.100 [ms] (mean, across all concurrent requests)
Transfer rate: 1229.53 [Kbytes / sec] received
Connection Times (ms)
min mean [± sd] median max
Connect: 0 61 214.1 20 3028
Processing: 7 34 17.6 31 277
Waiting: 6 28 16.9 25 267
Total: 17 95 219.5 50 3055
The test was conducted on an already not entirely new laptop (2 cores, 4GB of RAM) running a 32-bit Ubuntu 12.10 operating system.
Multi-threaded http server
Do you need multithreading? A rhetorical question ... It is possible to organize all IOs and in one thread to organize, and to add requests to a queue and rake it in several streams. In this case, the above server can be simply supplemented with a queue and a pool of threads for processing, and nothing more should be done. If there is a desire or need to build a multi-threaded server, then it will be slightly longer than the previous one, but not much. C ++ 11 with its smart pointers allow to implement
RAII well, as was shown with std :: unique_ptr in the example above, and the presence of lambda functions slightly reduces the code.
An example of a multi-threaded server is similar in its ideology to a single-threaded one, and some features associated with multithreading increase it approximately 2 times by the amount of code. Eighty-and-a-few lines of code for a multi-threaded http server in C ++ is not so much.
One of the decisions you can make:
- Create multiple threads, for example, equal to twice the number of processor cores. C ++ 11 has support for working with streams and now you no longer need to write your wrappers.
- For each thread, create your own object of working with events using the event_base_new function. The created object must be deleted at the end by the event_base_free function, and std :: unique_ptr and RAII allow for this to be made more compact.
- For each stream, taking into account the above object, create your own http server object using the evhttp_new function. This object should also be deleted at the end, and this can be done with the help of evhttp_free.
- As in the previous example, install the request handler using evhttp_set_gencb.
- This step may be the strangest. It is necessary to create and bind a socket to the network interface for several handlers, each of which is located in its stream. Here you can use the API for working with sockets (create a socket, configure it, bind to a specific interface), and then transfer the socket to work with the server using the evhttp_accept_socket function. This is a long time. Libevent provides several functions for this task. As mentioned above, libevent gives you the opportunity, if necessary, to go down to a lower and lower level, depending on need, and choose the best one for you. In this case, for the first thread, all the work on creating a socket, its configuration and binding is performed by the evhttp_bind_socket_with_handle function and the socket for other threads is extracted from the configured object using evhttp_bound_socket_get_fd. All other threads are already using the received socket, setting it for processing by the evhttp_accept_socket function. A bit strange, but much easier than using the API for working with sockets, and even easier if you take into account the cross-platform. It would seem that the API for Berkeley sockets is one and the same, but if you wrote cross-platform software using it, for example, for Windows and Linux, then the code written for one operating system is definitely not equivalent to the code for another.
- Start the event loop. Unlike a single-threaded server, this should be done in a different way, since the objects are different for everyone. For this there is a special function in libevent (event_base_dispatch). For myself, I see one minus in it - it is difficult to correct it in the correct way (for example, you need to have a situation in which you can call event_base_loopexit). For this you need to dodge a little. And so you can use the event_base_loop function. This function does not block even if there are no events to be processed, it returns control, which gives a simplified opportunity to complete the event processing cycle and the ability to do something between calls. There is also a minus - in order not to load the processor at idle in vain, you need to put at least a small delay (in C ++ 11 - 'this is easy to do something like this: std :: this_thread :: sleep_for (std :: chrono :: milliseconds (10)) ).
- Processing requests is similar to the first example.
- In the course of creating and configuring the next thread, something may not be right in its function: for example, some libevent function reported an error. In this case, you can throw an exception and intercept it, and then send it out of the stream using all the same C ++ 11 tools (std :: exception_ptr, std :: current_exception and std :: rethrow_exception)
Code for a simple multi-threaded server: #include <stdexcept> #include <iostream> #include <memory> #include <chrono> #include <thread> #include <cstdint> #include <vector> #include <evhttp.h> int main() { char const SrvAddress[] = "127.0.0.1"; std::uint16_t const SrvPort = 5555; int const SrvThreadCount = 4; try { void (*OnRequest)(evhttp_request *, void *) = [] (evhttp_request *req, void *) { auto *OutBuf = evhttp_request_get_output_buffer(req); if (!OutBuf) return; evbuffer_add_printf(OutBuf, "<html><body><center><h1>Hello Wotld!</h1></center></body></html>"); evhttp_send_reply(req, HTTP_OK, "", OutBuf); }; std::exception_ptr InitExcept; bool volatile IsRun = true; evutil_socket_t Socket = -1; auto ThreadFunc = [&] () { try { std::unique_ptr<event_base, decltype(&event_base_free)> EventBase(event_base_new(), &event_base_free); if (!EventBase) throw std::runtime_error("Failed to create new base_event."); std::unique_ptr<evhttp, decltype(&evhttp_free)> EvHttp(evhttp_new(EventBase.get()), &evhttp_free); if (!EvHttp) throw std::runtime_error("Failed to create new evhttp."); evhttp_set_gencb(EvHttp.get(), OnRequest, nullptr); if (Socket == -1) { auto *BoundSock = evhttp_bind_socket_with_handle(EvHttp.get(), SrvAddress, SrvPort); if (!BoundSock) throw std::runtime_error("Failed to bind server socket."); if ((Socket = evhttp_bound_socket_get_fd(BoundSock)) == -1) throw std::runtime_error("Failed to get server socket for next instance."); } else { if (evhttp_accept_socket(EvHttp.get(), Socket) == -1) throw std::runtime_error("Failed to bind server socket for new instance."); } for ( ; IsRun ; ) { event_base_loop(EventBase.get(), EVLOOP_NONBLOCK); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } catch (...) { InitExcept = std::current_exception(); } }; auto ThreadDeleter = [&] (std::thread *t) { IsRun = false; t->join(); delete t; }; typedef std::unique_ptr<std::thread, decltype(ThreadDeleter)> ThreadPtr; typedef std::vector<ThreadPtr> ThreadPool; ThreadPool Threads; for (int i = 0 ; i < SrvThreadCount ; ++i) { ThreadPtr Thread(new std::thread(ThreadFunc), ThreadDeleter); std::this_thread::sleep_for(std::chrono::milliseconds(500)); if (InitExcept != std::exception_ptr()) { IsRun = false; std::rethrow_exception(InitExcept); } Threads.push_back(std::move(Thread)); } std::cout << "Press Enter fot quit." << std::endl; std::cin.get(); IsRun = false; } catch (std::exception const &e) { std::cerr << "Error: " << e.what() << std::endl; } return 0; }
You can see in the code that each thread is created after some wait has been added. This is a small hack that will already be fixed in the final version of the server. For the time being, one can only say that if this is not done, the threads will need to be somehow synchronized so that they can work out the “strange step” in creating and binding the socket. For simplicity, let there be such a hack for now. Also, in the above code, the lambda function may seem like a controversial decision. Lambdas can be a good solution when used, for example, as a predicate when working with standard algorithms. At the same time, you can think about using them when writing larger code fragments. In the example above, it was possible to put everything into a normal function, pass all the necessary parameters and get the code in the C ++ 03 style. At the same time, the use of lambda reduced the amount of code. In my opinion, when the code is small, then lambdas can quite well enter into it even with not the shortest of its content and not adversely affect the quality of the code, of course you should not go into extremes and recall student workdays with writing a laboratory work of 700 lines in a single main functions.
Testing a multi-threaded server was carried out with the same parameters as the previous example.
ab -c 1000 -k -r -t 10 http://127.0.0.1.15555/Server Software:
Server Hostname: 127.0.0.1
Server Port: 5555
Document Path: /
Document Length: 64 bytes
Concurrency Level: 1000
Time taken for tests: 1.576 seconds
Complete requests: 50000
Failed requests: 0
Write errors: 0
Keep-Alive requests: 50000
Total transferred: 8500000 bytes
HTML transferred: 3200000 bytes
Requests per second: 31717.96 [# / sec] (mean)
Time per request: 31.528 [ms] (mean)
Time per request: 0.032 [ms] (mean, across all concurrent requests)
Transfer rate: 5265.68 [Kbytes / sec] received
ab -c 1000 -r -t 10 http://127.0.0.1.15555/Server Software:
Server Hostname: 127.0.0.1
Server Port: 5555
Document Path: /
Document Length: 64 bytes
Concurrency Level: 1000
Time taken for tests: 3.685 seconds
Complete requests: 50000
Failed requests: 0
Write errors: 0
Total transferred: 6300000 bytes
HTML transferred: 3200000 bytes
Requests per second: 13568.41 [# / sec] (mean)
Time per request: 73.701 [ms] (mean)
Time per request: 0.074 [ms] (mean, across all concurrent requests)
Transfer rate: 1669.55 [Kbytes / sec] received
Connection Times (ms)
min mean [± sd] median max
Connect: 0 36 117.2 23 1033
Processing: 3 37 10.0 37 247
Waiting: 3 30 8.7 30 242
Total: 9 73 118.8 61 1089
Final server
Basic equipment is provided, equipment with a small set of options is also there. Now the turn came and to create something more useful and functional, as well as with a little tuning.
Minimum http-server:
#include "http_server.h" #include "http_headers.h" #include "http_content_type.h" #include <iostream> int main() { try { using namespace Network; HttpServer Srv("127.0.0.1", 5555, 4, [&] (IHttpRequestPtr req) { req->SetResponseAttr(Http::Response::Header::Server::Value, "MyTestServer"); req->SetResponseAttr(Http::Response::Header::ContentType::Value, Http::Content::Type::html::Value); req->SetResponseString("<html><body><center><h1>Hello Wotld!</h1></center></body></html>"); }); std::cout << "Press Enter for quit." << std::endl; std::cin.get(); } catch (std::exception const &e) { std::cout << e.what() << std::endl; } return 0; }
Very minimal amount of code for the http-server in C ++. For everything there is a fee. And in this case, such simplicity of the client code for creating a server is paid for by a longer implementation hidden in the proposed wrapper over libevent. In fact, the implementation has not increased much. Just below its fragments will be described.
Server creation:
- You must create an object of type HttpServer. As parameters, at a minimum, pass the address and port on which the server will work, the number of threads and the function to process requests (in this case, since request processing is minimal, then you can do a little lambda without creating a separate function or even a whole handler class) . After an object is created, the server will work as long as its object exists.
- The handler accepts a smart pointer to the IHttpRequest interface, the implementation of which hides all the work with the libevent buffer and the sending of a response, and its methods make it possible to receive data from an incoming request and generate a response.
Interface IHttpRequest namespace Network { DECLARE_RUNTIME_EXCEPTION(HttpRequest) struct IHttpRequest { enum class Type { HEAD, GET, PUT, POST }; typedef std::unordered_map<std::string, std::string> RequestParams; virtual ~IHttpRequest() {} virtual Type GetRequestType() const = 0; virtual std::string const GetHeaderAttr(char const *attrName) const = 0; virtual std::size_t GetContentSize() const = 0; virtual void GetContent(void *buf, std::size_t len, bool remove) const = 0; virtual std::string const GetPath() const = 0; virtual RequestParams const GetParams() const = 0; virtual void SetResponseAttr(std::string const &name, std::string const &val) = 0; virtual void SetResponseCode(int code) = 0; virtual void SetResponseString(std::string const &str) = 0; virtual void SetResponseBuf(void const *data, std::size_t bytes) = 0; virtual void SetResponseFile(std::string const &fileName) = 0; }; typedef std::shared_ptr<IHttpRequest> IHttpRequestPtr; }
This interface allows you to receive from the incoming request its type, some attributes (headers), the size of the request body and the request body itself, if any, as well as form a response with the ability to specify attributes (headers), request completion code and response body (in this implementations have methods for passing a string, some buffer or file in response). Each method in its implementation can throw an exception of type HttpRequestException.
If you look at the server code again, you can see the following lines in the request processing code:
req->SetResponseAttr(Http::Response::Header::Server::Value, "MyTestServer"); req->SetResponseAttr(Http::Response::Header::ContentType::Value, Http::Content::Type::html::Value);
This is the formation of the response header, and in this example such header fields as “Content-Type” and “Server” are defined. Despite the fact that libevent has a fairly wide functionality that goes far beyond the needs of HTTP, there is no list of header field constants in it; there is only a partial list of return codes (most commonly used). To avoid messing with strings defining header fields (for example, in order to avoid typos in user code), all constants are already defined in the proposed wrapper over libevent.
Example of String Constant Definition namespace Network { namespace Http { namespace Request { namespace Header { DECLARE_STRING_CONSTANT(Accept, Accept) DECLARE_STRING_CONSTANT(AcceptCharset, Accept-Charset)
String constants can be defined as simple old-style macros of pure C in header files, or to distribute their declarations and definitions between .h and .cpp files, making them already typed in C ++ style. However, you can do without spacing on files, and make all typed definitions in the C ++ style only in the header file. To do this, you can use some approach with templates and write such a macro (macros, of course, a recognized C ++ evil, as well as in small dosages — a balm; heterogeneous solutions are more viable).
DECLARE_STRING_CONSTANT #define DECLARE_STRING_CONSTANT(name_, value_) \ namespace Private \ { \ template <typename T> \ struct name_ \ { \ static char const Name[]; \ static char const Value[]; \ }; \ template <typename T> \ char const name_ <T>::Name[] = #name_; \ template <typename T> \ char const name_ <T>::Value[] = #value_; \ } \ typedef Private:: name_ <void> name_;
The constants for setting the type of content are also defined in almost the same way; have a slight modification. There was a desire to implement a content type search by file extension for convenience when sending files in response to a request.
If you want to get something from the incoming request, for example, from which host and from which page the transition was made to the requested resource and, for example, whether the user has cookies, you can get this from the header of the incoming request in this way:
std::string Host = req->GetHeaderAttr(Http::Request::Header::Host::Value); std::string Referer = req->GetHeaderAttr(Http::Request::Header::Referer::Value); std::string Cookie = req->GetHeaderAttr(Http::Request::Header::Cookie::Value);
Similarly, in the response, you can, for example, set the user some Cookies, which later work with his session and track if you want to wander through your resource (an example of working with response headers is given in the server code).
If there is a desire to organize some of its API via HTTP, then this is just as easy to do. Suppose you need to create methods: opening a session, obtaining statistical information about the server and closing the session. Let for this query string to your server look like this:
http://myserver.com/service/login/OpenSession?user=nym&pwd=kakoyto
http://myserver.com/service/login/CliseSession?sessionId=nym1234567890
http://myserver.com/service/stat/GetInfo?sessionId=nym1234567890
By answering these query lines, the user's server can generate some kind of response, for example, in xml format. This is a server developer case. But how to work with such requests, get the parameters from them below:
auto Path = req->GetPath(); auto Params = req->GetParams();
One of the ways for the examples above will be / service / login / OpenSession, and the parameters are the map from the passed key / value pairs. Type of parameter map:
typedef std::unordered_map<std::string, std::string> RequestParams;
After analyzing all that can be implemented using the proposed final version of the wrapper over libevent, you can look under the hood of the wrapper itself.
Class HttpServer namespace Network { DECLARE_RUNTIME_EXCEPTION(HttpServer) class HttpServer final : private Common::NonCopyable { public: typedef std::vector<IHttpRequest::Type> MethodPool; typedef std::function<void (IHttpRequestPtr)> OnRequestFunc; enum { MaxHeaderSize = static_cast<std::size_t>(-1), MaxBodySize = MaxHeaderSize }; HttpServer(std::string const &address, std::uint16_t port, std::uint16_t threadCount, OnRequestFunc const &onRequest, MethodPool const &allowedMethods = {IHttpRequest::Type::GET }, std::size_t maxHeadersSize = MaxHeaderSize, std::size_t maxBodySize = MaxBodySize); private: volatile bool IsRun = true; void (*ThreadDeleter)(std::thread *t) = [] (std::thread *t) { t->join(); delete t; };; typedef std::unique_ptr<std::thread, decltype(ThreadDeleter)> ThreadPtr; typedef std::vector<ThreadPtr> ThreadPool; ThreadPool Threads; Common::BoolFlagInvertor RunFlag; }; } </source</spoiler> <spoiler title=" HttpServer"><source lang="cpp"> namespace Network { HttpServer::HttpServer(std::string const &address, std::uint16_t port, std::uint16_t threadCount, OnRequestFunc const &onRequest, MethodPool const &allowedMethods, std::size_t maxHeadersSize, std::size_t maxBodySize) : RunFlag(&IsRun) { int AllowedMethods = -1; for (auto const i : allowedMethods) AllowedMethods |= HttpRequestTypeToAllowedMethod(i); bool volatile DoneInitThread = false; std::exception_ptr Except; evutil_socket_t Socket = -1; auto ThreadFunc = [&] () { try { bool volatile ProcessRequest = false; RequestParams ReqPrm; ReqPrm.Func = onRequest; ReqPrm.Process = &ProcessRequest; typedef std::unique_ptr<event_base, decltype(&event_base_free)> EventBasePtr; EventBasePtr EventBase(event_base_new(), &event_base_free); if (!EventBase) throw HttpServerException("Failed to create new base_event."); typedef std::unique_ptr<evhttp, decltype(&evhttp_free)> EvHttpPtr; EvHttpPtr EvHttp(evhttp_new(EventBase.get()), &evhttp_free); if (!EvHttp) throw HttpServerException("Failed to create new evhttp."); evhttp_set_allowed_methods(EvHttp.get(), AllowedMethods); if (maxHeadersSize != MaxHeaderSize) evhttp_set_max_headers_size(EvHttp.get(), maxHeadersSize); if (maxBodySize != MaxBodySize) evhttp_set_max_body_size(EvHttp.get(), maxBodySize); evhttp_set_gencb(EvHttp.get(), &OnRawRequest, &ReqPrm); if (Socket == -1) { auto *BoundSock = evhttp_bind_socket_with_handle(EvHttp.get(), address.c_str(), port); if (!BoundSock) throw HttpServerException("Failed to bind server socket."); if ((Socket = evhttp_bound_socket_get_fd(BoundSock)) == -1) throw HttpServerException("Failed to get server socket for next instance."); } else { if (evhttp_accept_socket(EvHttp.get(), Socket) == -1) throw HttpServerException("Failed to bind server socket for new instance."); } DoneInitThread = true; for ( ; IsRun ; ) { ProcessRequest = false; event_base_loop(EventBase.get(), EVLOOP_NONBLOCK); if (!ProcessRequest) std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } catch (...) { Except = std::current_exception(); } }; ThreadPool NewThreads; for (int i = 0 ; i < threadCount ; ++i) { DoneInitThread = false; ThreadPtr Thread(new std::thread(ThreadFunc), ThreadDeleter); NewThreads.push_back(std::move(Thread)); for ( ; ; ) { if (Except != std::exception_ptr()) { IsRun = false; std::rethrow_exception(Except); } if (DoneInitThread) break; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } Threads = std::move(NewThreads); } }
The query processing function can be viewed in the full version by downloading the source files of the examples, it became a bit more than in the earlier examples, and stopped claiming lambda without losing the readability of the code. I also did not mention the implementation of the IHttpRequest interface, since it is of little interest in its routine work with the libevent buffer. And the rest if you look at the code of the final version, it has not changed much. A small modification and added a bit of "tuning".
The user server is not required to handle all types of http requests. , libevent evhttp_set_allowed_methods ( GET). libevent , .
: . «» - http- evhttp_set_max_headers_size evhttp_set_max_body_size. , . . - , , …
, GET ( ) , .
http- #include "http_server.h" #include "http_headers.h" #include "http_content_type.h" #include <iostream> #include <sstream> #include <mutex> int main() { char const SrvAddress[] = "127.0.0.1"; std::uint16_t SrvPort = 5555; std::uint16_t SrvThreadCount = 4; std::string const RootDir = "../test_content"; std::string const DefaultPage = "index.html"; std::mutex Mtx; try { using namespace Network; HttpServer Srv(SrvAddress, SrvPort, SrvThreadCount, [&] (IHttpRequestPtr req) { std::string Path = req->GetPath(); Path = RootDir + Path + (Path == "/" ? DefaultPage : std::string()); { std::stringstream Io; Io << "Path: " << Path << std::endl << Http::Request::Header::Host::Name << ": " << req->GetHeaderAttr(Http::Request::Header::Host::Value) << std::endl << Http::Request::Header::Referer::Name << ": " << req->GetHeaderAttr(Http::Request::Header::Referer::Value) << std::endl; std::lock_guard<std::mutex> Lock(Mtx); std::cout << Io.str() << std::endl; } req->SetResponseAttr(Http::Response::Header::Server::Value, "MyTestServer"); req->SetResponseAttr(Http::Response::Header::ContentType::Value, Http::Content::TypeFromFileName(Path)); req->SetResponseFile(Path); }); std::cin.get(); } catch (std::exception const &e) { std::cout << e.what() << std::endl; } return 0; }
Conclusion
libevent . : . , http-.
github .
http server .
:
ab -c 1000 -k -r -t 10 http://localhost:8888/libevent_test_http_srv.zipServer Software: test
Server Hostname: test
Server Port: 8888
Document Path: /libevent_test_http_srv.zip
Document Length: 23756 bytes
Concurrency Level: 1000
Time taken for tests: 10.012 seconds
Complete requests: 2293
Failed requests: 0
Write errors: 0
Keep-Alive requests: 2293
Total transferred: 60628847 bytes
HTML transferred: 60328370 bytes
Requests per second: 229.02 [#/sec] (mean)
Time per request: 4366.365 [ms] (mean)
Time per request: 4.366 [ms] (mean, across all concurrent requests)
Transfer rate: 5913.65 [Kbytes/sec] received
…
Thank you all for your attention!
Materials