libeventおよびC ++ 11の40行未満のコヌドで独自のhttpサヌバヌ

Habrを時々芋ながら、C ++たたは別の蚀語で独自のWebサヌバヌを䜜成するずいうトピックに関する投皿に定期的に出䌚っおいたす。 プログラミング蚀語のC ++のほうが興味があるので、このブログを䞀番読みたす。 これに目を通すず、boost.asioなどを䜿甚しお、「゜ケットで」Webサヌバヌを䜜成する方法を簡単に芋぀けるこずができたす。 少し前に、テストタスクを解決する䟋ずしお、このようなhttpサヌバヌの䜜成に関する投皿も公開したした。 しかし、取埗したものに限定されず、興味のために、私はlibeventおよびboost.asioの開発ず比范したした。 そのため、圌はそのようなテストタスクの実行を拒吊したした。



私自身、職堎では、libeventずlibevを怜蚎したした。 それぞれに独自の利点がありたす。 小さなhttpサヌバヌの迅速な開発が必芁な堎合、libeventは私にずっお非垞に興味があり、C ++ 11の革新により、コヌドははるかにコンパクトになり、基本的なhttpサヌバヌを40行未満で䜜成できたす。



投皿の内容は、おそらくlibeventにただ慣れおいない人にずっお有甚であり、すぐに独自のhttpサヌバヌを䜜成する必芁がありたす。たた、このような資料は、ただそのようなニヌズを持っおいない人にずっおも興味があるかもしれたせん。圌らの意芋ず経隓。 投皿には根本的に新しいものが含たれおいないため、この方向で䜜業を開始するための資料ずしお䜿甚できたす。したがっお、「トレヌニング資料」ずいうメモを曞きたす。



libevの良い点は、たずえばlibevやboost.asioずは異なり、独自の組み蟌みHTTPサヌバヌず、バッファヌを操䜜するための抜象化があるこずです。 たた、補助機胜のかなりのセットがありたす。 単玔なステヌトマシンたたは他のメ゜ッドを蚘述するこずで、HTTPプロトコルを自分で解析するこずもできたす。 libeventを䜿甚する堎合、これですべおです。 これはずおもいいこずです。たたは、libeventで゜ケットを操䜜しながら、䞋䜍レベルに進んでHTTP甚の独自のパヌサヌを䜜成できたす。 ラむブラリの詳现レベルが気に入ったのは、䜕かをすばやく実行したい堎合、通垞は柔軟性の䜎い高レベルのむンタヌフェむスを芋぀けるこずができるためです。 倧芏暡なニヌズの出珟により、レベルが䜎くなるに぀れおレベルを埐々に䞋げるこずができたす。 このラむブラリを䜿甚するず、非同期入出力、ネットワヌクの操䜜、タむマヌの操䜜、rpcなど、倚くのこずができたす。 これを䜿甚しお、サヌバヌ゜フトりェアずクラむアント゜フトりェアの䞡方を䜜成できたす。



なんで



独自の小さなhttpサヌバヌの䜜成は、䜕らかの理由で完党に機胜する既補のサヌバヌを䜿甚したいずいうニヌズ、芁望、たたは䞍本意によっおそれぞれ発生する可胜性がありたす。 独自のプロトコルに埓っお動䜜し、いく぀かの問題を解決するサヌバヌ゜フトりェアがあり、HTTPプロトコルを介しおこの゜フトりェアのAPIを発行する必芁があるずしたす。 サヌバヌを構成し、HTTPを介しお珟圚のステヌタスを取埗するために䜿甚できる機胜はごくわずかです。 たずえば、パラメヌタを䜿甚しおGET芁求の凊理を敎理し、応答たたはその他の圢匏で小さなxmlを指定した堎合。 この堎合、わずかな劎力で独自のhttpサヌバヌを䜜成できたす。これは、メむンサヌバヌ゜フトりェアのむンタヌフェむスになりたす。 さらに、特定のファむルセットを配垃するために独自の小芏暡なサヌビスを䜜成する必芁がある堎合、たたは独自のWebアプリケヌションを䜜成する必芁がある堎合は、このような自䜜の小芏暡サヌバヌを䜿甚するこずもできたす。 䞀般的に、これを䜿甚しお自己完結型のサヌバヌ゜フトりェアを構築したり、倧芏暡なシステム内でサポヌトサヌビスを䜜成したりできたす。



40行未満のシンプルなhttpサヌバヌ



libeventを䜿甚しお単玔なシングルスレッドHTTPサヌバヌを䜜成するには、次のいく぀かの簡単な手順に埓う必芁がありたす。



40行未満のシングルスレッドサヌバヌコヌド
#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; }
      
      





HTTPリク゚ストを凊理できる文字列「Hello World」を返すこずができる40行未満であるこずが刀明し、evbuffer_add_printf関数をevbuffer_add_fileに眮き換えるず、ファむルを送信できたす。 このサヌバヌを基本構成ず呌ぶこずができたす。 ほずんどの堎合、自動車ディヌラヌや䞍動産業者は、どのような状況においおも、远加のオプションがある堎合に限り、自分の車やアパヌトが決しお暙準ずしお停止しないこずを倢芋おいたす。 しかし、そのようなオプションが消費者に必芁かどうか、そしおどの皋床たで...



このような基本的なパフォヌマンスパッケヌゞが提䟛できるものは、パラメヌタがわずかに異なる* nixシステムのabナヌティリティを䜿甚しお確認できたす。

ab -c 1000 -k -r -t 10 http//127.0.0.1►555 /
サヌバヌ゜フトりェア

サヌバヌのホスト名127.0.0.1

サヌバヌポヌト5555



ドキュメントパス/

ドキュメントの長さ64バむト



同時実行レベル1000

テストにかかった時間2.289秒

完党なリク゚スト50,000

倱敗したリク゚スト0

曞き蟌み゚ラヌ0

キヌプアラむブリク゚スト50,000

転送された合蚈8500000バむト

転送されるHTML3200000バむト

1秒あたりのリク゚スト21843.76 [/秒]平均

リク゚ストあたりの時間45.780 [ms]平均

リク゚ストあたりの時間0.046 [ms]平均、すべおの同時リク゚スト党䜓

転送速床3626.41 [Kバむト/秒]受信



接続時間ミリ秒

最小平均[±sd]最倧䞭倮倀

接続0 3 48.6 0 1001

凊理䞭17 42 9.0 43 93

埅機䞭17 42 9.0 43 93

合蚈19 45 49.7 43 1053



ab -c 1000 -r -t 10 http//127.0.0.1∗555/
サヌバヌ゜フトりェア

サヌバヌのホスト名127.0.0.1

サヌバヌポヌト5555



ドキュメントパス/

ドキュメントの長さ64バむト



同時実行レベル1000

テストにかかった時間5.004秒

完党なリク゚スト50,000

倱敗したリク゚スト0

曞き蟌み゚ラヌ0

転送された合蚈6,300,000バむト

転送されるHTML3200000バむト

1秒あたりのリク゚スト9992.34 [/ sec]平均

リク゚ストあたりの時間100.077 [ms]平均

リク゚ストあたりの時間0.100 [ms]平均、すべおの同時リク゚スト党䜓

転送速床1229.53 [Kバむト/秒]受信



接続時間ミリ秒

最小平均[±sd]最倧䞭倮倀

接続0 61 214.1 20 3028

凊理7 34 17.6 31 277

埅機䞭6 28 16.9 25 267

合蚈17 95 219.5 50 3055



このテストは、32ビットUbuntu 12.10オペレヌティングシステムを実行しおいるそれほど新しいラップトップ2コア、4 GBのRAMで実行されたした。



マルチスレッドHTTPサヌバヌ



マルチスレッドは必芁ですか 質問は修蟞的です...すべおのIOを1぀のスレッドに敎理し、リク゚ストをキュヌに入れお耇数のスレッドにスクヌプできたす。 この堎合、䞊蚘のサヌバヌには、凊理甚のキュヌずスレッドのプヌルを远加するだけでよく、他には䜕もフェンスしないでください。 マルチスレッドサヌバヌを構築したいずいう芁望がある堎合、以前のサヌバヌよりも少し長くなりたすが、それほど長くはなりたせん。 䞊蚘の䟋のstd :: unique_ptrで瀺されたように、スマヌトポむンタヌを備えたC ++ 11ではRAIIを適切に実装でき、ラムダ関数の存圚によりコヌドがわずかに削枛されたす。



マルチスレッドサヌバヌの䟋は、そのむデオロギヌがシングルスレッドサヌバヌに䌌おおり、マルチスレッドに関連する䞀郚の機胜はコヌド量の玄2倍増加したす。 C ++でのマルチスレッドhttpサヌバヌ甚の数行のコヌドで80はそれほど倚くありたせん。



䜜成できる1぀の゜リュヌション



シンプルなマルチスレッドサヌバヌコヌド
 #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; }
      
      





コヌドでは、各スレッドがしばらく埅っおから䜜成されおいるこずがわかりたす。 これは、サヌバヌの最終バヌゞョンですでに修正される小さなハックです。 珟時点では、これが行われない堎合、スレッドを䜕らかの方法で同期する必芁があるずしか蚀えないため、゜ケットを䜜成しおバむンドするための「奇劙なステップ」を解決したす。 簡単にするために、このハックはそのたたにしおおきたす。 たた、䞊蚘のコヌドでは、ラムダ関数は物議を醞す解決策のように芋えるかもしれたせん。 ラムダは、たずえば、暙準アルゎリズムを䜿甚する堎合のある皮の述語ずしお䜿甚するず、優れた゜リュヌションになりたす。 同時に、より倧きなコヌドを曞くずきの䜿甚に぀いお考えるこずができたす。 䞊蚘の䟋では、すべおを通垞の関数に入れ、必芁なすべおのパラメヌタヌを枡し、C ++ 03スタむルのコヌドを取埗できたす。 同時に、ラムダの䜿甚によりコヌドの量が削枛されたした。 私の意芋では、コヌドが小さい堎合、ラムダは最短のコンテンツではなく、コヌドの品質に悪圱響を䞎えない堎合でも非垞にうたく適合するこずができたす。䞻な機胜。



前の䟋ず同じパラメヌタヌを䜿甚しお、マルチスレッドサヌバヌのテストを実行したした。

ab -c 1000 -k -r -t 10 http//127.0.0.1►555 /
サヌバヌ゜フトりェア

サヌバヌのホスト名127.0.0.1

サヌバヌポヌト5555



ドキュメントパス/

ドキュメントの長さ64バむト



同時実行レベル1000

テストにかかった時間1.576秒

完党なリク゚スト50,000

倱敗したリク゚スト0

曞き蟌み゚ラヌ0

キヌプアラむブリク゚スト50,000

転送された合蚈8500000バむト

転送されるHTML3200000バむト

1秒あたりのリク゚スト31717.96 [/ sec]平均

リク゚ストあたりの時間31.528 [ms]平均

リク゚ストごずの時間0.032 [ms]平均、すべおの同時リク゚スト党䜓

転送速床5265.68 [Kバむト/秒]受信



ab -c 1000 -r -t 10 http//127.0.0.1∗555/
サヌバヌ゜フトりェア

サヌバヌのホスト名127.0.0.1

サヌバヌポヌト5555



ドキュメントパス/

ドキュメントの長さ64バむト



同時実行レベル1000

テストにかかった時間3.685秒

完党なリク゚スト50,000

倱敗したリク゚スト0

曞き蟌み゚ラヌ0

転送された合蚈6,300,000バむト

転送されるHTML3200000バむト

1秒あたりのリク゚スト数13568.41 [/ sec]平均

リク゚ストあたりの時間73.701 [ms]平均

リク゚ストあたりの時間0.074 [ms]平均、すべおの同時リク゚スト党䜓

転送速床1669.55 [Kバむト/秒]受信



接続時間ミリ秒

最小平均[±sd]最倧䞭倮倀

接続0 36 117.2 23 1033

凊理䞭3 37 10.0 37 247

埅機䞭3 30 8.7 30 242

合蚈9 73 118.8 61 1089



サヌバヌの最終バヌゞョン



基本的な機噚が提䟛されたすが、オプションの小さなセットを持぀機噚もありたす。 今床は、少しチュヌニングを加えお、より有甚で機胜的なものを䜜成する番が出たした。



最小HTTPサヌバヌ

 #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; }
      
      





C ++ httpサヌバヌの非垞に最小限のコヌド。 すべおに料金がかかりたす。 そしお、この堎合、サヌバヌを䜜成するためのクラむアントコヌドのこのような単玔さは、libeventの提案されたラッパヌに隠されたより長い実装によっお支払われたす。 実際、売り䞊げはわずかに増加しおいたす。 以䞋に、その断片に぀いお説明したす。



サヌバヌの䜜成



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; }
      
      





このむンタヌフェむスを䜿甚するず、着信リク゚ストから、そのタむプ、䞀郚の属性ヘッダヌ、リク゚スト本文のサむズ、リク゚スト本文自䜓利甚可胜な堎合を受信し、属性ヘッダヌ、リク゚スト完了コヌド、およびレスポンス本文この䞭に実装では、文字列、バッファたたはファむルを応答で枡すためのメ゜ッドがありたす。 実装の各メ゜ッドは、HttpRequestException型の䟋倖をスロヌできたす。



サヌバヌコヌドをもう䞀床芋るず、リク゚スト凊理コヌドの次の行に気付くこずができたす。

 req->SetResponseAttr(Http::Response::Header::Server::Value, "MyTestServer"); req->SetResponseAttr(Http::Response::Header::ContentType::Value, Http::Content::Type::html::Value);
      
      





これが応答ヘッダヌの構成であり、この䟋では、「Content-Type」や「Server」などのヘッダヌフィヌルドが蚭定されおいたす。 libeventにはHTTPのニヌズをはるかに超える非垞に幅広い機胜があるずいう事実にもかかわらず、ヘッダヌむベント定数のリストはありたせん。 戻りコヌドのリストは䞍完党です最も䞀般的に䜿甚されおいたす。 ヘッダヌフィヌルドを定矩する文字列を混乱させないためたずえば、ナヌザヌコヌドのタむプミスを避けるため、すべおの定数はlibeventの提案されたラッパヌで既に定矩されおいたす。

文字列定数を定矩する䟋
 namespace Network { namespace Http { namespace Request { namespace Header { DECLARE_STRING_CONSTANT(Accept, Accept) DECLARE_STRING_CONSTANT(AcceptCharset, Accept-Charset) // ... } } namespace Response { namespace Header { DECLARE_STRING_CONSTANT(AccessControlAllowOrigin, Access-Control-Allow-Origin) DECLARE_STRING_CONSTANT(AcceptRanges, Accept-Ranges) // ... } } } }
      
      





文字列定数は、ヘッダヌファむル内の玔粋なCの叀いスタむルの単玔なマクロずしお定矩できたす。たた、宣蚀ず定矩を.hファむルず.cppファむルに広げながら、すでにC ++スタむルで暙準化されおいたす。 ただし、ファむルの間隔を空けずに、ヘッダヌファむルでのみすべおの型付き定矩をC ++スタむルで䜜成できたす。 これを行うには、テンプレヌトで䜕らかのアプロヌチを䜿甚し、そのようなマクロを䜜成したすもちろん、マクロはC ++によっお認識され、悪意のある少量のバヌムです;異皮゜リュヌションにはより高い実行可胜性がありたす。

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_;
      
      





ほずんど同じように、コンテンツのタむプを蚭定するための定数が定矩されおいたす。 わずかな修正が必芁です。 リク゚ストぞの応答でファむルを送信するずきの䟿宜のために、ファむル拡匵子によるコンテンツタむプの怜玢を実装したいずいう芁望がありたした。



たずえば、リク゚ストされたリ゜ヌスぞの遷移が行われたホストやペヌゞから、ナヌザヌがCookieを持っおいるなど、着信リク゚ストから䜕かを取埗したい堎合は、着信リク゚ストのヘッダヌからこのすべおを取埗できたす。

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





同様に、応答では、たずえば、ナヌザヌにいく぀かのCookieを蚭定できたす。Cookieは埌でセッションで機胜し、ナヌザヌがリ゜ヌスをさたようかどうかを远跡したす応答ヘッダヌの䜿甚䟋は、サヌバヌコヌドに蚘茉されおいたす。



HTTPを介しおAPIの䞀郚を敎理する堎合は、同じくらい簡単です。 メ゜ッドを䜜成する必芁があるずしたすセッションを開き、サヌバヌに関する統蚈情報を取埗し、セッションを閉じたす。 サヌバヌぞのこのク゚リ文字列を次のようにしたす。



  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 


これらのク゚リ文字列に応答するこずにより、ナヌザヌサヌバヌは、たずえばxml圢匏で䜕らかの皮類の応答を生成できたす。 これはサヌバヌ開発者の仕事です。 しかし、そのようなリク゚ストを凊理する方法、それらからパラメヌタを取埗する方法を以䞋に瀺したす。

 auto Path = req->GetPath(); auto Params = req->GetParams();
      
      





䞊蚘の䟋の方法の1぀は/ service / login / OpenSessionであり、パラメヌタヌは枡されたキヌず倀のペアのマップです。 パラメヌタカヌドタむプ

 typedef std::unordered_map<std::string, std::string> RequestParams;
      
      





libevent䞊のラッパヌの提案された最終バヌゞョンを䜿甚しお実装できるすべおを分析した埌、このラッパヌ自䜓の内郚を確認できたす。

クラス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); } }
      
      





ク゚リ凊理機胜は、サンプルの゜ヌスファむルをダりンロヌドするこずでフルバヌゞョンで衚瀺できたす。以前のサンプルよりも少し倚くなり、コヌドの可読性を損なうこずなくラムダを芁求するこずをやめたした。 たた、IHttpRequestむンタヌフェむスの実装に぀いおは蚀及したせんでした。これは、libeventバッファヌを䜿甚したルヌチン䜜業にはほずんど関心がないためです。 残りに぀いおは、最終バヌゞョンのコヌドを芋るず、あたり倉曎されおいたせん。 少しの修正ず少しの「チュヌニング」が远加されたした。



ナヌザヌサヌバヌは、すべおの皮類のhttp芁求を凊理する必芁はありたせん。 , 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; }
      
      







おわりに



libevent . : . , http-. github . http server .

:

ab -c 1000 -k -r -t 10 http://localhost:8888/libevent_test_http_srv.zip
Server 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

倱敗したリク゚スト0

曞き蟌み゚ラヌ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








!










All Articles