From a0c93f5b84cc11b30bc6320ac26127832ef8bf7a Mon Sep 17 00:00:00 2001 From: ipknHama Date: Wed, 6 Aug 2014 03:54:38 +0900 Subject: [PATCH] long polling implementation complete change `res handle(req)' into `void handle(req, res)' connnection::handle is divide into two part: before and after user handler --- Makefile | 8 +++- crow.h | 5 ++- example.cpp | 2 +- example_chat.cpp | 80 ++++++++++++++++++++++++++++++++++++++++ example_chat.html | 48 ++++++++++++++++++++++++ http_connection.h | 77 +++++++++++++++++++++++++-------------- http_response.h | 32 +++++++++++++++- mustache.h | 10 +++++ routing.h | 93 +++++++++++++++++++++++++++-------------------- unittest.cpp | 28 +++++++++++--- 10 files changed, 303 insertions(+), 80 deletions(-) create mode 100644 example_chat.cpp create mode 100644 example_chat.html diff --git a/Makefile b/Makefile index e71ff3759..192772291 100644 --- a/Makefile +++ b/Makefile @@ -8,9 +8,13 @@ FLAGS_DEBUG = -g FLAGS_GCOV = -r endif -binaries=covtest example +binaries=covtest example example_chat + +all: covtest example example_chat + +example_chat: example_chat.cpp settings.h crow.h http_server.h http_connection.h parser.h http_response.h routing.h common.h utility.h json.h datetime.h logging.h mustache.h + ${CXX} -Wall $(FLAGS_DEBUG) -std=c++1y -o example_chat example_chat.cpp http-parser/http_parser.c -pthread -lboost_system $(FLAGS_BOOST_THREAD) -I http-parser/ -all: covtest example example: example.cpp settings.h crow.h http_server.h http_connection.h parser.h http_response.h routing.h common.h utility.h json.h datetime.h logging.h mustache.h ${CXX} -Wall $(FLAGS_DEBUG) -O3 -std=c++1y -o example example.cpp http-parser/http_parser.c -pthread -lboost_system $(FLAGS_BOOST_THREAD) -ltcmalloc_minimal -I http-parser/ diff --git a/crow.h b/crow.h index 025d174c3..55572bfae 100644 --- a/crow.h +++ b/crow.h @@ -29,9 +29,9 @@ namespace crow { } - response handle(const request& req) + void handle(const request& req, response& res) { - return router_.handle(req); + return router_.handle(req, res); } template @@ -84,5 +84,6 @@ namespace crow Router router_; }; + using App = Crow; }; diff --git a/example.cpp b/example.cpp index 57f8e09d0..f21a7d638 100644 --- a/example.cpp +++ b/example.cpp @@ -46,7 +46,7 @@ int main() ([](const crow::request& req, crow::response& res, int a, int b){ std::ostringstream os; os << a+b; - res.send(os.str()); + res.write(os.str()); res.end(); }); diff --git a/example_chat.cpp b/example_chat.cpp new file mode 100644 index 000000000..994269edc --- /dev/null +++ b/example_chat.cpp @@ -0,0 +1,80 @@ +#include "crow.h" +#include "json.h" +#include "mustache.h" +#include +#include + +using namespace std; + +vector msgs; +vector ress; + +void broadcast(const string& msg) +{ + msgs.push_back(msg); + crow::json::wvalue x; + x["msgs"][0] = msgs.back(); + x["last"] = msgs.size(); + string body = crow::json::dump(x); + for(auto* res:ress) + { + CROW_LOG_DEBUG << res->p << " replied: " << body; + res->end(body); + } + ress.clear(); +} + +int main() +{ + crow::App app; + crow::mustache::set_base("."); + + CROW_ROUTE(app, "/") + ([]{ + crow::mustache::context ctx; + return crow::mustache::load("example_chat.html").render(); + }); + + CROW_ROUTE(app, "/logs") + ([]{ + CROW_LOG_INFO << "logs requested"; + crow::json::wvalue x; + for(int i = max(0, (int)msgs.size()-100); i < (int)msgs.size(); i++) + x["msgs"][i] = msgs[i]; + x["last"] = msgs.size(); + CROW_LOG_INFO << "logs completed"; + return x; + }); + + CROW_ROUTE(app, "/logs/") + ([](const crow::request& req, crow::response& res, int after){ + CROW_LOG_INFO << "logs with last " << after; + if (after < (int)msgs.size()) + { + crow::json::wvalue x; + for(int i = after; i < (int)msgs.size(); i ++) + x["msgs"][i-after] = msgs[i]; + x["last"] = msgs.size(); + + res.write(crow::json::dump(x)); + res.end(); + } + else + { + ress.push_back(&res); + CROW_LOG_DEBUG << res.p << " stored"; + } + }); + + CROW_ROUTE(app, "/send") + ([](const crow::request& req) + { + CROW_LOG_INFO << "msg from client: " << req.body; + broadcast(req.body); + return ""; + }); + + app.port(18080) + //.multithreaded() + .run(); +} diff --git a/example_chat.html b/example_chat.html new file mode 100644 index 000000000..0fe35dd2c --- /dev/null +++ b/example_chat.html @@ -0,0 +1,48 @@ + + + + + + + +
+
+ + + diff --git a/http_connection.h b/http_connection.h index 27aa5cb7d..0f23ab72e 100644 --- a/http_connection.h +++ b/http_connection.h @@ -36,13 +36,15 @@ namespace crow CROW_LOG_DEBUG << "Connection open, total " << connectionCount << ", " << this; #endif } -#ifdef CROW_ENABLE_DEBUG + ~Connection() { + res.complete_request_handler_ = nullptr; +#ifdef CROW_ENABLE_DEBUG connectionCount --; CROW_LOG_DEBUG << "Connection closed, total " << connectionCount << ", " << this; - } #endif + } void start() { @@ -66,28 +68,6 @@ namespace crow void handle() { - static std::unordered_map statusCodes = { - {200, "HTTP/1.1 200 OK\r\n"}, - {201, "HTTP/1.1 201 Created\r\n"}, - {202, "HTTP/1.1 202 Accepted\r\n"}, - {204, "HTTP/1.1 204 No Content\r\n"}, - - {300, "HTTP/1.1 300 Multiple Choices\r\n"}, - {301, "HTTP/1.1 301 Moved Permanently\r\n"}, - {302, "HTTP/1.1 302 Moved Temporarily\r\n"}, - {304, "HTTP/1.1 304 Not Modified\r\n"}, - - {400, "HTTP/1.1 400 Bad Request\r\n"}, - {401, "HTTP/1.1 401 Unauthorized\r\n"}, - {403, "HTTP/1.1 403 Forbidden\r\n"}, - {404, "HTTP/1.1 404 Not Found\r\n"}, - - {500, "HTTP/1.1 500 Internal Server Error\r\n"}, - {501, "HTTP/1.1 501 Not Implemented\r\n"}, - {502, "HTTP/1.1 502 Bad Gateway\r\n"}, - {503, "HTTP/1.1 503 Service Unavailable\r\n"}, - }; - bool is_invalid_request = false; request req = parser_.to_request(); @@ -109,14 +89,54 @@ namespace crow } } + CROW_LOG_INFO << "Request: "<< this << " HTTP/" << parser_.http_major << "." << parser_.http_minor << ' ' + << method_name(req.method) << " " << req.url; + + if (!is_invalid_request) { - res = handler_->handle(req); + deadline_.cancel(); + auto self = this->shared_from_this(); + res.complete_request_handler_ = [self]{ self->complete_request(); }; + handler_->handle(req, res); } + else + { + complete_request(); + } + } - CROW_LOG_INFO << "HTTP/" << parser_.http_major << "." << parser_.http_minor << ' ' - << method_name(req.method) << " " << req.url - << " " << res.code << ' ' << close_connection_; + void complete_request() + { + CROW_LOG_INFO << "Response: " << this << ' ' << res.code << ' ' << close_connection_; + + if (!socket_.is_open()) + return; + + auto self = this->shared_from_this(); + res.complete_request_handler_ = nullptr; + + static std::unordered_map statusCodes = { + {200, "HTTP/1.1 200 OK\r\n"}, + {201, "HTTP/1.1 201 Created\r\n"}, + {202, "HTTP/1.1 202 Accepted\r\n"}, + {204, "HTTP/1.1 204 No Content\r\n"}, + + {300, "HTTP/1.1 300 Multiple Choices\r\n"}, + {301, "HTTP/1.1 301 Moved Permanently\r\n"}, + {302, "HTTP/1.1 302 Moved Temporarily\r\n"}, + {304, "HTTP/1.1 304 Not Modified\r\n"}, + + {400, "HTTP/1.1 400 Bad Request\r\n"}, + {401, "HTTP/1.1 401 Unauthorized\r\n"}, + {403, "HTTP/1.1 403 Forbidden\r\n"}, + {404, "HTTP/1.1 404 Not Found\r\n"}, + + {500, "HTTP/1.1 500 Internal Server Error\r\n"}, + {501, "HTTP/1.1 501 Not Implemented\r\n"}, + {502, "HTTP/1.1 502 Bad Gateway\r\n"}, + {503, "HTTP/1.1 503 Service Unavailable\r\n"}, + }; static std::string seperator = ": "; static std::string crlf = "\r\n"; @@ -186,6 +206,7 @@ namespace crow buffers_.emplace_back(res.body.data(), res.body.size()); do_write(); + res.clear(); } private: diff --git a/http_response.h b/http_response.h index eff1d0f10..1a4cbd316 100644 --- a/http_response.h +++ b/http_response.h @@ -5,8 +5,13 @@ namespace crow { + template + class Connection; struct response { + template + friend class crow::Connection; + std::string body; json::wvalue json_value; int code{200}; @@ -24,22 +29,42 @@ namespace crow *this = std::move(r); } + response& operator = (const response& r) = delete; + response& operator = (response&& r) { body = std::move(r.body); json_value = std::move(r.json_value); code = r.code; headers = std::move(r.headers); + completed_ = r.completed_; return *this; } - void send(const std::string& body_part) + void clear() + { + body.clear(); + json_value.clear(); + code = 200; + headers.clear(); + completed_ = false; + } + + void write(const std::string& body_part) { body += body_part; } void end() { + if (!completed_) + { + completed_ = true; + if (complete_request_handler_) + { + complete_request_handler_(); + } + } } void end(const std::string& body_part) @@ -47,5 +72,10 @@ namespace crow body += body_part; end(); } + + void* p; + private: + bool completed_{}; + std::function complete_request_handler_; }; } diff --git a/mustache.h b/mustache.h index 7f878e3fa..7218ae8d7 100644 --- a/mustache.h +++ b/mustache.h @@ -282,6 +282,16 @@ namespace crow out.insert(out.size(), body_, fragment.first, fragment.second-fragment.first); } public: + std::string render() + { + context empty_ctx; + std::vector stack; + stack.emplace_back(&empty_ctx); + + std::string ret; + render_internal(0, fragments_.size()-1, stack, ret, 0); + return ret; + } std::string render(context& ctx) { std::vector stack; diff --git a/routing.h b/routing.h index 36891f126..28700c177 100644 --- a/routing.h +++ b/routing.h @@ -24,7 +24,7 @@ namespace crow virtual void validate() = 0; - virtual response handle(const request&, const routing_params&) = 0; + virtual void handle(const request&, response&, const routing_params&) = 0; protected: @@ -42,6 +42,7 @@ namespace crow H3& handler_with_req_res; const routing_params& params; const request& req; + response& res; }; template @@ -52,75 +53,83 @@ namespace crow template struct call, black_magic::S> { - response operator()(F cparams) + void operator()(F cparams) { using pushed = typename black_magic::S::template push_back>; - return call, pushed>()(cparams); + call, pushed>()(cparams); } }; template struct call, black_magic::S> { - response operator()(F cparams) + void operator()(F cparams) { using pushed = typename black_magic::S::template push_back>; - return call, pushed>()(cparams); + call, pushed>()(cparams); } }; template struct call, black_magic::S> { - response operator()(F cparams) + void operator()(F cparams) { using pushed = typename black_magic::S::template push_back>; - return call, pushed>()(cparams); + call, pushed>()(cparams); } }; template struct call, black_magic::S> { - response operator()(F cparams) + void operator()(F cparams) { using pushed = typename black_magic::S::template push_back>; - return call, pushed>()(cparams); + call, pushed>()(cparams); } }; template struct call, black_magic::S> { - response operator()(F cparams) + void operator()(F cparams) { if (cparams.handler) - return cparams.handler( + { + cparams.res = cparams.handler( cparams.params.template get(Args1::pos)... ); + cparams.res.end(); + return; + } if (cparams.handler_with_req) - return cparams.handler_with_req( + { + cparams.res = cparams.handler_with_req( cparams.req, cparams.params.template get(Args1::pos)... ); + cparams.res.end(); + return; + } if (cparams.handler_with_req_res) { - crow::response res; cparams.handler_with_req_res( cparams.req, - res, + cparams.res, cparams.params.template get(Args1::pos)... ); - return res; + return; } #ifdef CROW_ENABLE_LOGGING std::cerr << "ERROR cannot find handler" << std::endl; #endif - return response(500); + // we already found matched url; this is server error + cparams.res = response(500); } }; public: @@ -223,24 +232,23 @@ namespace crow (*this).template operator()(std::forward(f)); } - response handle(const request& req, const routing_params& params) + void handle(const request& req, response& res, const routing_params& params) override { - return - call< - call_params< - decltype(handler_), - decltype(handler_with_req_), - decltype(handler_with_req_res_)>, - 0, 0, 0, 0, - black_magic::S, - black_magic::S<> - >()( - call_params< - decltype(handler_), - decltype(handler_with_req_), - decltype(handler_with_req_res_)> - {handler_, handler_with_req_, handler_with_req_res_, params, req} - ); + call< + call_params< + decltype(handler_), + decltype(handler_with_req_), + decltype(handler_with_req_res_)>, + 0, 0, 0, 0, + black_magic::S, + black_magic::S<> + >()( + call_params< + decltype(handler_), + decltype(handler_with_req_), + decltype(handler_with_req_res_)> + {handler_, handler_with_req_, handler_with_req_res_, params, req, res} + ); } private: @@ -614,21 +622,26 @@ public: } } - response handle(const request& req) + void handle(const request& req, response& res) { auto found = trie_.find(req); unsigned rule_index = found.first; if (!rule_index) - return response(404); + { + CROW_LOG_DEBUG << "Cannot match rules " << req.url; + res = response(404); + res.end(); + return; + } if (rule_index >= rules_.size()) throw std::runtime_error("Trie internal structure corrupted!"); CROW_LOG_DEBUG << "Matched rule '" << ((TaggedRule<>*)rules_[rule_index].get())->rule_ << "'"; - return rules_[rule_index]->handle(req, found.second); + rules_[rule_index]->handle(req, res, found.second); } void debug_print() diff --git a/unittest.cpp b/unittest.cpp index e4f8105c0..918d02735 100644 --- a/unittest.cpp +++ b/unittest.cpp @@ -72,9 +72,11 @@ TEST(Rule) r.validate(); + response res; + // executing handler ASSERT_EQUAL(0, x); - r.handle(request(), routing_params()); + r.handle(request(), res, routing_params()); ASSERT_EQUAL(1, x); // registering handler with request argument @@ -84,7 +86,7 @@ TEST(Rule) // executing handler ASSERT_EQUAL(1, x); - r.handle(request(), routing_params()); + r.handle(request(), res, routing_params()); ASSERT_EQUAL(2, x); } @@ -143,13 +145,24 @@ TEST(RoutingTest) app.validate(); //app.debug_print(); + { + request req; + response res; + + req.url = "/-1"; + + app.handle(req, res); + + ASSERT_EQUAL(404, res.code); + } { request req; + response res; req.url = "/0/1001999"; - auto res = app.handle(req); + app.handle(req, res); ASSERT_EQUAL(200, res.code); @@ -158,10 +171,11 @@ TEST(RoutingTest) { request req; + response res; req.url = "/1/-100/1999"; - auto res = app.handle(req); + app.handle(req, res); ASSERT_EQUAL(200, res.code); @@ -170,11 +184,12 @@ TEST(RoutingTest) } { request req; + response res; req.url = "/4/5000/3/-2.71828/hellhere"; req.headers["TestHeader"] = "Value"; - auto res = app.handle(req); + app.handle(req, res); ASSERT_EQUAL(200, res.code); @@ -185,11 +200,12 @@ TEST(RoutingTest) } { request req; + response res; req.url = "/5/-5/999/3.141592/hello_there/a/b/c/d"; req.headers["TestHeader"] = "Value"; - auto res = app.handle(req); + app.handle(req, res); ASSERT_EQUAL(200, res.code);