منذ عامين ، قمنا بنشر RESTinio ، إطار عمل OpenSource C ++ الصغير لتضمين خادم HTTP في تطبيقات C ++. لم تصبح RESTinio ذات شعبية كبيرة خلال هذا الوقت ، لكنها لم تضيع . يختار أحدهم الدعم "الأصلي" لنظام التشغيل Windows ، وشخص ما لبعض الميزات الفردية (مثل دعم sendfile) ، وشخص نسبة الميزات ، وسهولة الاستخدام والتخصيص. لكنني أعتقد ، في البداية ، أن العديد من RESTinio ينجذبون إلى هذا "اللاهوت ، العالم" الخاطئ:
#include <restinio/all.hpp> int main() { restinio::run( restinio::on_this_thread() .port(8080) .address("localhost") .request_handler([](auto req) { return req->create_response().set_body("Hello, World!").done(); })); return 0; }
هذا حقًا كل ما هو مطلوب لتشغيل خادم HTTP داخل تطبيق C ++.
وعلى الرغم من أننا نحاول دائمًا أن نقول إن الميزة الرئيسية التي نشارك فيها بشكل عام في RESTinio هي المعالجة غير المتزامنة للطلبات الواردة ، فإننا لا نزال نواجه أحيانًا أسئلة حول ما يجب القيام به إذا كان عليك القيام بعمليات مطولة من داخل request_handler.
وبما أن مثل هذا السؤال ذو صلة ، فيمكنك التحدث عنه مرة أخرى وإعطاء مثالين صغيرين.
إشارة صغيرة إلى الأصول
قررنا أن نجعل خادم HTTP القابل للتضمين الخاص بنا بعد عدة مرات متتالية يواجه مهام متشابهة للغاية: كان من الضروري تنظيم إدخال HTTP لتطبيق C ++ موجود أو كان من الضروري كتابة خدمة microservice كان من الضروري فيها إعادة استخدام C ++ "الثقيل" الموجود بالفعل رمز نيويورك. من الخصائص الشائعة لهذه المهام أن معالجة الطلب للطلب يمكن أن تمتد لعشرات الثواني.
بمعنى تقريبي ، لمدة ميلي ثانية واحدة ، كان خادم HTTP يقوم بترتيب طلب HTTP جديد ، ولكن من أجل إصدار استجابة HTTP ، كان من الضروري اللجوء إلى بعض الخدمات الأخرى أو لإجراء بعض العمليات الحسابية المطولة. إذا قمت بمعالجة طلبات HTTP في الوضع المتزامن ، فسيحتاج خادم HTTP إلى مجموعة من آلاف سلاسل العمل ، والتي بالكاد يمكن اعتبارها فكرة جيدة حتى في الظروف الحديثة.
يكون أكثر ملاءمة عندما يمكن لخادم HTTP العمل على مؤشر ترابط عمل واحد فقط ، حيث يتم تنفيذ I / O ويتم استدعاء معالجات الطلب. يقوم معالج الطلب ببساطة بتفويض المعالجة الفعلية لبعض مؤشرات ترابط العمل الأخرى وإرجاع التحكم إلى خادم HTTP. عندما تكون المعلومات في وقت لاحق ، في مكان ما على مؤشر ترابط عمل آخر ، جاهزة للرد على الطلب ، يتم إنشاء استجابة HTTP ببساطة تلتقط خادم HTTP تلقائيًا وترسل هذا الرد إلى العميل المناسب.
نظرًا لأننا لم نعثر أبدًا على إصدار جاهز بسيط وسهل الاستخدام ، فقد كان نظامًا متقاطعًا وويندوزًا معتمدًا على أنه نظام أساسي "أصلي" ، وسيوفر أداء لائقًا إلى حد ما ، والأهم من ذلك أنه سيتم توحيده خصيصًا للاستخدام غير المتزامن العمل ، ثم في بداية عام 2017 بدأنا تطوير RESTinio.
لقد أردنا أن نجعل خادم HTTP مضمنًا غير متزامن ، وسهل الاستخدام ، مما يحرر المستخدم من بعض المخاوف الروتينية ، في حين أن أكثر أو أقل إنتاجية ، عبر منصة ويسمح بتكوين مرن لمختلف الظروف. يبدو أنه يعمل جيدًا ، ولكن دعنا نترك الأمر للمستخدمين ليحكموا ...
لذلك ، هناك طلب وارد يتطلب الكثير من وقت المعالجة. ما يجب القيام به
خيوط العمل RESTinio / Asio
في بعض الأحيان ، لا يفكر مستخدمو RESTinio في موضوعات العمل وكيف يستخدم RESTinio بالضبط. على سبيل المثال ، قد يعتبر شخص ما أنه عندما يتم تشغيل RESTinio على مؤشر ترابط عمل واحد (باستخدام run(on_this_thread(...))
، كما في المثال أعلاه) ، ثم على مؤشر ترابط العمل هذا يستدعي RESTinio معالجات طلب فقط. بينما I / O RESTinio يخلق موضوع منفصل تحت غطاء محرك السيارة. ويستمر مؤشر الترابط المنفصل هذا في خدمة الاتصالات الجديدة عندما يكون مؤشر ترابط العمل الرئيسي مشغولًا بالطلب.
في الواقع ، يتم استخدام كافة مؤشرات الترابط التي يخصصها المستخدم لـ RESTinio لتنفيذ عمليات الإدخال / الإخراج وللاطلب request_handlers. لذلك ، إذا بدأت تشغيل خادم RESTinio خلال run(on_this_thread(...))
، ثم داخل run()
في سلسلة العمليات الحالية ، سيتم تنفيذ كل من I / O ومعالجات الطلب.
بشكل تقريبي ، تطلق RESTinio حلقة أحداث Asio ، حيث تقوم بمعالجة الاتصالات الجديدة ، وقراءة وتحليل البيانات من الاتصالات الحالية ، وكتابة البيانات الجاهزة للإرسال ، ومعالجة الاتصالات المغلقة ، إلخ. من بين أشياء أخرى ، بعد قراءة الطلب الوارد وتحليله بالكامل من الاتصال التالي ، يتم استدعاء request_handler المحدد بواسطة المستخدم لمعالجة هذا الطلب.
وفقًا لذلك ، إذا قام request_handler بحظر تشغيل الخيط الحالي ، فسيتم أيضًا حظر حلقة حدث Asio-action التي تعمل على نفس الخيط. كل شيء بسيط.
إذا تم تشغيل RESTinio على مجموعة من مؤشرات ترابط العمل (أي عن طريق run(on_thread_pool(...))
، كما في هذا المثال ) ، run(on_thread_pool(...))
نفس الشيء تقريبًا: يتم تشغيل حلقة حدث Asio على كل مؤشر ترابط من التجمع. لذلك ، إذا بدأ بعض request_handler في مضاعفة المصفوفات الكبيرة ، فسيؤدي ذلك إلى حظر مؤشر ترابط العمل في التجمع ولن يتم عرض عمليات الإدخال / الإخراج في هذا الموضوع.
لذلك ، عند استخدام RESTinio ، تتمثل مهمة المطور في إكمال مهندسي الطلب في وقت معقول ، ويفضل ألا يكون ذلك طويلاً.
هل تحتاج إلى تجمع سير عمل لـ RESTinio / Asio؟
لذلك ، عندما يقوم request_handler المحدد من قبل المستخدم بحظر مؤشر ترابط العمل الذي يتم استدعاؤه لفترة طويلة ، يفقد مؤشر الترابط هذا القدرة على معالجة عمليات الإدخال / الإخراج. ولكن ماذا لو أن request_handler يحتاج إلى الكثير من الوقت لتكوين استجابة؟ لنفترض أنه يقوم بنوع من عمليات الحوسبة الثقيلة ، التي لا يمكن اختصارها ، من حيث المبدأ ، إلى بضعة ميلي ثانية؟
قد يعتقد أحد المستخدمين أنه نظرًا لأن RESTinio يمكنه العمل على مجموعة من مؤشرات ترابط العمل ، فما عليك سوى تحديد حجم التجمع الأكبر وهذا.
لسوء الحظ ، لن يعمل هذا إلا في الحالات البسيطة عندما يكون لديك عدد قليل من الاتصالات المتوازية. وكثافة الاستعلام منخفضة. إذا كان عدد الاستعلام المتوازي يصل إلى الآلاف (على الأقل بضع مئات فقط) ، فمن السهل أن تحصل على موقف عندما تكون جميع مؤشرات الترابط العاملة في التجمع مشغولة بمعالجة الطلبات المقبولة بالفعل. ولن يكون هناك المزيد من مؤشرات الترابط المتبقية لتنفيذ عمليات الإدخال / الإخراج. نتيجة لذلك ، سوف يفقد الخادم استجابته. بما في ذلك RESTinio سيفقد القدرة على معالجة المهلات التي RESTinio تحسب تلقائيا عندما يتلقى اتصالات جديدة وعند معالجة الطلبات.
لذلك ، إذا كنت بحاجة إلى تنفيذ عمليات حظر طويلة لخدمة الطلبات الواردة ، فمن الأفضل تخصيص مؤشر ترابط عمل واحد فقط لـ RESTinio ، ولكن تعيين مجموعة كبيرة من تدفقات العمل لتنفيذ هذه العمليات نفسها. سيقوم معالج الطلب بوضع الطلب التالي في قائمة انتظار ، حيث سيتم استرداد الطلب وإرساله للمعالجة.
لقد بحثنا في مثال على هذا المخطط بالتفصيل عندما تحدثنا عن مشروع عرض Shrimp الخاص بنا في هذه المقالة: " Shrimp: Scale and مشاركة صور HTTP في Modern C ++ باستخدام ImageMagic ++ و SObjectizer و RESTinio ."
أمثلة لتفويض معالجة الطلب إلى مؤشرات ترابط العمل الفردية
أعلاه ، حاولت أن أشرح لماذا ليس من الضروري إجراء معالجة طويلة مباشرة داخل request_handler. من أين تأتي النتيجة الواضحة: يجب تفويض معالجة الطلبات المطولة إلى سلسلة عمليات أخرى. دعونا ننظر في كيف يمكن أن يبدو هذا.
في المثالين أدناه ، نحتاج إلى مؤشر ترابط عمل واحد لتشغيل RESTinio ومؤشر ترابط عمل آخر لمحاكاة معالجة الطلبات الطويلة. ونحتاج أيضًا إلى نوع من قائمة انتظار الرسائل لنقل الطلبات من مؤشر ترابط RESTinio إلى مؤشر ترابط عمل منفصل.
لم يكن من السهل بالنسبة لي أن أقوم بتنفيذ قائمة انتظار رسائل جديدة آمنة لسلسلة الرسائل على ركبتي لهذين المثالين ، لذلك استخدمت برنامج SObjectizer الأصلي الخاص بي وسلاسله ، وهي قنوات CSP. يمكنك قراءة المزيد عن mchain هنا: " تبادل المعلومات بين خيوط العمل دون ألم؟ CSP- القنوات لمساعدتنا ."
حفظ كائن request_handle
التقنية الأساسية التي تم بناء تفويض معالجة الطلب عليها هي نقل كائن request_handle_t
مكان ما.
عندما تقوم RESTinio باستدعاء request_handler المحدد بواسطة المستخدم لمعالجة طلب وارد ، يتم تمرير كائن من النوع request_handle_t
إلى request_handle_t
. هذا النوع ليس أكثر من مؤشر ذكي لمعلمات الطلب المستلم. لذلك ، إذا كان من المريح أن يعتقد شخص ما أن request_handle_t
هو shared_ptr
، فيمكنك التفكير في ذلك بأمان. هذا shared_ptr
هو.
ونظرًا لأن request_handle_t
is shared_ptr
، يمكننا تمرير هذا المؤشر الذكي بأمان في مكان ما. ماذا سنفعل في الأمثلة أدناه.
لذلك ، نحن بحاجة إلى مؤشر ترابط عمل منفصل وقناة للتواصل معها. لنقم بإنشاء كل شيء:
int main() {
يوجد نص مؤشر ترابط العمل نفسه داخل دالة processing_thread_func()
، والتي سنناقشها بعد قليل.
الآن لدينا بالفعل خيط عمل منفصل وقناة للتواصل معها. يمكنك بدء خادم RESTinio:
منطق هذا الخادم بسيط جدا. إذا وصل طلب GET لـ '/' ، فسنفوض معالجة الطلب لمؤشر ترابط واحد. للقيام بذلك ، نقوم بتنفيذ عمليتين مهمتين:
- إرسال كائن
request_handle_t
إلى قناة CSP. بينما يتم تخزين هذا الكائن داخل قناة CSP أو في أي مكان آخر ، يعرف RESTinio أن الطلب لا يزال على قيد الحياة ؛ - نرجع القيمة
restinio::request_accepted()
من معالج الطلب. هذا يجعل RESTinio يفهم أنه تم قبول الطلب للمعالجة وأنه لا يمكن إغلاق الاتصال بالعميل.
حقيقة أن request_handler لم ينشئ على الفور استجابة RESTinio لا تهتم. بمجرد restinio::request_accepted()
، يتحمل المستخدم مسؤولية معالجة الطلب وسيتم إنشاء الرد على الطلب في يوم من الأيام.
إذا قام معالج الطلب بإرجاع restinio::request_rejected()
، فعندئذٍ يدرك RESTinio أن الطلب لن تتم معالجته وسيُرجع الخطأ 501 إلى العميل.
لذلك ، نصلح النتيجة الأولية: يمكن تمرير مثيل request_handle_t
مكان ما ، لأنه في الحقيقة std::shared_ptr
. على الرغم من أن هذا المثيل قيد الحياة ، فإن RESTinio تعتبر أن الطلب قيد المعالجة. إذا قام معالج الطلب بإرجاع restinio::request_accepted()
، فلن تقلق RESTinio من عدم إنشاء استجابة للطلب الآن.
الآن يمكننا أن ننظر إلى تنفيذ هذا الموضوع منفصلة جدا:
void processing_thread_func(so_5::mchain_t req_ch) {
المنطق هنا بسيط للغاية: نحن نتلقى الطلب الأولي في شكل رسالة handle_request
توجيهه إلى أنفسنا في شكل رسالة timeout_elapsed
تأخيرها لبعض الوقت العشوائي. نقوم بمعالجة الطلب الفعلية فقط عند استلام timeout_elapsed
.
محدث. عندما يتم استدعاء الأسلوب done()
على مؤشر ترابط عمل منفصل ، يتم إخطار RESTinio أن استجابة جاهزة قد ظهرت تحتاج إلى كتابتها إلى اتصال TCP. يبدأ RESTinio عملية الكتابة ، لكن لن يتم تنفيذ عملية الإدخال / الإخراج نفسها حيث done()
، ولكن حيث تقوم RESTinio بإجراء I / O وتدعو request_handlers. أي في هذا المثال ، يتم استدعاء done()
على مؤشر ترابط عمل منفصل ، وسيتم تنفيذ عملية الكتابة على مؤشر الترابط الرئيسي ، حيث restinio::run()
.
الرسائل نفسها كالتالي:
struct handle_request { restinio::request_handle_t m_req; }; struct timeout_elapsed { restinio::request_handle_t m_req; std::chrono::milliseconds m_pause; };
أي يأخذ مؤشر ترابط عمل منفصل request_handle_t
ويحفظه حتى تنشأ الفرصة لتشكيل استجابة كاملة. وعندما تنشأ هذه الفرصة ، يتم استدعاء create_response()
على كائن الطلب المحفوظ ويتم إرجاع الاستجابة إلى RESTinio. ثم RESTinio بالفعل في سياق عملها يكتب الاستجابة في اتصال مع العميل المطابق.
هنا ، يتم تخزين مثيل request_handle_t
في رسالة متأخرة timeout_elapsed
، نظرًا لعدم وجود معالجة حقيقية في هذا المثال البدائي. في تطبيق حقيقي ، يمكن تخزين request_handle_t
في قائمة انتظار معينة أو داخل كائن تم إنشاؤه لمعالجة الطلب.
يمكن العثور على الرمز الكامل لهذا المثال من بين أمثلة RESTinio العادية .
بعض الملاحظات رمز صغير
يعين هذا الإنشاء خصائص RESTinio التي يجب أن يشتمل عليها خادم RESTinio:
في هذا المثال ، أحتاج إلى RESTinio لتسجيل إجراءات معالجة الطلب الخاصة به. لذلك ، قمت بتعيين logger_t
ليكون مختلفًا عن null_logger_t
الافتراضي. لكن منذ ذلك الحين ستعمل RESTinio ، في الواقع ، على عدة سلاسل عمليات (RESTinio تعالج الطلبات الواردة في سلسلة الرسائل الرئيسية ، لكن الردود تأتي من سلسلة عمليات منفصلة) ، فأنت بحاجة إلى أداة تسجيل آمنة shared_ostream_logger_t
، وهي shared_ostream_logger_t
.
من الداخل processing_thread_func()
، يتم استخدام الدالة SObjectizer select()
، والتي تشبه إلى حد ما إنشاء Go-shn المحدد: يمكنك قراءة الرسائل ومعالجتها من عدة قنوات في وقت واحد. تعمل وظيفة select()
حتى يتم إغلاق جميع القنوات التي تم تمريرها إليها. أو حتى يتم إخبارها بالقوة أن الوقت قد انتهى.
في الوقت نفسه ، إذا كانت قناة الاتصال بخادم RESTinio مغلقة ، فلا فائدة من مواصلة العمل. لذلك ، في select()
، يتم تحديد الاستجابة لإغلاق أي من القنوات: بمجرد إغلاق قناة ، يتم رفع علامة التوقف. وسيؤدي ذلك إلى إكمال select()
والخروج من processing_thread_func()
.
حفظ كائن response_builder
في المثال السابق ، نظرنا في حالة بسيطة عندما يكون من الممكن حفظ request_handle_t
حتى نتمكن من تقديم الاستجابة الكاملة للطلب على الفور.
ولكن قد يكون هناك سيناريوهات أكثر تعقيدًا ، على سبيل المثال ، عندما تحتاج إلى تقديم إجابة في أجزاء. وهذا هو ، تلقينا طلبًا ، يمكننا على الفور تشكيل الجزء الأول فقط من الاستجابة. نحن تشكيله. ثم ، بعد بعض الوقت ، لدينا الفرصة لتشكيل الجزء الثاني من الإجابة. بعد ذلك ، بعد مرور بعض الوقت ، يمكننا تشكيل الجزء التالي ، إلخ.
علاوة على ذلك ، قد يكون من المستحسن بالنسبة لنا أن تختفي كل هذه الأجزاء أثناء تشكيلها. أي أولاً ، الجزء الأول من الإجابة حتى يتمكن العميل من طرحها ، ثم الجزء الثاني ، ثم الجزء الثالث ، إلخ.
يسمح لك RESTinio بالقيام بذلك نظرًا لأنواع مختلفة من أجهزة الاستجابة . على وجه الخصوص ، أنواع مثل user_controlled_output و chunked_output .
في هذه الحالة ، لا يكفي حفظ request_handle_t
، لأن request_handle_t
لن يكون مفيدًا إلا حتى أول مكالمة إلى create_reponse()
. بعد ذلك نحتاج للعمل مع response_builder. حسنا ...
حسنا ، هذا جيد. Response_builder هو نوع متحرك ، يشبه إلى حد ما unique_ptr. لذلك يمكننا أيضًا الاحتفاظ بها طالما احتجنا إليها. ولإظهار كيف يبدو ، قمنا بإعادة المثال أعلاه قليلاً. دع الدالة processing_thread_func()
تشكل الاستجابة في الأجزاء.
هذا ليس صعبا على الاطلاق.
أولاً ، نحتاج إلى تحديد الأنواع التي ستحتاج إلىها processing_thread_func()
الجديدة:
struct handle_request { restinio::request_handle_t m_req; };
تبقى الرسالة handle_request
بدون تغيير. ولكن في الرسالة التي timeout_elapsed
نقوم الآن بتخزين not request_handle_t
، ولكن استجابة timeout_elapsed
النوع الذي نحتاجه. بالإضافة إلى عداد الأجزاء المتبقية. بمجرد إعادة تعيين هذا العداد ، تنتهي طلب الخدمة.
الآن يمكننا إلقاء نظرة على إصدار جديد من الدالة processing_thread_func()
:
void processing_thread_func(so_5::mchain_t req_ch) { std::random_device rd; std::mt19937 generator{rd()}; std::uniform_int_distribution<> pause_generator{350, 3500}; auto delayed_ch = so_5::create_mchain(req_ch->environment()); bool stop = false; select( so_5::from_all() .on_close([&stop](const auto &) { stop = true; }) .stop_on([&stop]{ return stop; }), case_(req_ch, [&](handle_request cmd) {
أي , . . .
Upd. flush()
, done()
: RESTinio , I/O- , flush()
, , RESTinio - request_handler-. أي flush()
, , , restinio::run()
.
, RESTinio :
[2019-05-13 15:02:35.106] TRACE: starting server on 127.0.0.1:8080 [2019-05-13 15:02:35.106] INFO: init accept #0 [2019-05-13 15:02:35.106] INFO: server started on 127.0.0.1:8080 [2019-05-13 15:02:39.050] TRACE: accept connection from 127.0.0.1:49280 on socket #0 [2019-05-13 15:02:39.050] TRACE: [connection:1] start connection with 127.0.0.1:49280 [2019-05-13 15:02:39.050] TRACE: [connection:1] start waiting for request [2019-05-13 15:02:39.050] TRACE: [connection:1] continue reading request [2019-05-13 15:02:39.050] TRACE: [connection:1] received 78 bytes [2019-05-13 15:02:39.050] TRACE: [connection:1] request received (#0): GET / [2019-05-13 15:02:39.050] TRACE: [connection:1] append response (#0), flags: { not_final_parts, connection_keepalive }, write group size: 1 [2019-05-13 15:02:39.050] TRACE: [connection:1] start next write group for response (#0), size: 1 [2019-05-13 15:02:39.050] TRACE: [connection:1] start response (#0): HTTP/1.1 200 OK [2019-05-13 15:02:39.050] TRACE: [connection:1] sending resp data, buf count: 1, total size: 167 [2019-05-13 15:02:39.050] TRACE: [connection:1] outgoing data was sent: 167 bytes [2019-05-13 15:02:39.050] TRACE: [connection:1] finishing current write group [2019-05-13 15:02:39.050] TRACE: [connection:1] should keep alive [2019-05-13 15:02:40.190] TRACE: [connection:1] append response (#0), flags: { not_final_parts, connection_keepalive }, write group size: 3 [2019-05-13 15:02:40.190] TRACE: [connection:1] start next write group for response (#0), size: 3 [2019-05-13 15:02:40.190] TRACE: [connection:1] sending resp data, buf count: 3, total size: 42 [2019-05-13 15:02:40.190] TRACE: [connection:1] outgoing data was sent: 42 bytes [2019-05-13 15:02:40.190] TRACE: [connection:1] finishing current write group [2019-05-13 15:02:40.190] TRACE: [connection:1] should keep alive [2019-05-13 15:02:43.542] TRACE: [connection:1] append response (#0), flags: { not_final_parts, connection_keepalive }, write group size: 3 [2019-05-13 15:02:43.542] TRACE: [connection:1] start next write group for response (#0), size: 3 [2019-05-13 15:02:43.542] TRACE: [connection:1] sending resp data, buf count: 3, total size: 42 [2019-05-13 15:02:43.542] TRACE: [connection:1] outgoing data was sent: 42 bytes [2019-05-13 15:02:43.542] TRACE: [connection:1] finishing current write group [2019-05-13 15:02:43.542] TRACE: [connection:1] should keep alive [2019-05-13 15:02:46.297] TRACE: [connection:1] append response (#0), flags: { not_final_parts, connection_keepalive }, write group size: 3 [2019-05-13 15:02:46.297] TRACE: [connection:1] start next write group for response (#0), size: 3 [2019-05-13 15:02:46.297] TRACE: [connection:1] sending resp data, buf count: 3, total size: 42 [2019-05-13 15:02:46.297] TRACE: [connection:1] append response (#0), flags: { final_parts, connection_keepalive }, write group size: 1 [2019-05-13 15:02:46.297] TRACE: [connection:1] outgoing data was sent: 42 bytes [2019-05-13 15:02:46.298] TRACE: [connection:1] finishing current write group [2019-05-13 15:02:46.298] TRACE: [connection:1] should keep alive [2019-05-13 15:02:46.298] TRACE: [connection:1] start next write group for response (#0), size: 1 [2019-05-13 15:02:46.298] TRACE: [connection:1] sending resp data, buf count: 1, total size: 5 [2019-05-13 15:02:46.298] TRACE: [connection:1] outgoing data was sent: 5 bytes [2019-05-13 15:02:46.298] TRACE: [connection:1] finishing current write group [2019-05-13 15:02:46.298] TRACE: [connection:1] should keep alive [2019-05-13 15:02:46.298] TRACE: [connection:1] start waiting for request [2019-05-13 15:02:46.298] TRACE: [connection:1] continue reading request [2019-05-13 15:02:46.298] TRACE: [connection:1] EOF and no request, close connection [2019-05-13 15:02:46.298] TRACE: [connection:1] close [2019-05-13 15:02:46.298] TRACE: [connection:1] close: close socket [2019-05-13 15:02:46.298] TRACE: [connection:1] close: timer canceled [2019-05-13 15:02:46.298] TRACE: [connection:1] close: reset responses data [2019-05-13 15:02:46.298] TRACE: [connection:1] destructor called
, RESTinio 167 . , , RESTinio .
, RESTinio - response_builder , .
. , , . response_builder . , responce_builder , ..
.
, ?
, request_handler- - . , , ?
RESTinio , - request_handler-. - , , RESTinio . , . , :
[2019-05-13 15:32:23.618] TRACE: starting server on 127.0.0.1:8080 [2019-05-13 15:32:23.618] INFO: init accept #0 [2019-05-13 15:32:23.618] INFO: server started on 127.0.0.1:8080 [2019-05-13 15:32:26.768] TRACE: accept connection from 127.0.0.1:49502 on socket #0 [2019-05-13 15:32:26.768] TRACE: [connection:1] start connection with 127.0.0.1:49502 [2019-05-13 15:32:26.768] TRACE: [connection:1] start waiting for request [2019-05-13 15:32:26.768] TRACE: [connection:1] continue reading request [2019-05-13 15:32:26.768] TRACE: [connection:1] received 78 bytes [2019-05-13 15:32:26.768] TRACE: [connection:1] request received (#0): GET / [2019-05-13 15:32:30.768] TRACE: [connection:1] handle request timed out [2019-05-13 15:32:30.768] TRACE: [connection:1] close [2019-05-13 15:32:30.768] TRACE: [connection:1] close: close socket [2019-05-13 15:32:30.768] TRACE: [connection:1] close: timer canceled [2019-05-13 15:32:30.768] TRACE: [connection:1] close: reset responses data [2019-05-13 15:32:31.768] WARN: [connection:1] try to write response, while socket is closed [2019-05-13 15:32:31.768] TRACE: [connection:1] destructor called
- . , , RESTinio , .. .
- handle_request_timeout
, RESTinio- ( ).
استنتاج
, , RESTinio — , . , RESTinio, , RESTinio, .
RESTinio , , , : ? - ? - ? - - ?
PS. RESTinio , SObjectizer, . , - RESTinio , : " C++ HTTP- ", " HTTP- C++: RESTinio, libcurl. 1 ", " Shrimp: HTTP C++ ImageMagic++, SObjectizer RESTinio "