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
This commit is contained in:
ipknHama 2014-08-06 03:54:38 +09:00
parent 88cc6079cb
commit a0c93f5b84
10 changed files with 303 additions and 80 deletions

View File

@ -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/

5
crow.h
View File

@ -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 <uint64_t Tag>
@ -84,5 +84,6 @@ namespace crow
Router router_;
};
using App = Crow;
};

View File

@ -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();
});

80
example_chat.cpp Normal file
View File

@ -0,0 +1,80 @@
#include "crow.h"
#include "json.h"
#include "mustache.h"
#include <string>
#include <vector>
using namespace std;
vector<string> msgs;
vector<crow::response*> 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/<int>")
([](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();
}

48
example_chat.html Normal file
View File

@ -0,0 +1,48 @@
<html>
<head>
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
</head>
<body>
<input id="msg" type="text">
<button id="send">Send</button>
<div id="logs">
</div>
<script>
$(document).ready(function(){
$("#send").click(function(){
var msg = $("#msg").val();
console.log(msg);
if (msg.length > 0)
$.post("/send", msg);
$("#msg").val("");
});
$("#msg").keyup(function(event){
if(event.keyCode == 13){
$("#send").click();
}
});
var lastLog = 0;
var updateLog;
updateLog = function(data)
{
console.log("recv ");
console.log(data);
var lastLog = data.last*1;
console.log("lastLog: " + lastLog);
var s = "";
for(var x in data.msgs)
{
s += data.msgs[x] + "<BR>";
}
$("#logs").html(s+$("#logs").html());
var failFunction;
failFunction = function(){
$.getJSON("/logs/"+lastLog, updateLog).fail(failFunction);
};
$.getJSON("/logs/"+lastLog, updateLog).fail(failFunction);
}
$.getJSON("/logs", updateLog);
});
</script>
</body>
</html>

View File

@ -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<int, std::string> 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<int, std::string> 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:

View File

@ -5,8 +5,13 @@
namespace crow
{
template <typename T>
class Connection;
struct response
{
template <typename T>
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<void()> complete_request_handler_;
};
}

View File

@ -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<context*> 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<context*> stack;

View File

@ -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 <typename F, int NInt, int NUint, int NDouble, int NString, typename S1, typename S2>
@ -52,75 +53,83 @@ namespace crow
template <typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<int64_t, Args1...>, black_magic::S<Args2...>>
{
response operator()(F cparams)
void operator()(F cparams)
{
using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<int64_t, NInt>>;
return call<F, NInt+1, NUint, NDouble, NString,
black_magic::S<Args1...>, pushed>()(cparams);
call<F, NInt+1, NUint, NDouble, NString,
black_magic::S<Args1...>, pushed>()(cparams);
}
};
template <typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<uint64_t, Args1...>, black_magic::S<Args2...>>
{
response operator()(F cparams)
void operator()(F cparams)
{
using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<uint64_t, NUint>>;
return call<F, NInt, NUint+1, NDouble, NString,
black_magic::S<Args1...>, pushed>()(cparams);
call<F, NInt, NUint+1, NDouble, NString,
black_magic::S<Args1...>, pushed>()(cparams);
}
};
template <typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<double, Args1...>, black_magic::S<Args2...>>
{
response operator()(F cparams)
void operator()(F cparams)
{
using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<double, NDouble>>;
return call<F, NInt, NUint, NDouble+1, NString,
black_magic::S<Args1...>, pushed>()(cparams);
call<F, NInt, NUint, NDouble+1, NString,
black_magic::S<Args1...>, pushed>()(cparams);
}
};
template <typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<std::string, Args1...>, black_magic::S<Args2...>>
{
response operator()(F cparams)
void operator()(F cparams)
{
using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<std::string, NString>>;
return call<F, NInt, NUint, NDouble, NString+1,
black_magic::S<Args1...>, pushed>()(cparams);
call<F, NInt, NUint, NDouble, NString+1,
black_magic::S<Args1...>, pushed>()(cparams);
}
};
template <typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<>, black_magic::S<Args1...>>
{
response operator()(F cparams)
void operator()(F cparams)
{
if (cparams.handler)
return cparams.handler(
{
cparams.res = cparams.handler(
cparams.params.template get<typename Args1::type>(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<typename Args1::type>(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<typename Args1::type>(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()<Func>(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<Args...>,
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<Args...>,
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()

View File

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