diff --git a/include/ylt/thirdparty/cinatra/cookie.hpp b/include/ylt/thirdparty/cinatra/cookie.hpp new file mode 100644 index 000000000..a74292ddb --- /dev/null +++ b/include/ylt/thirdparty/cinatra/cookie.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include + +#include "time_util.hpp" + +namespace cinatra { + +class cookie { + public: + cookie() = default; + cookie(const std::string &name, const std::string &value) + : name_(name), value_(value) {} + + void set_version(int version) { version_ = version; } + + void set_name(const std::string &name) { name_ = name; } + + std::string get_name() const { return name_; } + + void set_value(const std::string &value) { value_ = value; } + + std::string get_value() const { return value_; } + + void set_comment(const std::string &comment) { comment_ = comment; } + + void set_domain(const std::string &domain) { domain_ = domain; } + + void set_path(const std::string &path) { path_ = path; } + + void set_priority(const std::string &priority) { priority_ = priority; } + + void set_secure(bool secure) { secure_ = secure; } + + void set_max_age(std::time_t seconds) { max_age_ = seconds; } + + void set_http_only(bool http_only) { http_only_ = http_only; } + + std::string to_string() const { + std::string result; + result.reserve(256); + result.append(name_); + result.append("="); + if (version_ == 0) { + // Netscape cookie + result.append(value_); + if (!domain_.empty()) { + result.append("; domain="); + result.append(domain_); + } + if (!path_.empty()) { + result.append("; path="); + result.append(path_); + } + if (!priority_.empty()) { + result.append("; Priority="); + result.append(priority_); + } + if (max_age_ != -1) { + result.append("; expires="); + result.append(get_gmt_time_str(std::chrono::system_clock::from_time_t( + max_age_ + std::time(nullptr)))); + } + if (secure_) { + result.append("; secure"); + } + if (http_only_) { + result.append("; HttpOnly"); + } + } + else { + // RFC 2109 cookie + result.append("\""); + result.append(value_); + result.append("\""); + if (!comment_.empty()) { + result.append("; Comment=\""); + result.append(comment_); + result.append("\""); + } + if (!domain_.empty()) { + result.append("; Domain=\""); + result.append(domain_); + result.append("\""); + } + if (!path_.empty()) { + result.append("; Path=\""); + result.append(path_); + result.append("\""); + } + if (!priority_.empty()) { + result.append("; Priority=\""); + result.append(priority_); + result.append("\""); + } + + if (max_age_ != -1) { + result.append("; Max-Age=\""); + result.append(std::to_string(max_age_)); + result.append("\""); + } + if (secure_) { + result.append("; secure"); + } + if (http_only_) { + result.append("; HttpOnly"); + } + result.append("; Version=\"1\""); + } + return result; + } + + private: + std::string name_ = ""; + std::string value_ = ""; + std::string comment_ = ""; + std::string domain_ = ""; + std::string priority_ = ""; + std::string path_ = ""; + std::time_t max_age_ = -1; + int version_ = 0; + bool secure_ = false; + bool http_only_ = false; +}; + +} // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/coro_http_connection.hpp b/include/ylt/thirdparty/cinatra/coro_http_connection.hpp index 8c47cdbbe..2b1160a02 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_connection.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_connection.hpp @@ -11,11 +11,13 @@ #include "async_simple/coro/Lazy.h" #include "cinatra/cinatra_log_wrapper.hpp" #include "cinatra/response_cv.hpp" +#include "cookie.hpp" #include "coro_http_request.hpp" #include "coro_http_router.hpp" #include "define.h" #include "http_parser.hpp" #include "multipart.hpp" +#include "session_manager.hpp" #include "sha1.hpp" #include "string_resize.hpp" #include "websocket.hpp" @@ -218,7 +220,7 @@ class coro_http_connection if (is_coro_exist) { if (coro_handler) { - co_await (coro_handler)(request_, response_); + co_await coro_handler(request_, response_); } else { response_.set_status(status_type::not_found); @@ -236,7 +238,7 @@ class coro_http_connection std::get<0>(pair))) { auto coro_handler = std::get<1>(pair); if (coro_handler) { - co_await (coro_handler)(request_, response_); + co_await coro_handler(request_, response_); is_matched_regex_router = true; } } @@ -268,12 +270,73 @@ class coro_http_connection } if (!response_.get_delay()) { - co_await reply(); + if (head_buf_.size()) { + // handle pipeling, only support GET and HEAD method now. + if (parser_.method()[0] != 'G' && parser_.method()[0] != 'H') { + response_.set_status_and_content(status_type::method_not_allowed, + "method not allowed"); + co_await reply(); + } + else { + resp_str_.reserve(512); + response_.build_resp_str(resp_str_); + + while (true) { + size_t left_size = head_buf_.size(); + auto data_ptr = asio::buffer_cast(head_buf_.data()); + std::string_view left_content{data_ptr, left_size}; + size_t pos = left_content.find(TWO_CRCF); + if (pos == std::string_view::npos) { + break; + } + http_parser parser; + int head_len = parser.parse_request(data_ptr, size, 0); + if (head_len <= 0) { + CINATRA_LOG_ERROR << "parse http header error"; + close(); + break; + } + + head_buf_.consume(pos + TWO_CRCF.length()); + + std::string_view key = { + parser_.method().data(), + parser_.method().length() + 1 + parser_.url().length()}; + + coro_http_request req(parser, this); + coro_http_response resp(this); + resp.need_date_head(response_.need_date()); + if (auto handler = router_.get_handler(key); handler) { + router_.route(handler, req, resp, key); + } + else { + if (auto coro_handler = router_.get_coro_handler(key); + coro_handler) { + co_await router_.route_coro(coro_handler, req, resp, key); + } + } + + resp.build_resp_str(resp_str_); + } + + auto [write_ec, _] = co_await async_write(asio::buffer(resp_str_)); + if (write_ec) { + CINATRA_LOG_ERROR << "async_write error: " << write_ec.message(); + close(); + co_return; + } + } + } + else { + handle_session_for_response(); + co_await reply(); + } } response_.clear(); buffers_.clear(); body_.clear(); + resp_str_.clear(); if (need_shrink_every_time_) { body_.shrink_to_fit(); } @@ -684,6 +747,17 @@ class coro_http_connection void set_check_timeout(bool r) { checkout_timeout_ = r; } + void handle_session_for_response() { + if (request_.has_session()) { + auto session = + session_manager::get().get_session(request_.get_cached_session_id()); + if (session != nullptr && session->get_need_set_to_client()) { + response_.add_cookie(session->get_session_cookie()); + session->set_need_set_to_client(false); + } + } + } + private: bool check_keep_alive() { bool keep_alive = true; @@ -739,6 +813,7 @@ class coro_http_connection bool checkout_timeout_ = false; std::atomic last_rwtime_; uint64_t max_part_size_ = 8 * 1024 * 1024; + std::string resp_str_; websocket ws_; #ifdef CINATRA_ENABLE_SSL diff --git a/include/ylt/thirdparty/cinatra/coro_http_request.hpp b/include/ylt/thirdparty/cinatra/coro_http_request.hpp index 85383b242..b4b7bf4c4 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_request.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_request.hpp @@ -8,6 +8,8 @@ #include "async_simple/coro/Lazy.h" #include "define.h" #include "http_parser.hpp" +#include "session.hpp" +#include "session_manager.hpp" #include "utils.hpp" #include "ws_define.h" @@ -224,6 +226,40 @@ class coro_http_request { } } + std::unordered_map get_cookies( + std::string_view cookie_str) const { + auto cookies = get_cookies_map(cookie_str); + return cookies; + } + + std::shared_ptr get_session(bool create = true) { + auto &session_manager = session_manager::get(); + + auto cookies = get_cookies(get_header_value("Cookie")); + std::string session_id; + auto iter = cookies.find(CSESSIONID); + if (iter == cookies.end() && !create) { + return nullptr; + } + else if (iter == cookies.end()) { + session_id = session_manager.generate_session_id(); + } + else { + session_id = iter->second; + } + + cached_session_id_ = session_id; + return session_manager.get_session(session_id); + } + + std::string get_cached_session_id() { + std::string temp_session_id = ""; + cached_session_id_.swap(temp_session_id); + return temp_session_id; + } + + bool has_session() { return !cached_session_id_.empty(); } + std::unordered_map params_; std::smatch matches_; @@ -233,5 +269,6 @@ class coro_http_request { coro_http_connection *conn_; bool is_websocket_; std::unordered_map aspect_data_; + std::string cached_session_id_; }; } // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/coro_http_response.hpp b/include/ylt/thirdparty/cinatra/coro_http_response.hpp index a5e7dd30e..4b8c3b88e 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_response.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_response.hpp @@ -10,6 +10,7 @@ #include "async_simple/coro/Lazy.h" #include "async_simple/coro/SyncAwait.h" +#include "cookie.hpp" #include "define.h" #include "response_cv.hpp" #include "time_util.hpp" @@ -38,9 +39,7 @@ class coro_http_response { : status_(status_type::not_implemented), fmt_type_(format_type::normal), delay_(false), - conn_(conn) { - head_.reserve(128); - } + conn_(conn) {} void set_status(cinatra::status_type status) { status_ = status; } void set_content(std::string content) { @@ -55,6 +54,13 @@ class coro_http_response { void set_delay(bool r) { delay_ = r; } bool get_delay() const { return delay_; } void set_format_type(format_type type) { fmt_type_ = type; } + template + void set_content_type() { + content_type_ = get_content_type(); + } + + status_type status() { return status_; } + std::string_view content() { return content_; } void add_header(auto k, auto v) { resp_headers_.emplace_back(resp_header{std::move(k), std::move(v)}); @@ -62,15 +68,16 @@ class coro_http_response { void set_keepalive(bool r) { keepalive_ = r; } + void need_date_head(bool r) { need_date_ = r; } + bool need_date() { return need_date_; } + void set_boundary(std::string_view boundary) { boundary_ = boundary; } std::string_view get_boundary() { return boundary_; } void to_buffers(std::vector &buffers) { - build_resp_head(); - buffers.push_back(asio::buffer(to_http_status_string(status_))); - buffers.push_back(asio::buffer(head_)); + build_resp_head(buffers); if (!content_.empty()) { if (fmt_type_ == format_type::chunked) { to_chunked_buffers(buffers, content_, true); @@ -81,7 +88,8 @@ class coro_http_response { } } - void build_resp_head() { + void build_resp_str(std::string &resp_str) { + resp_str.append(to_http_status_string(status_)); bool has_len = false; bool has_host = false; for (auto &[k, v] : resp_headers_) { @@ -94,7 +102,7 @@ class coro_http_response { } if (!has_host) { - resp_headers_sv_.emplace_back(resp_header_sv{"Host", "cinatra"}); + resp_str.append(CINATRA_HOST_SV); } if (content_.empty() && !has_set_content_ && @@ -103,78 +111,157 @@ class coro_http_response { } if (fmt_type_ == format_type::chunked) { - resp_headers_sv_.emplace_back( - resp_header_sv{"Transfer-Encoding", "chunked"}); + resp_str.append(TRANSFER_ENCODING_SV); } else { if (!content_.empty()) { auto [ptr, ec] = std::to_chars(buf_, buf_ + 32, content_.size()); - resp_headers_sv_.emplace_back( - resp_header_sv{"Content-Length", - std::string_view(buf_, std::distance(buf_, ptr))}); + resp_str.append(CONTENT_LENGTH_SV); + resp_str.append(std::string_view(buf_, std::distance(buf_, ptr))); + resp_str.append(CRCF); } else { if (!has_len && boundary_.empty()) - resp_headers_sv_.emplace_back(resp_header_sv{"Content-Length", "0"}); + resp_str.append(ZERO_LENGTH_SV); } } - resp_headers_sv_.emplace_back(resp_header_sv{"Date", get_gmt_time_str()}); + if (need_date_) { + resp_str.append(DATE_SV); + resp_str.append(get_gmt_time_str()); + resp_str.append(CRCF); + } if (keepalive_.has_value()) { bool keepalive = keepalive_.value(); - resp_headers_sv_.emplace_back( - resp_header_sv{"Connection", keepalive ? "keep-alive" : "close"}); + keepalive ? resp_str.append(CONN_KEEP_SV) + : resp_str.append(CONN_CLOSE_SV); + } + + if (!content_type_.empty()) { + resp_str.append(content_type_); } - append_head(resp_headers_); - append_head(resp_headers_sv_); - head_.append(CRCF); + for (auto &[k, v] : resp_headers_) { + resp_str.append(k); + resp_str.append(COLON_SV); + resp_str.append(v); + resp_str.append(CRCF); + } + + resp_str.append(CRCF); + resp_str.append(content_); + } + + void build_resp_head(std::vector &buffers) { + bool has_len = false; + bool has_host = false; + for (auto &[k, v] : resp_headers_) { + if (k == "Host") { + has_host = true; + } + if (k == "Content-Length") { + has_len = true; + } + } + + if (!has_host) { + buffers.emplace_back(asio::buffer(CINATRA_HOST_SV)); + } + + if (content_.empty() && !has_set_content_ && + fmt_type_ != format_type::chunked) { + content_.append(default_status_content(status_)); + } + + if (fmt_type_ == format_type::chunked) { + buffers.emplace_back(asio::buffer(TRANSFER_ENCODING_SV)); + } + else { + if (!cookies_.empty()) { + for (auto &[_, cookie] : cookies_) { + resp_headers_.emplace_back( + resp_header{"Set-Cookie", cookie.to_string()}); + } + } + + if (!content_.empty()) { + auto [ptr, ec] = std::to_chars(buf_, buf_ + 32, content_.size()); + buffers.emplace_back(asio::buffer(CONTENT_LENGTH_SV)); + buffers.emplace_back( + asio::buffer(std::string_view(buf_, std::distance(buf_, ptr)))); + buffers.emplace_back(asio::buffer(CRCF)); + } + else { + if (!has_len && boundary_.empty()) + buffers.emplace_back(asio::buffer(ZERO_LENGTH_SV)); + } + } + + if (need_date_) { + buffers.emplace_back(asio::buffer(DATE_SV)); + buffers.emplace_back(asio::buffer(get_gmt_time_str())); + buffers.emplace_back(asio::buffer(CRCF)); + } + + if (keepalive_.has_value()) { + bool keepalive = keepalive_.value(); + keepalive ? buffers.emplace_back(asio::buffer(CONN_KEEP_SV)) + : buffers.emplace_back(asio::buffer(CONN_CLOSE_SV)); + } + + if (!content_type_.empty()) { + buffers.emplace_back(asio::buffer(content_type_)); + } + + for (auto &[k, v] : resp_headers_) { + buffers.emplace_back(asio::buffer(k)); + buffers.emplace_back(asio::buffer(COLON_SV)); + buffers.emplace_back(asio::buffer(v)); + buffers.emplace_back(asio::buffer(CRCF)); + } + + buffers.emplace_back(asio::buffer(CRCF)); } coro_http_connection *get_conn() { return conn_; } void clear() { - head_.clear(); content_.clear(); if (need_shrink_every_time_) { content_.shrink_to_fit(); } resp_headers_.clear(); - resp_headers_sv_.clear(); keepalive_ = {}; delay_ = false; status_ = status_type::init; fmt_type_ = format_type::normal; boundary_.clear(); has_set_content_ = false; + cookies_.clear(); } void set_shrink_to_fit(bool r) { need_shrink_every_time_ = r; } - void append_head(auto &headers) { - for (auto &[k, v] : headers) { - head_.append(k); - head_.append(":"); - head_.append(v); - head_.append(CRCF); - } + void add_cookie(const cookie &cookie) { + cookies_[cookie.get_name()] = cookie; } private: status_type status_; format_type fmt_type_; - std::string head_; std::string content_; std::optional keepalive_; bool delay_; char buf_[32]; std::vector resp_headers_; - std::vector resp_headers_sv_; coro_http_connection *conn_; std::string boundary_; bool has_set_content_ = false; bool need_shrink_every_time_ = false; + bool need_date_ = true; + std::unordered_map cookies_; + std::string_view content_type_; }; -} // namespace cinatra \ No newline at end of file +} // namespace cinatra diff --git a/include/ylt/thirdparty/cinatra/coro_http_server.hpp b/include/ylt/thirdparty/cinatra/coro_http_server.hpp index 03934e1e6..0a4bf66f0 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_server.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_server.hpp @@ -713,4 +713,8 @@ class coro_http_server { coro_http_router router_; bool need_shrink_every_time_ = false; }; + +using http_server = coro_http_server; +using request = coro_http_request; +using response = coro_http_response; } // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/define.h b/include/ylt/thirdparty/cinatra/define.h index 46907cf97..2a3cf3d84 100644 --- a/include/ylt/thirdparty/cinatra/define.h +++ b/include/ylt/thirdparty/cinatra/define.h @@ -87,7 +87,6 @@ constexpr inline auto TEXT = req_content_type::string; constexpr inline auto RANGES = req_content_type::ranges; constexpr inline auto NONE = req_content_type::none; -inline const std::string_view STATIC_RESOURCE = "cinatra_static_resource"; inline const std::string CSESSIONID = "CSESSIONID"; const static inline std::string CRCF = "\r\n"; @@ -96,6 +95,15 @@ const static inline std::string BOUNDARY = "--CinatraBoundary2B8FAF4A80EDB307"; const static inline std::string MULTIPART_END = CRCF + "--" + BOUNDARY + "--" + CRCF; constexpr std::string_view LAST_CHUNK = "0\r\n"; +constexpr std::string_view CINATRA_HOST_SV = "Server: cinatra\r\n"; +constexpr std::string_view TRANSFER_ENCODING_SV = + "Transfer-Encoding: chunked\r\n"; +constexpr std::string_view CONTENT_LENGTH_SV = "Content-Length: "; +constexpr std::string_view ZERO_LENGTH_SV = "Content-Length: 0\r\n"; +constexpr std::string_view DATE_SV = "Date: "; +constexpr std::string_view CONN_KEEP_SV = "Connection: keep-alive\r\n"; +constexpr std::string_view CONN_CLOSE_SV = "Connection: close\r\n"; +constexpr std::string_view COLON_SV = ": "; struct chunked_result { std::error_code ec; @@ -109,6 +117,111 @@ struct part_head_t { std::string filename; }; +enum resp_content_type { + css, + csv, + htm, + html, + js, + mjs, + txt, + vtt, + apng, + avif, + bmp, + gif, + png, + svg, + webp, + ico, + tif, + tiff, + jpg, + jpeg, + mp4, + mpeg, + webm, + mp3, + mpga, + weba, + wav, + otf, + ttf, + woff, + woff2, + x7z, + atom, + pdf, + json, + rss, + tar, + xht, + xhtml, + xslt, + xml, + gz, + zip, + wasm, + unknown +}; + +constexpr std::array content_type_arr{ + "Content-Type: text/css\r\n", + "Content-Type: text/csv\r\n", + "Content-Type: text/html\r\n", + "Content-Type: text/html\r\n", + "Content-Type: text/javascript\r\n", + "Content-Type: text/javascript\r\n", + "Content-Type: text/plain\r\n", + "Content-Type: text/vtt\r\n", + "Content-Type: image/apng\r\n", + "Content-Type: image/avif\r\n", + "Content-Type: image/bmp\r\n", + "Content-Type: image/gif\r\n", + "Content-Type: image/png\r\n", + "Content-Type: image/svg+xml\r\n", + "Content-Type: image/webp\r\n", + "Content-Type: image/x-icon\r\n", + "Content-Type: image/tiff\r\n", + "Content-Type: image/tiff\r\n", + "Content-Type: image/jpeg\r\n", + "Content-Type: image/jpeg\r\n", + "Content-Type: video/mp4\r\n", + "Content-Type: video/mpeg\r\n", + "Content-Type: video/webm\r\n", + "Content-Type: audio/mp3\r\n", + "Content-Type: audio/mpeg\r\n", + "Content-Type: audio/webm\r\n", + "Content-Type: audio/wave\r\n", + "Content-Type: font/otf\r\n", + "Content-Type: font/ttf\r\n", + "Content-Type: font/woff\r\n", + "Content-Type: font/woff2\r\n", + "Content-Type: application/x-7z-compressed\r\n", + "Content-Type: application/atom+xml\r\n", + "Content-Type: application/pdf\r\n", + "Content-Type: application/json\r\n", + "Content-Type: application/rss+xml\r\n", + "Content-Type: application/x-tar\r\n", + "Content-Type: application/xhtml+xml\r\n", + "Content-Type: application/xhtml+xml\r\n", + "Content-Type: application/xslt+xml\r\n", + "Content-Type: application/xml\r\n", + "Content-Type: application/gzip\r\n", + "Content-Type: application/zip\r\n", + "Content-Type: application/wasm\r\n", + "Content-Type: unknown\r\n"}; + +template +inline constexpr std::string_view get_content_type() { + if constexpr (N > 43) { + return content_type_arr[44]; + } + else { + return content_type_arr[N]; + } +} + inline std::unordered_map g_content_type_map = { {".css", "text/css"}, {".csv", "text/csv"}, diff --git a/include/ylt/thirdparty/cinatra/session.hpp b/include/ylt/thirdparty/cinatra/session.hpp new file mode 100644 index 000000000..42f382ccf --- /dev/null +++ b/include/ylt/thirdparty/cinatra/session.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include +#include + +#include "cookie.hpp" + +namespace cinatra { + +class session { + public: + session(const std::string &session_id, std::size_t session_timeout, + bool need_set_to_client) + : session_id_(session_id), + session_timeout_(session_timeout), + cookie_(CSESSIONID, session_id_), + need_set_to_client_(need_set_to_client) { + time_stamp_ = session_timeout_ + std::time(nullptr); + cookie_.set_max_age(session_timeout_); + } + + void set_session_timeout(const std::size_t session_timeout = 86400) { + std::unique_lock lock(mtx_); + + session_timeout_ = session_timeout; + time_stamp_ = session_timeout_ + std::time(nullptr); + cookie_.set_max_age(session_timeout_); + need_set_to_client_ = true; + } + + void invalidate() { set_session_timeout(0); } + + void set_data(const std::string &name, std::any data) { + std::unique_lock lock(mtx_); + + data_[name] = std::move(data); + } + + void remove_data(const std::string &name) { + std::unique_lock lock(mtx_); + + if (data_.find(name) != data_.end()) { + data_.erase(name); + } + } + + template + std::optional get_data(const std::string &name) { + std::unique_lock lock(mtx_); + + auto iter = data_.find(name); + if (iter != data_.end()) { + try { + return std::any_cast(iter->second); + } catch (const std::exception &e) { + return std::nullopt; + } + } + + return std::nullopt; + } + + const std::string &get_session_id() { + std::unique_lock lock(mtx_); + return session_id_; + } + + std::size_t get_time_stamp() { + std::unique_lock lock(mtx_); + return time_stamp_; + } + + cookie &get_session_cookie() { + std::unique_lock lock(mtx_); + return cookie_; + } + + bool get_need_set_to_client() { + std::unique_lock lock(mtx_); + return need_set_to_client_; + } + + void set_need_set_to_client(bool need_set_to_client) { + std::unique_lock lock(mtx_); + need_set_to_client_ = need_set_to_client; + } + + private: + session() = delete; + + std::string session_id_; + // after session_timeout_ seconds, the session expires + std::size_t session_timeout_; + // the session expires at time_stamp_ + std::time_t time_stamp_; + std::mutex mtx_; + std::unordered_map data_; + cookie cookie_; + bool need_set_to_client_; +}; + +} // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/session_manager.hpp b/include/ylt/thirdparty/cinatra/session_manager.hpp new file mode 100644 index 000000000..2178ed92f --- /dev/null +++ b/include/ylt/thirdparty/cinatra/session_manager.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include + +#include "session.hpp" +#include "ylt/coro_io/coro_io.hpp" + +namespace cinatra { + +class session_manager { + public: + static session_manager &get() { + static session_manager instance; + return instance; + } + + std::string generate_session_id() { + auto tp = std::chrono::high_resolution_clock::now(); + auto nano = tp.time_since_epoch().count(); + id_++; + return std::to_string(nano).append(std::to_string(id_)); + } + + std::shared_ptr get_session(const std::string &session_id) { + std::unique_lock lock(mtx_); + + std::shared_ptr new_session = nullptr; + auto iter = map_.find(session_id); + if (iter != map_.end()) { + return iter->second; + } + else { + new_session = + std::make_shared(session_id, session_timeout_, true); + map_.insert({session_id, new_session}); + } + + return new_session; + } + + void remove_expire_session() { + std::unique_lock lock(mtx_); + + auto now = std::time(nullptr); + for (auto it = map_.begin(); it != map_.end();) { + if (it->second->get_time_stamp() <= now) + it = map_.erase(it); + else + ++it; + } + } + + bool check_session_existence(const std::string &session_id) { + std::unique_lock lock(mtx_); + + return map_.find(session_id) != map_.end(); + } + + void start_check_session_timer() { + check_session_timer_.expires_after(check_session_duration_); + check_session_timer_.async_wait([this](auto ec) { + if (ec || stop_timer_) { + return; + } + + remove_expire_session(); + start_check_session_timer(); + }); + } + + void set_check_session_duration(auto duration) { + check_session_duration_ = duration; + start_check_session_timer(); + } + + void stop_timer() { + stop_timer_ = true; + std::error_code ec; + check_session_timer_.cancel(ec); + } + + private: + session_manager() + : check_session_timer_( + coro_io::get_global_executor()->get_asio_executor()) { + start_check_session_timer(); + }; + session_manager(const session_manager &) = delete; + session_manager(session_manager &&) = delete; + + std::atomic_int64_t id_ = 0; + std::unordered_map> map_; + std::mutex mtx_; + + // session_timeout_ should be no less than 0 + std::size_t session_timeout_ = 86400; + std::atomic stop_timer_ = false; + asio::steady_timer check_session_timer_; + std::chrono::steady_clock::duration check_session_duration_ = + std::chrono::seconds(15); +}; + +} // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/utils.hpp b/include/ylt/thirdparty/cinatra/utils.hpp index 4daf2ea97..befea49ed 100644 --- a/include/ylt/thirdparty/cinatra/utils.hpp +++ b/include/ylt/thirdparty/cinatra/utils.hpp @@ -257,6 +257,39 @@ inline bool is_valid_utf8(unsigned char *s, size_t length) { return true; } +inline std::vector split(std::string_view s, + std::string_view delimiter) { + size_t start = 0; + size_t end = s.find_first_of(delimiter); + + std::vector output; + + while (end <= std::string_view::npos) { + output.emplace_back(s.substr(start, end - start)); + + if (end == std::string_view::npos) + break; + + start = end + 1; + end = s.find_first_of(delimiter, start); + } + + return output; +} + +inline const std::unordered_map +get_cookies_map(std::string_view cookies_str) { + std::unordered_map cookies; + auto cookies_vec = split(cookies_str, "; "); + for (auto iter : cookies_vec) { + auto cookie_key_vlaue = split(iter, "="); + if (cookie_key_vlaue.size() == 2) { + cookies[cookie_key_vlaue[0]] = cookie_key_vlaue[1]; + } + } + return cookies; +}; + } // namespace cinatra #endif // CINATRA_UTILS_HPP