From ec19494853942f20d2fd3c3fbadbd90eceb7f601 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Sat, 30 Jan 2021 18:04:28 +0800 Subject: [PATCH] format code --- include/cinatra/connection.hpp | 2719 ++++++++++++++++--------------- include/cinatra/http_server.hpp | 1045 ++++++------ include/cinatra/request.hpp | 1705 +++++++++---------- include/cinatra/response.hpp | 686 ++++---- 4 files changed, 3037 insertions(+), 3118 deletions(-) diff --git a/include/cinatra/connection.hpp b/include/cinatra/connection.hpp index fddae03..56c9639 100644 --- a/include/cinatra/connection.hpp +++ b/include/cinatra/connection.hpp @@ -11,1376 +11,1391 @@ #include "http_cache.hpp" namespace cinatra { - using http_handler = std::function; - using send_ok_handler = std::function; - using send_failed_handler = std::function; - - class base_connection{ - public: - virtual ~base_connection() {} - }; - - struct ssl_configure { - std::string cert_file; - std::string key_file; - }; - - template - class connection : public base_connection, public std::enable_shared_from_this>, private noncopyable { - public: - explicit connection(boost::asio::io_service& io_service, ssl_configure ssl_conf, std::size_t max_req_size, long keep_alive_timeout, - http_handler& handler, std::string& static_dir, std::function* upload_check): - socket_(io_service), - MAX_REQ_SIZE_(max_req_size), KEEP_ALIVE_TIMEOUT_(keep_alive_timeout), - timer_(io_service), http_handler_(handler), req_(res_), static_dir_(static_dir), upload_check_(upload_check) - { - if constexpr(is_ssl_) { - init_ssl_context(std::move(ssl_conf)); - } - - init_multipart_parser(); - } - - void init_ssl_context(ssl_configure ssl_conf) { +using http_handler = std::function; +using send_ok_handler = std::function; +using send_failed_handler = + std::function; + +class base_connection { +public: + virtual ~base_connection() {} +}; + +struct ssl_configure { + std::string cert_file; + std::string key_file; +}; + +template +class connection : public base_connection, + public std::enable_shared_from_this>, + private noncopyable { +public: + explicit connection( + boost::asio::io_service &io_service, ssl_configure ssl_conf, + std::size_t max_req_size, long keep_alive_timeout, http_handler &handler, + std::string &static_dir, + std::function *upload_check) + : socket_(io_service), MAX_REQ_SIZE_(max_req_size), + KEEP_ALIVE_TIMEOUT_(keep_alive_timeout), timer_(io_service), + http_handler_(handler), req_(res_), static_dir_(static_dir), + upload_check_(upload_check) { + if constexpr (is_ssl_) { + init_ssl_context(std::move(ssl_conf)); + } + + init_multipart_parser(); + } + + void init_ssl_context(ssl_configure ssl_conf) { #ifdef CINATRA_ENABLE_SSL - unsigned long ssl_options = boost::asio::ssl::context::default_workarounds - | boost::asio::ssl::context::no_sslv2 - | boost::asio::ssl::context::single_dh_use; - try { - boost::asio::ssl::context ssl_context(boost::asio::ssl::context::sslv23); - ssl_context.set_options(ssl_options); - ssl_context.set_password_callback([](auto, auto) {return "123456"; }); - - std::error_code ec; - if (fs::exists(ssl_conf.cert_file, ec)) { - ssl_context.use_certificate_chain_file(std::move(ssl_conf.cert_file)); - } - - if (fs::exists(ssl_conf.key_file, ec)) - ssl_context.use_private_key_file(std::move(ssl_conf.key_file), boost::asio::ssl::context::pem); - - //ssl_context_callback(ssl_context); - ssl_stream_ = std::make_unique>(socket_, ssl_context); - } - catch (const std::exception& e) { - std::cout << e.what() << "\n"; - } + unsigned long ssl_options = boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::single_dh_use; + try { + boost::asio::ssl::context ssl_context(boost::asio::ssl::context::sslv23); + ssl_context.set_options(ssl_options); + ssl_context.set_password_callback([](auto, auto) { return "123456"; }); + + std::error_code ec; + if (fs::exists(ssl_conf.cert_file, ec)) { + ssl_context.use_certificate_chain_file(std::move(ssl_conf.cert_file)); + } + + if (fs::exists(ssl_conf.key_file, ec)) + ssl_context.use_private_key_file(std::move(ssl_conf.key_file), + boost::asio::ssl::context::pem); + + // ssl_context_callback(ssl_context); + ssl_stream_ = std::make_unique< + boost::asio::ssl::stream>( + socket_, ssl_context); + } catch (const std::exception &e) { + std::cout << e.what() << "\n"; + } #endif - } + } - auto& tcp_socket() { - return socket_; - } + auto &tcp_socket() { return socket_; } - auto& socket() { - if constexpr (is_ssl_) { + auto &socket() { + if constexpr (is_ssl_) { #ifdef CINATRA_ENABLE_SSL - return *ssl_stream_; + return *ssl_stream_; #else - static_assert(!is_ssl_, "please add definition CINATRA_ENABLE_SSL");//guard, not allowed coming in this branch + static_assert( + !is_ssl_, + "please add definition CINATRA_ENABLE_SSL"); // guard, not allowed coming in this branch #endif - } - else { - return socket_; - } - } - - std::string local_address() { - if (has_closed_) { - return ""; - } - - std::stringstream ss; - boost::system::error_code ec; - ss << socket_.local_endpoint(ec); - if (ec) { - return ""; - } - return ss.str(); - } - - std::string remote_address() { - if (has_closed_) { - return ""; - } - - std::stringstream ss; - boost::system::error_code ec; - ss << socket_.remote_endpoint(ec); - if (ec) { - return ""; - } - return ss.str(); - } - - std::pair remote_ip_port() { - std::string remote_addr = remote_address(); - if (remote_addr.empty()) - return {}; - - size_t pos = remote_addr.find(':'); - std::string ip = remote_addr.substr(0, pos); - std::string port = remote_addr.substr(pos + 1); - return {std::move(ip), std::move(port)}; - } - - void start() { - req_.set_conn(this->shared_from_this()); - do_read(); - } - - const std::string& static_dir() { - return static_dir_; - } - - void on_error(status_type status, std::string&& reason) { - keep_alive_ = false; - req_.set_state(data_proc_state::data_error); - response_back(status, std::move(reason)); - } - - void on_close() { - keep_alive_ = false; - req_.set_state(data_proc_state::data_error); - close(); - } - - void reset_timer() { - if (!enable_timeout_) - return; - - timer_.expires_from_now(std::chrono::seconds(KEEP_ALIVE_TIMEOUT_)); - auto self = this->shared_from_this(); - - timer_.async_wait([self](boost::system::error_code const& ec) { - if (ec) { - return; - } - - self->close(false); - }); - } - - void cancel_timer() { - if (!enable_timeout_) - return; - - boost::system::error_code ec; - timer_.cancel(ec); - } - - void enable_timeout(bool enable) { - enable_timeout_ = enable; - } - - void set_tag(std::any&& tag) { - tag_ = std::move(tag); - } - - auto& get_tag() { - return tag_; - } - - template - void send_ws_string(std::string msg, Fs&&... fs) { - send_ws_msg(std::move(msg), opcode::text, std::forward(fs)...); - } - - template - void send_ws_binary(std::string msg, Fs&&... fs) { - send_ws_msg(std::move(msg), opcode::binary, std::forward(fs)...); - } - - template - void send_ws_msg(std::string msg, opcode op = opcode::text, Fs&&... fs) { - constexpr const size_t size = sizeof...(Fs); - static_assert(size != 0 || size != 2); - if constexpr(size == 2) { - set_callback(std::forward(fs)...); - } - - auto header = ws_.format_header(msg.length(), op); - send_msg(std::move(header), std::move(msg)); - } - - void write_chunked_header(std::string_view mime,bool is_range=false) { - req_.set_http_type(content_type::chunked); - reset_timer(); - if(!is_range){ - chunked_header_ = http_chunk_header + "Content-Type: " + std::string(mime.data(), mime.length()) + "\r\n\r\n"; - }else{ - chunked_header_ = http_range_chunk_header + "Content-Type: " + std::string(mime.data(), mime.length()) + "\r\n\r\n"; - } - boost::asio::async_write(socket(), - boost::asio::buffer(chunked_header_), - [self = this->shared_from_this()](const boost::system::error_code& ec, std::size_t bytes_transferred) { - self->handle_chunked_header(ec); - }); - } - - void write_ranges_header(std::string header_str) { - reset_timer(); - chunked_header_ = std::move(header_str); //reuse the variable - boost::asio::async_write(socket(), - boost::asio::buffer(chunked_header_), - [this, self = this->shared_from_this()](const boost::system::error_code& ec, std::size_t bytes_transferred) { - handle_chunked_header(ec); - }); - } - - void write_chunked_data(std::string&& buf, bool eof) { - reset_timer(); - - std::vector buffers = res_.to_chunked_buffers(buf.data(), buf.length(), eof); - if (buffers.empty()) { - handle_write(boost::system::error_code{}); - return; - } - - auto self = this->shared_from_this(); - boost::asio::async_write(socket(), buffers, [this, self, buf = std::move(buf), eof](const boost::system::error_code& ec, size_t) { - if (ec) { - return; - } - - if (eof) { - req_.set_state(data_proc_state::data_end); - } - else { - req_.set_state(data_proc_state::data_continue); - } - - call_back(); - }); - } - - void write_ranges_data(std::string&& buf, bool eof) { - reset_timer(); - - chunked_header_ = std::move(buf); //reuse the variable - auto self = this->shared_from_this(); - boost::asio::async_write(socket(), boost::asio::buffer(chunked_header_), [this, self, eof](const boost::system::error_code& ec, size_t) { - if (ec) { - return; - } - - if (eof) { - req_.set_state(data_proc_state::data_end); - } - else { - req_.set_state(data_proc_state::data_continue); - } - - call_back(); - }); - } - - void response_now() { - do_write(); - } + } else { + return socket_; + } + } + + std::string local_address() { + if (has_closed_) { + return ""; + } + + std::stringstream ss; + boost::system::error_code ec; + ss << socket_.local_endpoint(ec); + if (ec) { + return ""; + } + return ss.str(); + } + + std::string remote_address() { + if (has_closed_) { + return ""; + } + + std::stringstream ss; + boost::system::error_code ec; + ss << socket_.remote_endpoint(ec); + if (ec) { + return ""; + } + return ss.str(); + } + + std::pair remote_ip_port() { + std::string remote_addr = remote_address(); + if (remote_addr.empty()) + return {}; + + size_t pos = remote_addr.find(':'); + std::string ip = remote_addr.substr(0, pos); + std::string port = remote_addr.substr(pos + 1); + return {std::move(ip), std::move(port)}; + } + + void start() { + req_.set_conn(this->shared_from_this()); + do_read(); + } + + const std::string &static_dir() { return static_dir_; } + + void on_error(status_type status, std::string &&reason) { + keep_alive_ = false; + req_.set_state(data_proc_state::data_error); + response_back(status, std::move(reason)); + } + + void on_close() { + keep_alive_ = false; + req_.set_state(data_proc_state::data_error); + close(); + } + + void reset_timer() { + if (!enable_timeout_) + return; + + timer_.expires_from_now(std::chrono::seconds(KEEP_ALIVE_TIMEOUT_)); + auto self = this->shared_from_this(); + + timer_.async_wait([self](boost::system::error_code const &ec) { + if (ec) { + return; + } + + self->close(false); + }); + } + + void cancel_timer() { + if (!enable_timeout_) + return; + + boost::system::error_code ec; + timer_.cancel(ec); + } + + void enable_timeout(bool enable) { enable_timeout_ = enable; } + + void set_tag(std::any &&tag) { tag_ = std::move(tag); } + + auto &get_tag() { return tag_; } + + template void send_ws_string(std::string msg, Fs &&... fs) { + send_ws_msg(std::move(msg), opcode::text, std::forward(fs)...); + } + + template void send_ws_binary(std::string msg, Fs &&... fs) { + send_ws_msg(std::move(msg), opcode::binary, std::forward(fs)...); + } + + template + void send_ws_msg(std::string msg, opcode op = opcode::text, Fs &&... fs) { + constexpr const size_t size = sizeof...(Fs); + static_assert(size != 0 || size != 2); + if constexpr (size == 2) { + set_callback(std::forward(fs)...); + } + + auto header = ws_.format_header(msg.length(), op); + send_msg(std::move(header), std::move(msg)); + } + + void write_chunked_header(std::string_view mime, bool is_range = false) { + req_.set_http_type(content_type::chunked); + reset_timer(); + if (!is_range) { + chunked_header_ = http_chunk_header + "Content-Type: " + + std::string(mime.data(), mime.length()) + "\r\n\r\n"; + } else { + chunked_header_ = http_range_chunk_header + "Content-Type: " + + std::string(mime.data(), mime.length()) + "\r\n\r\n"; + } + boost::asio::async_write( + socket(), boost::asio::buffer(chunked_header_), + [self = this->shared_from_this()](const boost::system::error_code &ec, + std::size_t bytes_transferred) { + self->handle_chunked_header(ec); + }); + } + + void write_ranges_header(std::string header_str) { + reset_timer(); + chunked_header_ = std::move(header_str); // reuse the variable + boost::asio::async_write( + socket(), boost::asio::buffer(chunked_header_), + [this, self = this->shared_from_this()]( + const boost::system::error_code &ec, + std::size_t bytes_transferred) { handle_chunked_header(ec); }); + } + + void write_chunked_data(std::string &&buf, bool eof) { + reset_timer(); + + std::vector buffers = + res_.to_chunked_buffers(buf.data(), buf.length(), eof); + if (buffers.empty()) { + handle_write(boost::system::error_code{}); + return; + } + + auto self = this->shared_from_this(); + boost::asio::async_write(socket(), buffers, + [this, self, buf = std::move(buf), eof]( + const boost::system::error_code &ec, size_t) { + if (ec) { + return; + } + + if (eof) { + req_.set_state(data_proc_state::data_end); + } else { + req_.set_state(data_proc_state::data_continue); + } + + call_back(); + }); + } + + void write_ranges_data(std::string &&buf, bool eof) { + reset_timer(); + + chunked_header_ = std::move(buf); // reuse the variable + auto self = this->shared_from_this(); + boost::asio::async_write( + socket(), boost::asio::buffer(chunked_header_), + [this, self, eof](const boost::system::error_code &ec, size_t) { + if (ec) { + return; + } + + if (eof) { + req_.set_state(data_proc_state::data_end); + } else { + req_.set_state(data_proc_state::data_continue); + } + + call_back(); + }); + } + + void response_now() { do_write(); } + + void + set_multipart_begin(std::function begin) { + multipart_begin_ = std::move(begin); + } + + void set_validate(size_t max_header_len, check_header_cb check_headers) { + req_.set_validate(max_header_len, std::move(check_headers)); + } + + void enable_response_time(bool enable) { res_.enable_response_time(enable); } + + bool has_close() { return has_closed_; } + + response &get_res() { return res_; } + + //~connection() { + // close(); + //} +private: + void do_read() { + reset(); + + if (is_ssl_ && !has_shake_) { + async_handshake(); + } else { + async_read_some(); + } + } + + void reset() { + last_transfer_ = 0; + len_ = 0; + req_.reset(); + res_.reset(); + reset_timer(); + } + + void async_handshake() { +#ifdef CINATRA_ENABLE_SSL + ssl_stream_->async_handshake(boost::asio::ssl::stream_base::server, + [this, self = this->shared_from_this()]( + const boost::system::error_code &error) { + if (error) { + std::cout << error.message() << std::endl; + return; + } + + has_shake_ = true; + async_read_some(); + }); +#else + static_assert( + !is_ssl_, + "please add definition CINATRA_ENABLE_SSL"); // guard, not allowed coming in this branch +#endif + } - void set_multipart_begin(std::function begin) { - multipart_begin_ = std::move(begin); - } + void async_read_some() { +#ifdef CINATRA_ENABLE_SSL + if (is_ssl_ && ssl_stream_ == nullptr) { + return; + } +#endif - void set_validate(size_t max_header_len, check_header_cb check_headers) { - req_.set_validate(max_header_len, std::move(check_headers)); + socket().async_read_some( + boost::asio::buffer(req_.buffer(), req_.left_size()), + [this, self = this->shared_from_this()]( + const boost::system::error_code &e, std::size_t bytes_transferred) { + if (e) { + close(); + has_shake_ = false; + return; + } + + handle_read(e, bytes_transferred); + }); + } + + void handle_read(const boost::system::error_code &e, + std::size_t bytes_transferred) { + if (e) { + close(); + return; + } + + auto last_len = req_.current_size(); + last_transfer_ = last_len; + bool at_capacity = req_.update_and_expand_size(bytes_transferred); + if (at_capacity) { + response_back(status_type::bad_request, + "The request is too long, limitation is 3M"); + return; + } + + int ret = req_.parse_header(len_); + + if (ret == parse_status::has_error) { + response_back(status_type::bad_request); + return; + } + + check_keep_alive(); + if (ret == parse_status::not_complete) { + do_read_head(); + } else { + auto total_len = req_.total_len(); + if (bytes_transferred > total_len + 4) { + std::string_view str(req_.data() + len_ + total_len, 4); + if (str == "GET " || str == "POST") { + handle_pipeline(total_len, bytes_transferred); + return; } - - void enable_response_time(bool enable) { - res_.enable_response_time(enable); + } + // if (req_.get_method() == "GET"&&http_cache::get().need_cache(req_.get_url())&&!http_cache::get().not_cache(req_.get_url())) { handle_cache(); return; + // } + + req_.set_last_len(len_); + handle_request(bytes_transferred); + } + } + + void handle_request(std::size_t bytes_transferred) { + if (req_.has_body()) { + auto type = get_content_type(); + req_.set_http_type(type); + switch (type) { + case cinatra::content_type::string: + case cinatra::content_type::unknown: + handle_string_body(bytes_transferred); + break; + case cinatra::content_type::multipart: + handle_multipart(); + break; + case cinatra::content_type::octet_stream: + handle_octet_stream(bytes_transferred); + break; + case cinatra::content_type::urlencoded: + handle_form_urlencoded(bytes_transferred); + break; + case cinatra::content_type::chunked: + handle_chunked(bytes_transferred); + break; + } + } else { + handle_header_request(); + } + } + + void handle_cache() { + auto raw_url = req_.raw_url(); + if (!http_cache::get().empty()) { + auto resp_vec = + http_cache::get().get(std::string(raw_url.data(), raw_url.length())); + if (!resp_vec.empty()) { + std::vector buffers; + for (auto &iter : resp_vec) { + buffers.emplace_back(boost::asio::buffer(iter.data(), iter.size())); } - - bool has_close() { - return has_closed_; - } - - response& get_res() { - return res_; + boost::asio::async_write( + socket(), buffers, + [self = this->shared_from_this(), resp_vec = std::move(resp_vec)]( + const boost::system::error_code &ec, + std::size_t bytes_transferred) { self->handle_write(ec); }); + } + } + } + + void handle_pipeline(size_t ret, std::size_t bytes_transferred) { + res_.set_delay(true); + req_.set_last_len(len_); + handle_request(bytes_transferred); + last_transfer_ += bytes_transferred; + if (len_ == 0) + len_ = ret; + else + len_ += ret; + + auto &rep_str = res_.response_str(); + int result = 0; + size_t left = ret; + bool head_not_complete = false; + bool body_not_complete = false; + size_t left_body_len = 0; + // int index = 1; + while (true) { + // std::cout << std::this_thread::get_id() << ", index: " << index << "\n"; + result = req_.parse_header(len_); + if (result == -1) { + return; + } + + if (result == -2) { + head_not_complete = true; + break; + } + + // index++; + auto total_len = req_.total_len(); + + if (total_len <= (bytes_transferred - len_)) { + req_.set_last_len(len_); + handle_request(bytes_transferred); + } + + len_ += total_len; + + if (len_ == last_transfer_) { + break; + } else if (len_ > last_transfer_) { + auto n = len_ - last_transfer_; + len_ -= total_len; + if (n < req_.header_len()) { + head_not_complete = true; + } else { + body_not_complete = true; + left_body_len = n; } - //~connection() { - // close(); - //} - private: - void do_read() { - reset(); - - if (is_ssl_ && !has_shake_) { - async_handshake(); - } - else { - async_read_some(); - } - } - - void reset() { - last_transfer_ = 0; - len_ = 0; - req_.reset(); - res_.reset(); - reset_timer(); + break; + } + } + + res_.set_delay(false); + boost::asio::async_write( + socket(), boost::asio::buffer(rep_str.data(), rep_str.size()), + [head_not_complete, body_not_complete, left_body_len, this, + self = this->shared_from_this(), + &rep_str](const boost::system::error_code &ec, + std::size_t bytes_transferred) { + rep_str.clear(); + if (head_not_complete) { + do_read_head(); + return; + } + + if (body_not_complete) { + req_.set_left_body_size(left_body_len); + do_read_body(); + return; + } + + handle_write(ec); + }); + } + + void do_read_head() { + reset_timer(); + + socket().async_read_some( + boost::asio::buffer(req_.buffer(), req_.left_size()), + [this, self = this->shared_from_this()]( + const boost::system::error_code &e, std::size_t bytes_transferred) { + handle_read(e, bytes_transferred); + }); + } + + void do_read_body() { + reset_timer(); + + auto self = this->shared_from_this(); + boost::asio::async_read( + socket(), boost::asio::buffer(req_.buffer(), req_.left_body_len()), + [this, self](const boost::system::error_code &ec, + size_t bytes_transferred) { + if (ec) { + // LOG_WARN << ec.message(); + close(); + return; + } + + req_.update_size(bytes_transferred); + req_.reduce_left_body_size(bytes_transferred); + + if (req_.body_finished()) { + handle_body(); + } else { + do_read_body(); + } + }); + } + + void do_write() { + reset_timer(); + + std::string &rep_str = res_.response_str(); + if (rep_str.empty()) { + handle_write(boost::system::error_code{}); + return; + } + + // cache + // if (req_.get_method() == "GET"&&http_cache::get().need_cache(req_.get_url()) && !http_cache::get().not_cache(req_.get_url())) { auto raw_url = req_.raw_url(); http_cache::get().add(std::string(raw_url.data(), raw_url.length()), res_.raw_content()); + // } + + boost::asio::async_write( + socket(), boost::asio::buffer(rep_str.data(), rep_str.size()), + [this, self = this->shared_from_this()]( + const boost::system::error_code &ec, + std::size_t bytes_transferred) { handle_write(ec); }); + } + + void handle_write(const boost::system::error_code &ec) { + if (ec) { + return; + } + + if (keep_alive_) { + do_read(); + } else { + reset(); + cancel_timer(); // avoid close two times + shutdown(); + close(); + } + } + + content_type get_content_type() { + if (req_.is_chunked()) + return content_type::chunked; + + auto content_type = req_.get_header_value("content-type"); + if (!content_type.empty()) { + if (content_type.find("application/x-www-form-urlencoded") != + std::string_view::npos) { + return content_type::urlencoded; + } else if (content_type.find("multipart/form-data") != + std::string_view::npos) { + auto size = content_type.find("="); + auto bd = content_type.substr(size + 1, content_type.length() - size); + if (bd[0] == '"' && bd[bd.length() - 1] == '"') { + bd = bd.substr(1, bd.length() - 2); } - - void async_handshake() { -#ifdef CINATRA_ENABLE_SSL - ssl_stream_->async_handshake(boost::asio::ssl::stream_base::server, - [this, self = this->shared_from_this()](const boost::system::error_code& error) { - if (error) { - std::cout << error.message() << std::endl; - return; - } - - has_shake_ = true; - async_read_some(); - }); -#else - static_assert(!is_ssl_, "please add definition CINATRA_ENABLE_SSL");//guard, not allowed coming in this branch -#endif - } - - void async_read_some() { + std::string boundary(bd.data(), bd.length()); + multipart_parser_.set_boundary("\r\n--" + std::move(boundary)); + return content_type::multipart; + } else if (content_type.find("application/octet-stream") != + std::string_view::npos) { + return content_type::octet_stream; + } else { + return content_type::string; + } + } + + return content_type::unknown; + } + + void close(bool close_ssl = true) { #ifdef CINATRA_ENABLE_SSL - if (is_ssl_ && ssl_stream_ == nullptr) { - return; - } -#endif - - socket().async_read_some(boost::asio::buffer(req_.buffer(), req_.left_size()), - [this, self = this->shared_from_this()](const boost::system::error_code& e, std::size_t bytes_transferred) { - if (e) { - close(); - has_shake_ = false; - return; - } - - handle_read(e, bytes_transferred); - }); - } - - void handle_read(const boost::system::error_code& e, std::size_t bytes_transferred) { - if (e) { - close(); - return; - } - - auto last_len = req_.current_size(); - last_transfer_ = last_len; - bool at_capacity = req_.update_and_expand_size(bytes_transferred); - if (at_capacity) { - response_back(status_type::bad_request, "The request is too long, limitation is 3M"); - return; - } - - int ret = req_.parse_header(len_); - - if (ret == parse_status::has_error) { - response_back(status_type::bad_request); - return; - } - - check_keep_alive(); - if (ret == parse_status::not_complete) { - do_read_head(); - } - else { - auto total_len = req_.total_len(); - if (bytes_transferred > total_len + 4) { - std::string_view str(req_.data()+ len_+ total_len, 4); - if (str == "GET " || str == "POST") { - handle_pipeline(total_len, bytes_transferred); - return; - } - } -// if (req_.get_method() == "GET"&&http_cache::get().need_cache(req_.get_url())&&!http_cache::get().not_cache(req_.get_url())) { -// handle_cache(); -// return; -// } - - req_.set_last_len(len_); - handle_request(bytes_transferred); - } - } - - void handle_request(std::size_t bytes_transferred) { - if (req_.has_body()) { - auto type = get_content_type(); - req_.set_http_type(type); - switch (type) { - case cinatra::content_type::string: - case cinatra::content_type::unknown: - handle_string_body(bytes_transferred); - break; - case cinatra::content_type::multipart: - handle_multipart(); - break; - case cinatra::content_type::octet_stream: - handle_octet_stream(bytes_transferred); - break; - case cinatra::content_type::urlencoded: - handle_form_urlencoded(bytes_transferred); - break; - case cinatra::content_type::chunked: - handle_chunked(bytes_transferred); - break; - } - } - else { - handle_header_request(); - } - } - - void handle_cache() { - auto raw_url = req_.raw_url(); - if (!http_cache::get().empty()) { - auto resp_vec = http_cache::get().get(std::string(raw_url.data(), raw_url.length())); - if (!resp_vec.empty()) { - std::vector buffers; - for (auto& iter : resp_vec) { - buffers.emplace_back(boost::asio::buffer(iter.data(), iter.size())); - } - boost::asio::async_write(socket(), buffers, - [self = this->shared_from_this(), resp_vec = std::move(resp_vec)](const boost::system::error_code& ec, std::size_t bytes_transferred) { - self->handle_write(ec); - }); - } - } - } - - void handle_pipeline(size_t ret, std::size_t bytes_transferred) { - res_.set_delay(true); - req_.set_last_len(len_); - handle_request(bytes_transferred); - last_transfer_ += bytes_transferred; - if (len_ == 0) - len_ = ret; - else - len_ += ret; - - auto& rep_str = res_.response_str(); - int result = 0; - size_t left = ret; - bool head_not_complete = false; - bool body_not_complete = false; - size_t left_body_len = 0; - //int index = 1; - while (true) { - //std::cout << std::this_thread::get_id() << ", index: " << index << "\n"; - result = req_.parse_header(len_); - if (result == -1) { - return; - } - - if (result == -2) { - head_not_complete = true; - break; - } - - //index++; - auto total_len = req_.total_len(); - - if (total_len <= (bytes_transferred - len_)) { - req_.set_last_len(len_); - handle_request(bytes_transferred); - } - - len_ += total_len; - - if (len_ == last_transfer_) { - break; - } - else if (len_ > last_transfer_) { - auto n = len_ - last_transfer_; - len_ -= total_len; - if (nshared_from_this(), &rep_str](const boost::system::error_code& ec, std::size_t bytes_transferred) { - rep_str.clear(); - if (head_not_complete) { - do_read_head(); - return; - } - - if (body_not_complete) { - req_.set_left_body_size(left_body_len); - do_read_body(); - return; - } - - handle_write(ec); - }); - } - - void do_read_head() { - reset_timer(); - - socket().async_read_some(boost::asio::buffer(req_.buffer(), req_.left_size()), - [this, self = this->shared_from_this()](const boost::system::error_code& e, std::size_t bytes_transferred) { - handle_read(e, bytes_transferred); - }); - } - - void do_read_body() { - reset_timer(); - - auto self = this->shared_from_this(); - boost::asio::async_read(socket(), boost::asio::buffer(req_.buffer(), req_.left_body_len()), - [this, self](const boost::system::error_code& ec, size_t bytes_transferred) { - if (ec) { - //LOG_WARN << ec.message(); - close(); - return; - } - - req_.update_size(bytes_transferred); - req_.reduce_left_body_size(bytes_transferred); - - if (req_.body_finished()) { - handle_body(); - } - else { - do_read_body(); - } - }); - } - - void do_write() { - reset_timer(); - - std::string& rep_str = res_.response_str(); - if (rep_str.empty()) { - handle_write(boost::system::error_code{}); - return; - } - - //cache -// if (req_.get_method() == "GET"&&http_cache::get().need_cache(req_.get_url()) && !http_cache::get().not_cache(req_.get_url())) { -// auto raw_url = req_.raw_url(); -// http_cache::get().add(std::string(raw_url.data(), raw_url.length()), res_.raw_content()); -// } - - boost::asio::async_write(socket(), boost::asio::buffer(rep_str.data(), rep_str.size()), - [this, self = this->shared_from_this()](const boost::system::error_code& ec, std::size_t bytes_transferred) { - handle_write(ec); - }); - } - - void handle_write(const boost::system::error_code& ec) { - if (ec) { - return; - } - - if (keep_alive_) { - do_read(); - } - else { - reset(); - cancel_timer(); //avoid close two times - shutdown(); - close(); - } - } - - content_type get_content_type() { - if (req_.is_chunked()) - return content_type::chunked; - - auto content_type = req_.get_header_value("content-type"); - if (!content_type.empty()) { - if (content_type.find("application/x-www-form-urlencoded") != std::string_view::npos) { - return content_type::urlencoded; - } - else if (content_type.find("multipart/form-data") != std::string_view::npos) { - auto size = content_type.find("="); - auto bd = content_type.substr(size + 1, content_type.length() - size); - if (bd[0] == '"'&& bd[bd.length()-1] == '"') { - bd = bd.substr(1, bd.length() - 2); - } - std::string boundary(bd.data(), bd.length()); - multipart_parser_.set_boundary("\r\n--" + std::move(boundary)); - return content_type::multipart; - } - else if (content_type.find("application/octet-stream") != std::string_view::npos) { - return content_type::octet_stream; - } - else { - return content_type::string; - } - } - - return content_type::unknown; - } - - void close(bool close_ssl = true) { -#ifdef CINATRA_ENABLE_SSL - if (close_ssl && ssl_stream_) { - boost::system::error_code ec; - ssl_stream_->shutdown(ec); - ssl_stream_ = nullptr; - } + if (close_ssl && ssl_stream_) { + boost::system::error_code ec; + ssl_stream_->shutdown(ec); + ssl_stream_ = nullptr; + } #endif - if (has_closed_) { - return; - } - - req_.close_upload_file(); - shutdown(); - boost::system::error_code ec; - socket_.close(ec); - has_closed_ = true; - has_shake_ = false; - } - - /****************** begin handle http body data *****************/ - void handle_string_body(std::size_t bytes_transferred) { - //defalt add limitation for string_body and else. you can remove the limitation for very big string. - if (req_.at_capacity()) { - response_back(status_type::bad_request, "The request is too long, limitation is 3M"); - return; - } - - if (req_.has_recieved_all()) { - handle_body(); - } - else { - req_.expand_size(); - assert(req_.current_size() >= req_.header_len()); - size_t part_size = req_.current_size() - req_.header_len(); - if (part_size == -1) { - response_back(status_type::internal_server_error); - return; - } - req_.reduce_left_body_size(part_size); - do_read_body(); - } - } - - //-------------octet-stream----------------// - void handle_octet_stream(size_t bytes_transferred) { - //call_back(); - try { - auto tp = std::chrono::high_resolution_clock::now(); - auto nano = tp.time_since_epoch().count(); - std::string name = static_dir_ + "/" + std::to_string(nano); - req_.open_upload_file(name); - } - catch (const std::exception& ex) { - response_back(status_type::internal_server_error, ex.what()); - return; - } - - req_.set_state(data_proc_state::data_continue);//data - size_t part_size = bytes_transferred - req_.header_len(); - if (part_size != 0) { - req_.reduce_left_body_size(part_size); - req_.set_part_data({ req_.current_part(), part_size }); - req_.write_upload_data(req_.current_part(), part_size); - //call_back(); - } - - if (req_.has_recieved_all()) { - //on finish - req_.set_state(data_proc_state::data_end); - call_back(); - do_write(); - } - else { - req_.fit_size(); - req_.set_current_size(0); - do_read_octet_stream_body(); - } - } - - void do_read_octet_stream_body() { - auto self = this->shared_from_this(); - boost::asio::async_read(socket(), boost::asio::buffer(req_.buffer(), req_.left_body_len()), - [this, self](const boost::system::error_code& ec, size_t bytes_transferred) { - if (ec) { - req_.set_state(data_proc_state::data_error); - call_back(); - close(); - return; - } - - req_.set_part_data({ req_.buffer(), bytes_transferred }); - req_.write_upload_data(req_.buffer(), bytes_transferred); - //call_back(); - - req_.reduce_left_body_size(bytes_transferred); - - if (req_.body_finished()) { - req_.set_state(data_proc_state::data_end); - call_back(); - do_write(); - } - else { - do_read_octet_stream_body(); - } - }); - } - - //-------------octet-stream----------------// - - //-------------form urlencoded----------------// - //TODO: here later will refactor the duplicate code - void handle_form_urlencoded(size_t bytes_transferred) { - if (req_.at_capacity()) { - response_back(status_type::bad_request, "The request is too long, limitation is 3M"); - return; - } - - if (req_.has_recieved_all()) { - handle_url_urlencoded_body(); - } - else { - req_.expand_size(); - size_t part_size = bytes_transferred - req_.header_len(); - req_.reduce_left_body_size(part_size); - //req_.fit_size(); - do_read_form_urlencoded(); - } - } - - void handle_url_urlencoded_body() { - bool success = req_.parse_form_urlencoded(); - - if (!success) { - response_back(status_type::bad_request, "form urlencoded error"); - return; - } - if (req_.body_len() > 0 && !req_.check_request()) { - response_back(status_type::bad_request, "request check error"); - return; - } - - call_back(); - if (!res_.need_delay()) - do_write(); - } - - void do_read_form_urlencoded() { - reset_timer(); - - auto self = this->shared_from_this(); - boost::asio::async_read(socket(), boost::asio::buffer(req_.buffer(), req_.left_body_len()), - [this, self](const boost::system::error_code& ec, size_t bytes_transferred) { - if (ec) { - //LOG_WARN << ec.message(); - close(); - return; - } - - req_.update_size(bytes_transferred); - req_.reduce_left_body_size(bytes_transferred); - - if (req_.body_finished()) { - handle_url_urlencoded_body(); - } - else { - do_read_form_urlencoded(); - } - }); - } - //-------------form urlencoded----------------// - - void call_back() { - assert(http_handler_); - http_handler_(req_, res_); - } - - void call_back_data() { - req_.set_state(data_proc_state::data_continue); - call_back(); - req_.set_part_data({}); - } - //-------------multipart----------------------// - void init_multipart_parser() { - multipart_parser_.on_part_begin = [this](const multipart_headers & headers) { - req_.set_multipart_headers(headers); - auto filename = req_.get_multipart_field_name("filename"); - is_multi_part_file_ = req_.is_multipart_file(); - if (filename.empty()&& is_multi_part_file_) { - req_.set_state(data_proc_state::data_error); - res_.set_status_and_content(status_type::bad_request, "mutipart error"); - return; - } - if(is_multi_part_file_) - { - auto ext = get_extension(filename); - try { - auto tp = std::chrono::high_resolution_clock::now(); - auto nano = tp.time_since_epoch().count(); - std::string name = static_dir_ + "/" + std::to_string(nano) - + std::string(ext.data(), ext.length())+"_ing"; - if (multipart_begin_) { - multipart_begin_(req_, name); - name = static_dir_ + "/" + name; - } - - req_.open_upload_file(name); - } - catch (const std::exception& ex) { - req_.set_state(data_proc_state::data_error); - res_.set_status_and_content(status_type::internal_server_error, ex.what()); - return; - } - }else{ - auto key = req_.get_multipart_field_name("name"); - req_.save_multipart_key_value(std::string(key.data(),key.size()),""); - } - }; - multipart_parser_.on_part_data = [this](const char* buf, size_t size) { - if (req_.get_state() == data_proc_state::data_error) { - return; - } - if(is_multi_part_file_){ - req_.write_upload_data(buf, size); - }else{ - auto key = req_.get_multipart_field_name("name"); - req_.update_multipart_value(std::move(key), buf, size); - } - }; - multipart_parser_.on_part_end = [this] { - if (req_.get_state() == data_proc_state::data_error) - return; - if(is_multi_part_file_) - { - req_.close_upload_file(); - auto pfile = req_.get_file(); - if (pfile) { - auto old_name = pfile->get_file_path(); - auto pos = old_name.rfind("_ing"); - if (pos != std::string::npos) { - pfile->rename_file(old_name.substr(0, old_name.length() - 4)); - } - } - } - }; - multipart_parser_.on_end = [this] { - if (req_.get_state() == data_proc_state::data_error) - return; - req_.handle_multipart_key_value(); - //call_back(); - }; - } - - bool parse_multipart(size_t size, std::size_t length) { - if (length == 0) - return false; - - req_.set_part_data(std::string_view(req_.buffer(size), length)); - std::string_view multipart_body = req_.get_part_data(); - size_t bufsize = multipart_body.length(); - - size_t fed = 0; - do { - size_t ret = multipart_parser_.feed(multipart_body.data() + fed, multipart_body.length() - fed); - fed += ret; - } while (fed < bufsize && !multipart_parser_.stopped()); - - if (multipart_parser_.has_error()) { - //LOG_WARN << multipart_parser_.get_error_message(); - req_.set_state(data_proc_state::data_error); - return true; - } - - req_.reduce_left_body_size(length); - return false; - } - - void handle_multipart() { - if (upload_check_) { - bool r = (*upload_check_)(req_, res_); - if (!r) { - close(); - return; - } - } - - bool has_error = parse_multipart(req_.header_len(), req_.current_size() - req_.header_len()); - - if (has_error) { - response_back(status_type::bad_request, "mutipart error"); - return; - } - - if (req_.has_recieved_all_part()) { - call_back(); - do_write(); - } - else { - req_.set_current_size(0); - do_read_multipart(); - } - } - - void do_read_multipart() { - reset_timer(); - - req_.fit_size(); - auto self = this->shared_from_this(); - boost::asio::async_read(socket(), boost::asio::buffer(req_.buffer(), req_.left_body_len()), - [self, this](boost::system::error_code ec, std::size_t length) { - if (ec) { - req_.set_state(data_proc_state::data_error); - call_back(); - response_back(status_type::bad_request, "mutipart error"); - return; - } - - bool has_error = parse_multipart(0, length); - - if (has_error) { //parse error - keep_alive_ = false; - response_back(status_type::bad_request, "mutipart error"); - return; - } - - reset_timer(); - if (req_.body_finished()) { - call_back(); - do_write(); - return; - } - - req_.set_current_size(0); - do_read_part_data(); - }); - } - - void do_read_part_data() { - auto self = this->shared_from_this(); - boost::asio::async_read(socket(), boost::asio::buffer(req_.buffer(), req_.left_body_size()), - [self, this](boost::system::error_code ec, std::size_t length) { - if (ec) { - req_.set_state(data_proc_state::data_error); - call_back(); - return; - } - - bool has_error = parse_multipart(0, length); - - if (has_error) { - response_back(status_type::bad_request, "mutipart error"); - return; - } - - reset_timer(); - if (!req_.body_finished()) { - do_read_part_data(); - } - else { - //response_back(status_type::ok, "multipart finished"); - call_back(); - do_write(); - } - }); - } - //-------------multipart----------------------// - - void handle_header_request() { - if (is_upgrade_) { //websocket - req_.set_http_type(content_type::websocket); - //timer_.cancel(); - ws_.upgrade_to_websocket(req_, res_); - response_handshake(); - return; - } - - bool r = handle_gzip(); - if (!r) { - response_back(status_type::bad_request, "gzip uncompress error"); - return; - } - - call_back(); - - if (req_.get_content_type() == content_type::chunked) - return; - - if (req_.get_state() == data_proc_state::data_error) { - return; - } - - if (!res_.need_delay()) - do_write(); - } - - //-------------web socket----------------// - void response_handshake() { - std::vector buffers = res_.to_buffers(); - if (buffers.empty()) { - close(); - return; - } - - auto self = this->shared_from_this(); - boost::asio::async_write(socket(), buffers, [this, self](const boost::system::error_code& ec, std::size_t length) { - if (ec) { - close(); - return; - } - - req_.set_state(data_proc_state::data_begin); - call_back(); - req_.call_event(req_.get_state()); - - req_.set_current_size(0); - do_read_websocket_head(SHORT_HEADER); - }); - } - - void do_read_websocket_head(size_t length) { - auto self = this->shared_from_this(); - boost::asio::async_read(socket(), boost::asio::buffer(req_.buffer(), length), - [this, self](const boost::system::error_code& ec, size_t bytes_transferred) { - if (ec) { - cancel_timer(); - req_.call_event(data_proc_state::data_error); - - close(); - return; - } - - size_t length = bytes_transferred + req_.current_size(); - req_.set_current_size(0); - int ret = ws_.parse_header(req_.buffer(), length); - - if (ret == parse_status::complete) { - //read payload - auto payload_length = ws_.payload_length(); - req_.set_body_len(payload_length); - if (req_.at_capacity(payload_length)) { - req_.call_event(data_proc_state::data_error); - close(); - return; - } - - req_.set_current_size(0); - req_.fit_size(); - do_read_websocket_data(req_.left_body_len()); - } - else if (ret == parse_status::not_complete) { - req_.set_current_size(bytes_transferred); - do_read_websocket_head(ws_.left_header_len()); - } - else { - req_.call_event(data_proc_state::data_error); - close(); - } - }); - } - - void do_read_websocket_data(size_t length) { - auto self = this->shared_from_this(); - boost::asio::async_read(socket(), boost::asio::buffer(req_.buffer(), length), - [this, self](const boost::system::error_code& ec, size_t bytes_transferred) { - if (ec) { - req_.call_event(data_proc_state::data_error); - close(); - return; - } - - if (req_.body_finished()) { - req_.set_current_size(0); - bytes_transferred = ws_.payload_length(); - } - - std::string payload; - ws_frame_type ret = ws_.parse_payload(req_.buffer(), bytes_transferred, payload); - if (ret == ws_frame_type::WS_INCOMPLETE_FRAME) { - req_.update_size(bytes_transferred); - req_.reduce_left_body_size(bytes_transferred); - do_read_websocket_data(req_.left_body_len()); - return; - } - - if (ret == ws_frame_type::WS_INCOMPLETE_TEXT_FRAME || ret == ws_frame_type::WS_INCOMPLETE_BINARY_FRAME) { - last_ws_str_.append(std::move(payload)); - } - - if (!handle_ws_frame(ret, std::move(payload), bytes_transferred)) - return; - - req_.set_current_size(0); - do_read_websocket_head(SHORT_HEADER); - }); - } - - bool handle_ws_frame(ws_frame_type ret, std::string&& payload, size_t bytes_transferred) { - switch (ret) - { - case cinatra::ws_frame_type::WS_ERROR_FRAME: - req_.call_event(data_proc_state::data_error); - close(); - return false; - case cinatra::ws_frame_type::WS_OPENING_FRAME: - break; - case cinatra::ws_frame_type::WS_TEXT_FRAME: - case cinatra::ws_frame_type::WS_BINARY_FRAME: - { - reset_timer(); - std::string temp; - if (!last_ws_str_.empty()) { - temp = std::move(last_ws_str_); - } - temp.append(std::move(payload)); - req_.set_part_data(temp); - req_.call_event(data_proc_state::data_continue); - } - //on message - break; - case cinatra::ws_frame_type::WS_CLOSE_FRAME: - { - close_frame close_frame = ws_.parse_close_payload(payload.data(), payload.length()); - const int MAX_CLOSE_PAYLOAD = 123; - size_t len = std::min(MAX_CLOSE_PAYLOAD, payload.length()); - req_.set_part_data({ close_frame.message, len }); - req_.call_event(data_proc_state::data_close); - - std::string close_msg = ws_.format_close_payload(opcode::close, close_frame.message, len); - auto header = ws_.format_header(close_msg.length(), opcode::close); - send_msg(std::move(header), std::move(close_msg)); - } - break; - case cinatra::ws_frame_type::WS_PING_FRAME: - { - auto header = ws_.format_header(payload.length(), opcode::pong); - send_msg(std::move(header), std::move(payload)); - } - break; - case cinatra::ws_frame_type::WS_PONG_FRAME: - ws_ping(); - break; - default: - break; - } - - return true; - } - - void ws_ping() { - timer_.expires_from_now(std::chrono::seconds(60)); - timer_.async_wait([self = this->shared_from_this()](boost::system::error_code const& ec) { - if (ec) { - self->close(false); - return; - } - - self->send_ws_msg("ping", opcode::ping); - }); - } - //-------------web socket----------------// - - //-------------chunked(read chunked not support yet, write chunked is ok)----------------------// - void handle_chunked(size_t bytes_transferred) { - int ret = req_.parse_chunked(bytes_transferred); - if (ret == parse_status::has_error) { - response_back(status_type::internal_server_error, "not support yet"); - return; - } - } - - void handle_chunked_header(const boost::system::error_code& ec) { - if (ec) { - return; - } - - req_.set_state(data_proc_state::data_continue); - call_back();//app set the data - } - //-------------chunked(read chunked not support yet, write chunked is ok)----------------------// - - void handle_body() { - if (req_.at_capacity()) { - response_back(status_type::bad_request, "The body is too long, limitation is 3M"); - return; - } - - bool r = handle_gzip(); - if (!r) { - response_back(status_type::bad_request, "gzip uncompress error"); - return; - } - - if (req_.get_content_type() == content_type::multipart) { - bool has_error = parse_multipart(req_.header_len(), req_.current_size() - req_.header_len()); - if (has_error) { - response_back(status_type::bad_request, "mutipart error"); - return; - } - do_write(); - return; - } - - if (req_.body_len()>0&&!req_.check_request()) { - response_back(status_type::bad_request, "request check error"); - return; + if (has_closed_) { + return; + } + + req_.close_upload_file(); + shutdown(); + boost::system::error_code ec; + socket_.close(ec); + has_closed_ = true; + has_shake_ = false; + } + + /****************** begin handle http body data *****************/ + void handle_string_body(std::size_t bytes_transferred) { + // defalt add limitation for string_body and else. you can remove the limitation for very big string. + if (req_.at_capacity()) { + response_back(status_type::bad_request, + "The request is too long, limitation is 3M"); + return; + } + + if (req_.has_recieved_all()) { + handle_body(); + } else { + req_.expand_size(); + assert(req_.current_size() >= req_.header_len()); + size_t part_size = req_.current_size() - req_.header_len(); + if (part_size == -1) { + response_back(status_type::internal_server_error); + return; + } + req_.reduce_left_body_size(part_size); + do_read_body(); + } + } + + //-------------octet-stream----------------// + void handle_octet_stream(size_t bytes_transferred) { + // call_back(); + try { + auto tp = std::chrono::high_resolution_clock::now(); + auto nano = tp.time_since_epoch().count(); + std::string name = static_dir_ + "/" + std::to_string(nano); + req_.open_upload_file(name); + } catch (const std::exception &ex) { + response_back(status_type::internal_server_error, ex.what()); + return; + } + + req_.set_state(data_proc_state::data_continue); // data + size_t part_size = bytes_transferred - req_.header_len(); + if (part_size != 0) { + req_.reduce_left_body_size(part_size); + req_.set_part_data({req_.current_part(), part_size}); + req_.write_upload_data(req_.current_part(), part_size); + // call_back(); + } + + if (req_.has_recieved_all()) { + // on finish + req_.set_state(data_proc_state::data_end); + call_back(); + do_write(); + } else { + req_.fit_size(); + req_.set_current_size(0); + do_read_octet_stream_body(); + } + } + + void do_read_octet_stream_body() { + auto self = this->shared_from_this(); + boost::asio::async_read( + socket(), boost::asio::buffer(req_.buffer(), req_.left_body_len()), + [this, self](const boost::system::error_code &ec, + size_t bytes_transferred) { + if (ec) { + req_.set_state(data_proc_state::data_error); + call_back(); + close(); + return; + } + + req_.set_part_data({req_.buffer(), bytes_transferred}); + req_.write_upload_data(req_.buffer(), bytes_transferred); + // call_back(); + + req_.reduce_left_body_size(bytes_transferred); + + if (req_.body_finished()) { + req_.set_state(data_proc_state::data_end); + call_back(); + do_write(); + } else { + do_read_octet_stream_body(); + } + }); + } + + //-------------octet-stream----------------// + + //-------------form urlencoded----------------// + // TODO: here later will refactor the duplicate code + void handle_form_urlencoded(size_t bytes_transferred) { + if (req_.at_capacity()) { + response_back(status_type::bad_request, + "The request is too long, limitation is 3M"); + return; + } + + if (req_.has_recieved_all()) { + handle_url_urlencoded_body(); + } else { + req_.expand_size(); + size_t part_size = bytes_transferred - req_.header_len(); + req_.reduce_left_body_size(part_size); + // req_.fit_size(); + do_read_form_urlencoded(); + } + } + + void handle_url_urlencoded_body() { + bool success = req_.parse_form_urlencoded(); + + if (!success) { + response_back(status_type::bad_request, "form urlencoded error"); + return; + } + if (req_.body_len() > 0 && !req_.check_request()) { + response_back(status_type::bad_request, "request check error"); + return; + } + + call_back(); + if (!res_.need_delay()) + do_write(); + } + + void do_read_form_urlencoded() { + reset_timer(); + + auto self = this->shared_from_this(); + boost::asio::async_read( + socket(), boost::asio::buffer(req_.buffer(), req_.left_body_len()), + [this, self](const boost::system::error_code &ec, + size_t bytes_transferred) { + if (ec) { + // LOG_WARN << ec.message(); + close(); + return; + } + + req_.update_size(bytes_transferred); + req_.reduce_left_body_size(bytes_transferred); + + if (req_.body_finished()) { + handle_url_urlencoded_body(); + } else { + do_read_form_urlencoded(); + } + }); + } + //-------------form urlencoded----------------// + + void call_back() { + assert(http_handler_); + http_handler_(req_, res_); + } + + void call_back_data() { + req_.set_state(data_proc_state::data_continue); + call_back(); + req_.set_part_data({}); + } + //-------------multipart----------------------// + void init_multipart_parser() { + multipart_parser_.on_part_begin = [this](const multipart_headers &headers) { + req_.set_multipart_headers(headers); + auto filename = req_.get_multipart_field_name("filename"); + is_multi_part_file_ = req_.is_multipart_file(); + if (filename.empty() && is_multi_part_file_) { + req_.set_state(data_proc_state::data_error); + res_.set_status_and_content(status_type::bad_request, "mutipart error"); + return; + } + if (is_multi_part_file_) { + auto ext = get_extension(filename); + try { + auto tp = std::chrono::high_resolution_clock::now(); + auto nano = tp.time_since_epoch().count(); + std::string name = static_dir_ + "/" + std::to_string(nano) + + std::string(ext.data(), ext.length()) + "_ing"; + if (multipart_begin_) { + multipart_begin_(req_, name); + name = static_dir_ + "/" + name; + } + + req_.open_upload_file(name); + } catch (const std::exception &ex) { + req_.set_state(data_proc_state::data_error); + res_.set_status_and_content(status_type::internal_server_error, + ex.what()); + return; + } + } else { + auto key = req_.get_multipart_field_name("name"); + req_.save_multipart_key_value(std::string(key.data(), key.size()), ""); + } + }; + multipart_parser_.on_part_data = [this](const char *buf, size_t size) { + if (req_.get_state() == data_proc_state::data_error) { + return; + } + if (is_multi_part_file_) { + req_.write_upload_data(buf, size); + } else { + auto key = req_.get_multipart_field_name("name"); + req_.update_multipart_value(std::move(key), buf, size); + } + }; + multipart_parser_.on_part_end = [this] { + if (req_.get_state() == data_proc_state::data_error) + return; + if (is_multi_part_file_) { + req_.close_upload_file(); + auto pfile = req_.get_file(); + if (pfile) { + auto old_name = pfile->get_file_path(); + auto pos = old_name.rfind("_ing"); + if (pos != std::string::npos) { + pfile->rename_file(old_name.substr(0, old_name.length() - 4)); + } + } + } + }; + multipart_parser_.on_end = [this] { + if (req_.get_state() == data_proc_state::data_error) + return; + req_.handle_multipart_key_value(); + // call_back(); + }; + } + + bool parse_multipart(size_t size, std::size_t length) { + if (length == 0) + return false; + + req_.set_part_data(std::string_view(req_.buffer(size), length)); + std::string_view multipart_body = req_.get_part_data(); + size_t bufsize = multipart_body.length(); + + size_t fed = 0; + do { + size_t ret = multipart_parser_.feed(multipart_body.data() + fed, + multipart_body.length() - fed); + fed += ret; + } while (fed < bufsize && !multipart_parser_.stopped()); + + if (multipart_parser_.has_error()) { + // LOG_WARN << multipart_parser_.get_error_message(); + req_.set_state(data_proc_state::data_error); + return true; + } + + req_.reduce_left_body_size(length); + return false; + } + + void handle_multipart() { + if (upload_check_) { + bool r = (*upload_check_)(req_, res_); + if (!r) { + close(); + return; + } + } + + bool has_error = parse_multipart(req_.header_len(), + req_.current_size() - req_.header_len()); + + if (has_error) { + response_back(status_type::bad_request, "mutipart error"); + return; + } + + if (req_.has_recieved_all_part()) { + call_back(); + do_write(); + } else { + req_.set_current_size(0); + do_read_multipart(); + } + } + + void do_read_multipart() { + reset_timer(); + + req_.fit_size(); + auto self = this->shared_from_this(); + boost::asio::async_read( + socket(), boost::asio::buffer(req_.buffer(), req_.left_body_len()), + [self, this](boost::system::error_code ec, std::size_t length) { + if (ec) { + req_.set_state(data_proc_state::data_error); + call_back(); + response_back(status_type::bad_request, "mutipart error"); + return; + } + + bool has_error = parse_multipart(0, length); + + if (has_error) { // parse error + keep_alive_ = false; + response_back(status_type::bad_request, "mutipart error"); + return; + } + + reset_timer(); + if (req_.body_finished()) { + call_back(); + do_write(); + return; + } + + req_.set_current_size(0); + do_read_part_data(); + }); + } + + void do_read_part_data() { + auto self = this->shared_from_this(); + boost::asio::async_read( + socket(), boost::asio::buffer(req_.buffer(), req_.left_body_size()), + [self, this](boost::system::error_code ec, std::size_t length) { + if (ec) { + req_.set_state(data_proc_state::data_error); + call_back(); + return; + } + + bool has_error = parse_multipart(0, length); + + if (has_error) { + response_back(status_type::bad_request, "mutipart error"); + return; + } + + reset_timer(); + if (!req_.body_finished()) { + do_read_part_data(); + } else { + // response_back(status_type::ok, "multipart finished"); + call_back(); + do_write(); + } + }); + } + //-------------multipart----------------------// + + void handle_header_request() { + if (is_upgrade_) { // websocket + req_.set_http_type(content_type::websocket); + // timer_.cancel(); + ws_.upgrade_to_websocket(req_, res_); + response_handshake(); + return; + } + + bool r = handle_gzip(); + if (!r) { + response_back(status_type::bad_request, "gzip uncompress error"); + return; + } + + call_back(); + + if (req_.get_content_type() == content_type::chunked) + return; + + if (req_.get_state() == data_proc_state::data_error) { + return; + } + + if (!res_.need_delay()) + do_write(); + } + + //-------------web socket----------------// + void response_handshake() { + std::vector buffers = res_.to_buffers(); + if (buffers.empty()) { + close(); + return; + } + + auto self = this->shared_from_this(); + boost::asio::async_write( + socket(), buffers, + [this, self](const boost::system::error_code &ec, std::size_t length) { + if (ec) { + close(); + return; + } + + req_.set_state(data_proc_state::data_begin); + call_back(); + req_.call_event(req_.get_state()); + + req_.set_current_size(0); + do_read_websocket_head(SHORT_HEADER); + }); + } + + void do_read_websocket_head(size_t length) { + auto self = this->shared_from_this(); + boost::asio::async_read( + socket(), boost::asio::buffer(req_.buffer(), length), + [this, self](const boost::system::error_code &ec, + size_t bytes_transferred) { + if (ec) { + cancel_timer(); + req_.call_event(data_proc_state::data_error); + + close(); + return; + } + + size_t length = bytes_transferred + req_.current_size(); + req_.set_current_size(0); + int ret = ws_.parse_header(req_.buffer(), length); + + if (ret == parse_status::complete) { + // read payload + auto payload_length = ws_.payload_length(); + req_.set_body_len(payload_length); + if (req_.at_capacity(payload_length)) { + req_.call_event(data_proc_state::data_error); + close(); + return; } - call_back(); - - if (!res_.need_delay()) - do_write(); - } - - bool handle_gzip() { - if (req_.has_gzip()) { - return req_.uncompress(); - } - - return true; - } - - void response_back(status_type status, std::string&& content) { - res_.set_status_and_content(status, std::move(content)); - do_write(); //response to client - } - - void response_back(status_type status) { - res_.set_status_and_content(status); - do_write(); //response to client - } - - enum parse_status { - complete = 0, - has_error = -1, - not_complete = -2, - }; - - void check_keep_alive() { - auto req_conn_hdr = req_.get_header_value("connection"); - if (req_.is_http11()) { - keep_alive_ = req_conn_hdr.empty() || !iequal(req_conn_hdr.data(), req_conn_hdr.size(), "close"); - } - else { - keep_alive_ = !req_conn_hdr.empty() && iequal(req_conn_hdr.data(), req_conn_hdr.size(), "keep-alive"); - } - - if (keep_alive_) { - is_upgrade_ = ws_.is_upgrade(req_); - } - } - - void shutdown_send() { - boost::system::error_code ignored_ec; - socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_send, ignored_ec); - } - - void shutdown() { - boost::system::error_code ignored_ec; - socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); - } - - //-----------------send message----------------// - void send_msg(std::string&& data) { - std::lock_guard lock(buffers_mtx_); - buffers_[active_buffer_ ^ 1].push_back(std::move(data)); // move input data to the inactive buffer - if (!writing()) - do_write_msg(); - } - - void send_msg(std::string&& header, std::string&& data) { - std::lock_guard lock(buffers_mtx_); - buffers_[active_buffer_ ^ 1].push_back(std::move(header)); - buffers_[active_buffer_ ^ 1].push_back(std::move(data)); // move input data to the inactive buffer - if (!writing()) - do_write_msg(); - } - - void do_write_msg() { - active_buffer_ ^= 1; // switch buffers - for (const auto& data : buffers_[active_buffer_]) { - buffer_seq_.push_back(boost::asio::buffer(data)); - } - - boost::asio::async_write(socket(), buffer_seq_, [this, self = this->shared_from_this()](const boost::system::error_code& ec, size_t bytes_transferred) { - std::lock_guard lock(buffers_mtx_); - buffers_[active_buffer_].clear(); - buffer_seq_.clear(); - - if (!ec) { - if (send_ok_cb_) - send_ok_cb_(); - if (!buffers_[active_buffer_ ^ 1].empty()) // have more work - do_write_msg(); - } - else { - if (send_failed_cb_) - send_failed_cb_(ec); - req_.set_state(data_proc_state::data_error); - call_back(); - close(); - } - }); - } - - bool writing() const { return !buffer_seq_.empty(); } - - template - void set_callback(F1&& f1, F2&& f2) { - send_ok_cb_ = std::move(f1); - send_failed_cb_ = std::move(f2); - } - - static constexpr bool is_ssl_ = std::is_same_v; - - //-----------------send message----------------// - boost::asio::ip::tcp::socket socket_; + req_.set_current_size(0); + req_.fit_size(); + do_read_websocket_data(req_.left_body_len()); + } else if (ret == parse_status::not_complete) { + req_.set_current_size(bytes_transferred); + do_read_websocket_head(ws_.left_header_len()); + } else { + req_.call_event(data_proc_state::data_error); + close(); + } + }); + } + + void do_read_websocket_data(size_t length) { + auto self = this->shared_from_this(); + boost::asio::async_read( + socket(), boost::asio::buffer(req_.buffer(), length), + [this, self](const boost::system::error_code &ec, + size_t bytes_transferred) { + if (ec) { + req_.call_event(data_proc_state::data_error); + close(); + return; + } + + if (req_.body_finished()) { + req_.set_current_size(0); + bytes_transferred = ws_.payload_length(); + } + + std::string payload; + ws_frame_type ret = + ws_.parse_payload(req_.buffer(), bytes_transferred, payload); + if (ret == ws_frame_type::WS_INCOMPLETE_FRAME) { + req_.update_size(bytes_transferred); + req_.reduce_left_body_size(bytes_transferred); + do_read_websocket_data(req_.left_body_len()); + return; + } + + if (ret == ws_frame_type::WS_INCOMPLETE_TEXT_FRAME || + ret == ws_frame_type::WS_INCOMPLETE_BINARY_FRAME) { + last_ws_str_.append(std::move(payload)); + } + + if (!handle_ws_frame(ret, std::move(payload), bytes_transferred)) + return; + + req_.set_current_size(0); + do_read_websocket_head(SHORT_HEADER); + }); + } + + bool handle_ws_frame(ws_frame_type ret, std::string &&payload, + size_t bytes_transferred) { + switch (ret) { + case cinatra::ws_frame_type::WS_ERROR_FRAME: + req_.call_event(data_proc_state::data_error); + close(); + return false; + case cinatra::ws_frame_type::WS_OPENING_FRAME: + break; + case cinatra::ws_frame_type::WS_TEXT_FRAME: + case cinatra::ws_frame_type::WS_BINARY_FRAME: { + reset_timer(); + std::string temp; + if (!last_ws_str_.empty()) { + temp = std::move(last_ws_str_); + } + temp.append(std::move(payload)); + req_.set_part_data(temp); + req_.call_event(data_proc_state::data_continue); + } + // on message + break; + case cinatra::ws_frame_type::WS_CLOSE_FRAME: { + close_frame close_frame = + ws_.parse_close_payload(payload.data(), payload.length()); + const int MAX_CLOSE_PAYLOAD = 123; + size_t len = std::min(MAX_CLOSE_PAYLOAD, payload.length()); + req_.set_part_data({close_frame.message, len}); + req_.call_event(data_proc_state::data_close); + + std::string close_msg = + ws_.format_close_payload(opcode::close, close_frame.message, len); + auto header = ws_.format_header(close_msg.length(), opcode::close); + send_msg(std::move(header), std::move(close_msg)); + } break; + case cinatra::ws_frame_type::WS_PING_FRAME: { + auto header = ws_.format_header(payload.length(), opcode::pong); + send_msg(std::move(header), std::move(payload)); + } break; + case cinatra::ws_frame_type::WS_PONG_FRAME: + ws_ping(); + break; + default: + break; + } + + return true; + } + + void ws_ping() { + timer_.expires_from_now(std::chrono::seconds(60)); + timer_.async_wait( + [self = this->shared_from_this()](boost::system::error_code const &ec) { + if (ec) { + self->close(false); + return; + } + + self->send_ws_msg("ping", opcode::ping); + }); + } + //-------------web socket----------------// + + //-------------chunked(read chunked not support yet, write chunked is ok)----------------------// + void handle_chunked(size_t bytes_transferred) { + int ret = req_.parse_chunked(bytes_transferred); + if (ret == parse_status::has_error) { + response_back(status_type::internal_server_error, "not support yet"); + return; + } + } + + void handle_chunked_header(const boost::system::error_code &ec) { + if (ec) { + return; + } + + req_.set_state(data_proc_state::data_continue); + call_back(); // app set the data + } + //-------------chunked(read chunked not support yet, write chunked is ok)----------------------// + + void handle_body() { + if (req_.at_capacity()) { + response_back(status_type::bad_request, + "The body is too long, limitation is 3M"); + return; + } + + bool r = handle_gzip(); + if (!r) { + response_back(status_type::bad_request, "gzip uncompress error"); + return; + } + + if (req_.get_content_type() == content_type::multipart) { + bool has_error = parse_multipart(req_.header_len(), + req_.current_size() - req_.header_len()); + if (has_error) { + response_back(status_type::bad_request, "mutipart error"); + return; + } + do_write(); + return; + } + + if (req_.body_len() > 0 && !req_.check_request()) { + response_back(status_type::bad_request, "request check error"); + return; + } + + call_back(); + + if (!res_.need_delay()) + do_write(); + } + + bool handle_gzip() { + if (req_.has_gzip()) { + return req_.uncompress(); + } + + return true; + } + + void response_back(status_type status, std::string &&content) { + res_.set_status_and_content(status, std::move(content)); + do_write(); // response to client + } + + void response_back(status_type status) { + res_.set_status_and_content(status); + do_write(); // response to client + } + + enum parse_status { + complete = 0, + has_error = -1, + not_complete = -2, + }; + + void check_keep_alive() { + auto req_conn_hdr = req_.get_header_value("connection"); + if (req_.is_http11()) { + keep_alive_ = req_conn_hdr.empty() || + !iequal(req_conn_hdr.data(), req_conn_hdr.size(), "close"); + } else { + keep_alive_ = + !req_conn_hdr.empty() && + iequal(req_conn_hdr.data(), req_conn_hdr.size(), "keep-alive"); + } + + if (keep_alive_) { + is_upgrade_ = ws_.is_upgrade(req_); + } + } + + void shutdown_send() { + boost::system::error_code ignored_ec; + socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_send, ignored_ec); + } + + void shutdown() { + boost::system::error_code ignored_ec; + socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); + } + + //-----------------send message----------------// + void send_msg(std::string &&data) { + std::lock_guard lock(buffers_mtx_); + buffers_[active_buffer_ ^ 1].push_back( + std::move(data)); // move input data to the inactive buffer + if (!writing()) + do_write_msg(); + } + + void send_msg(std::string &&header, std::string &&data) { + std::lock_guard lock(buffers_mtx_); + buffers_[active_buffer_ ^ 1].push_back(std::move(header)); + buffers_[active_buffer_ ^ 1].push_back( + std::move(data)); // move input data to the inactive buffer + if (!writing()) + do_write_msg(); + } + + void do_write_msg() { + active_buffer_ ^= 1; // switch buffers + for (const auto &data : buffers_[active_buffer_]) { + buffer_seq_.push_back(boost::asio::buffer(data)); + } + + boost::asio::async_write( + socket(), buffer_seq_, + [this, self = this->shared_from_this()]( + const boost::system::error_code &ec, size_t bytes_transferred) { + std::lock_guard lock(buffers_mtx_); + buffers_[active_buffer_].clear(); + buffer_seq_.clear(); + + if (!ec) { + if (send_ok_cb_) + send_ok_cb_(); + if (!buffers_[active_buffer_ ^ 1].empty()) // have more work + do_write_msg(); + } else { + if (send_failed_cb_) + send_failed_cb_(ec); + req_.set_state(data_proc_state::data_error); + call_back(); + close(); + } + }); + } + + bool writing() const { return !buffer_seq_.empty(); } + + template void set_callback(F1 &&f1, F2 &&f2) { + send_ok_cb_ = std::move(f1); + send_failed_cb_ = std::move(f2); + } + + static constexpr bool is_ssl_ = std::is_same_v; + + //-----------------send message----------------// + boost::asio::ip::tcp::socket socket_; #ifdef CINATRA_ENABLE_SSL - std::unique_ptr> ssl_stream_ = nullptr; + std::unique_ptr> + ssl_stream_ = nullptr; #endif - boost::asio::steady_timer timer_; - bool enable_timeout_ = true; - response res_; - request req_; - websocket ws_; - bool is_upgrade_ = false; - bool keep_alive_ = false; - const std::size_t MAX_REQ_SIZE_; - const long KEEP_ALIVE_TIMEOUT_; - const std::string& static_dir_; - bool has_shake_ = false; - std::atomic_bool has_closed_ = false; - - //for writing message - std::mutex buffers_mtx_; - std::vector buffers_[2]; // a double buffer - std::vector buffer_seq_; - int active_buffer_ = 0; - std::function send_ok_cb_ = nullptr; - std::function send_failed_cb_ = nullptr; - - std::string last_ws_str_; - - std::string chunked_header_; - multipart_reader multipart_parser_; - bool is_multi_part_file_; - //callback handler to application layer - const http_handler& http_handler_; - std::function* upload_check_ = nullptr; - std::any tag_; - std::function multipart_begin_ = nullptr; - - size_t len_ = 0; - size_t last_transfer_ = 0; - }; - - inline constexpr data_proc_state ws_open = data_proc_state::data_begin; - inline constexpr data_proc_state ws_message = data_proc_state::data_continue; - inline constexpr data_proc_state ws_close = data_proc_state::data_close; - inline constexpr data_proc_state ws_error = data_proc_state::data_error; + boost::asio::steady_timer timer_; + bool enable_timeout_ = true; + response res_; + request req_; + websocket ws_; + bool is_upgrade_ = false; + bool keep_alive_ = false; + const std::size_t MAX_REQ_SIZE_; + const long KEEP_ALIVE_TIMEOUT_; + const std::string &static_dir_; + bool has_shake_ = false; + std::atomic_bool has_closed_ = false; + + // for writing message + std::mutex buffers_mtx_; + std::vector buffers_[2]; // a double buffer + std::vector buffer_seq_; + int active_buffer_ = 0; + std::function send_ok_cb_ = nullptr; + std::function send_failed_cb_ = + nullptr; + + std::string last_ws_str_; + + std::string chunked_header_; + multipart_reader multipart_parser_; + bool is_multi_part_file_; + // callback handler to application layer + const http_handler &http_handler_; + std::function *upload_check_ = nullptr; + std::any tag_; + std::function multipart_begin_ = nullptr; + + size_t len_ = 0; + size_t last_transfer_ = 0; +}; + +inline constexpr data_proc_state ws_open = data_proc_state::data_begin; +inline constexpr data_proc_state ws_message = data_proc_state::data_continue; +inline constexpr data_proc_state ws_close = data_proc_state::data_close; +inline constexpr data_proc_state ws_error = data_proc_state::data_error; } diff --git a/include/cinatra/http_server.hpp b/include/cinatra/http_server.hpp index 407777b..4a163b6 100644 --- a/include/cinatra/http_server.hpp +++ b/include/cinatra/http_server.hpp @@ -15,549 +15,544 @@ #include "cookie.hpp" namespace cinatra { - - //cache - template - struct enable_cache { - enable_cache(T t) :value(t) {} - T value; - }; - - template - class http_server_ : private noncopyable { - public: - using type = ScoketType; - template - explicit http_server_(Args&&... args) : io_service_pool_(std::forward(args)...) - { - http_cache::get().set_cache_max_age(86400); - init_conn_callback(); - } - - void enable_http_cache(bool b) { - http_cache::get().enable_cache(b); - } - - void set_ssl_conf(ssl_configure conf) { - ssl_conf_ = std::move(conf); - } - - bool port_in_use(unsigned short port) { - using namespace boost::asio; - using ip::tcp; - - io_service svc; - tcp::acceptor acept(svc); - boost::system::error_code ec; - acept.open(tcp::v4(), ec); -#ifndef _WIN32 - acept.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), ec); -#endif - acept.bind({ tcp::v4(), port }, ec); +// cache +template struct enable_cache { + enable_cache(T t) : value(t) {} + T value; +}; - return ec == error::address_in_use; - } +template +class http_server_ : private noncopyable { +public: + using type = ScoketType; + template + explicit http_server_(Args &&... args) + : io_service_pool_(std::forward(args)...) { + http_cache::get().set_cache_max_age(86400); + init_conn_callback(); + } - //address : - // "0.0.0.0" : ipv4. use 'https://localhost/' to visit - // "::1" : ipv6. use 'https://[::1]/' to visit - // "" : ipv4 & ipv6. - bool listen(std::string_view address, std::string_view port) { - if (port_in_use(atoi(port.data()))) { - return false; - } + void enable_http_cache(bool b) { http_cache::get().enable_cache(b); } - boost::asio::ip::tcp::resolver::query query(address.data(), port.data()); - auto [r, err_msg] = listen(query); - return r; - } + void set_ssl_conf(ssl_configure conf) { ssl_conf_ = std::move(conf); } - bool listen(std::string_view address, std::string_view port, std::string& error_msg) { - if (port_in_use(atoi(port.data()))) { - error_msg = std::string("port: ") + std::string(port) + " is in use!"; - return false; - } + bool port_in_use(unsigned short port) { + using namespace boost::asio; + using ip::tcp; - boost::asio::ip::tcp::resolver::query query(address.data(), port.data()); - auto [r, err_msg] = listen(query); - error_msg = std::move(err_msg); - return r; - } + io_service svc; + tcp::acceptor acept(svc); - //support ipv6 & ipv4 - bool listen(std::string_view port) { - if (port_in_use(atoi(port.data()))) { - return false; - } - - boost::asio::ip::tcp::resolver::query query(port.data()); - auto [r, err_msg] = listen(query); - return r; - } - - std::pair listen(const boost::asio::ip::tcp::resolver::query & query) { - boost::asio::ip::tcp::resolver resolver(io_service_pool_.get_io_service()); - boost::asio::ip::tcp::resolver::iterator endpoints = resolver.resolve(query); - - bool r = false; - std::string err_msg; - for (; endpoints != boost::asio::ip::tcp::resolver::iterator(); ++endpoints) { - boost::asio::ip::tcp::endpoint endpoint = *endpoints; - - auto acceptor = std::make_shared(io_service_pool_.get_io_service()); - acceptor->open(endpoint.protocol()); - acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); - - try { - acceptor->bind(endpoint); - acceptor->listen(); - start_accept(acceptor); - r = true; - } - catch (const std::exception& ex) { - err_msg = ex.what(); + boost::system::error_code ec; + acept.open(tcp::v4(), ec); +#ifndef _WIN32 + acept.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), ec); +#endif + acept.bind({tcp::v4(), port}, ec); + + return ec == error::address_in_use; + } + + // address : + // "0.0.0.0" : ipv4. use 'https://localhost/' to visit + // "::1" : ipv6. use 'https://[::1]/' to visit + // "" : ipv4 & ipv6. + bool listen(std::string_view address, std::string_view port) { + if (port_in_use(atoi(port.data()))) { + return false; + } + + boost::asio::ip::tcp::resolver::query query(address.data(), port.data()); + auto [r, err_msg] = listen(query); + return r; + } + + bool listen(std::string_view address, std::string_view port, + std::string &error_msg) { + if (port_in_use(atoi(port.data()))) { + error_msg = std::string("port: ") + std::string(port) + " is in use!"; + return false; + } + + boost::asio::ip::tcp::resolver::query query(address.data(), port.data()); + auto [r, err_msg] = listen(query); + error_msg = std::move(err_msg); + return r; + } + + // support ipv6 & ipv4 + bool listen(std::string_view port) { + if (port_in_use(atoi(port.data()))) { + return false; + } + + boost::asio::ip::tcp::resolver::query query(port.data()); + auto [r, err_msg] = listen(query); + return r; + } + + std::pair + listen(const boost::asio::ip::tcp::resolver::query &query) { + boost::asio::ip::tcp::resolver resolver(io_service_pool_.get_io_service()); + boost::asio::ip::tcp::resolver::iterator endpoints = + resolver.resolve(query); + + bool r = false; + std::string err_msg; + for (; endpoints != boost::asio::ip::tcp::resolver::iterator(); + ++endpoints) { + boost::asio::ip::tcp::endpoint endpoint = *endpoints; + + auto acceptor = std::make_shared( + io_service_pool_.get_io_service()); + acceptor->open(endpoint.protocol()); + acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); + + try { + acceptor->bind(endpoint); + acceptor->listen(); + start_accept(acceptor); + r = true; + } catch (const std::exception &ex) { + err_msg = ex.what(); #ifdef DEBUG - std::cout << ex.what() << "\n"; + std::cout << ex.what() << "\n"; #endif // DEBUG - } - } - - return { r, std::move(err_msg) }; - } - - void stop() { - io_service_pool_.stop(); - } - - void run() { - init_dir(static_dir_); - init_dir(upload_dir_); - - io_service_pool_.run(); - } - - intptr_t run_one() { - return io_service_pool_.run_one(); - } - - intptr_t poll() { - return io_service_pool_.poll(); - } - - intptr_t poll_one() { - return io_service_pool_.poll_one(); - } - - void set_static_dir(std::string path) { - set_file_dir(std::move(path), static_dir_); - } - - void set_upload_dir(std::string path) { - set_file_dir(std::move(path), upload_dir_); - } - - const std::string& static_dir() const { - return static_dir_; - } - - //xM - void set_max_req_buf_size(std::size_t max_buf_size) { - max_req_buf_size_ = max_buf_size; - } - - void set_keep_alive_timeout(long seconds) { - keep_alive_timeout_ = seconds; - } - - template - bool need_cache(T&& t) { - if constexpr(std::is_same_v>) { - return t.value; - } - else { - return false; - } - } - - //set http handlers - template - void set_http_handler(std::string_view name, Function&& f, AP&&... ap) { - if constexpr(has_type, std::tuple...>>::value) {//for cache - bool b = false; - ((!b&&(b = need_cache(std::forward(ap)))),...); - if (!b) { - http_cache::get().add_skip(name); - }else{ - http_cache::get().add_single_cache(name); - } - auto tp = filter>(std::forward(ap)...); - auto lm = [this, name, f = std::move(f)](auto... ap) { - http_router_.register_handler(name, std::move(f), std::move(ap)...); - }; - std::apply(lm, std::move(tp)); - } - else { - http_router_.register_handler(name, std::forward(f), std::forward(ap)...); - } - } - - void set_res_cache_max_age(std::time_t seconds) - { - static_res_cache_max_age_ = seconds; - } - - std::time_t get_res_cache_max_age() - { - return static_res_cache_max_age_; - } - - void set_cache_max_age(std::time_t seconds) - { - http_cache::get().set_cache_max_age(seconds); - } - - std::time_t get_cache_max_age() - { - return http_cache::get().get_cache_max_age(); - } - - void set_download_check(std::function checker) { - download_check_ = std::move(checker); - } - - //should be called before listen - void set_upload_check(std::function checker) { - upload_check_ = std::move(checker); - } - - void mapping_to_root_path(std::string relate_path) { - relate_paths_.emplace_back("."+std::move(relate_path)); - } - - void set_not_found_handler(std::function not_found) { - not_found_ = std::move(not_found); - } - - void set_multipart_begin(std::function begin) { - multipart_begin_ = std::move(begin); - } - - void set_validate(size_t max_header_len, check_header_cb check_headers) { - max_header_len_ = max_header_len; - check_headers_ = std::move(check_headers); - } - - void enable_timeout(bool enable){ - enable_timeout_ = enable; - } - - void enable_response_time(bool enable) { - need_response_time_ = enable; - } - - void set_transfer_type(transfer_type type) { - transfer_type_ = type; - } - - void on_connection(std::function>)> on_conn) { - on_conn_ = std::move(on_conn); - } - - private: - void start_accept(std::shared_ptr const& acceptor) { - auto new_conn = std::make_shared>( - io_service_pool_.get_io_service(), ssl_conf_, max_req_buf_size_, keep_alive_timeout_, http_handler_, upload_dir_, - upload_check_?&upload_check_ : nullptr - ); - - acceptor->async_accept(new_conn->tcp_socket(), [this, new_conn, acceptor](const boost::system::error_code& e) { - if (!e) { - new_conn->tcp_socket().set_option(boost::asio::ip::tcp::no_delay(true)); - if (multipart_begin_) { - new_conn->set_multipart_begin(multipart_begin_); - } - - new_conn->enable_response_time(need_response_time_); - new_conn->enable_timeout(enable_timeout_); - - if (check_headers_) { - new_conn->set_validate(max_header_len_, check_headers_); - } - - if (!on_conn_) { - new_conn->start(); - } - else { - if (on_conn_(new_conn)) { - new_conn->start(); - } - } - } - else { - //LOG_INFO << "server::handle_accept: " << e.message(); - } - - start_accept(acceptor); - }); - } - - void set_static_res_handler() - { - set_http_handler(STATIC_RESOURCE, [this](request& req, response& res){ - if (download_check_) { - bool r = download_check_(req, res); - if (!r) { - res.set_status_and_content(status_type::bad_request); - return; - } - } - - auto state = req.get_state(); - switch (state) { - case cinatra::data_proc_state::data_begin: - { - std::string relative_file_name = req.get_relative_filename(); - std::string fullpath = static_dir_ + relative_file_name; - - auto mime = req.get_mime(relative_file_name); - auto in = std::make_shared(fullpath, std::ios_base::binary); - if (!in->is_open()) { - if (not_found_) { - not_found_(req, res); - return; - } - res.set_status_and_content(status_type::not_found,""); - return; - } - - auto start = req.get_header_value("cinatra_start_pos"); - if (!start.empty()) { - std::string start_str(start); - int64_t start = (int64_t)atoll(start_str.data()); - std::error_code code; - int64_t file_size = fs::file_size(fullpath, code); - if (start > 0 && !code && file_size >= start) { - in->seekg(start); - } - } - - req.get_conn()->set_tag(in); - - //if(is_small_file(in.get(),req)){ - // send_small_file(res, in.get(), mime); - // return; - //} - - if(transfer_type_== transfer_type::CHUNKED) - write_chunked_header(req, in, mime); - else - write_ranges_header(req, mime, fs::path(relative_file_name).filename().string(), std::to_string(fs::file_size(fullpath))); - } - break; - case cinatra::data_proc_state::data_continue: - { - if (transfer_type_ == transfer_type::CHUNKED) - write_chunked_body(req); - else - write_ranges_data(req); - } - break; - case cinatra::data_proc_state::data_end: - { - auto conn = req.get_conn(); - conn->on_close(); - } - break; - case cinatra::data_proc_state::data_error: - { - //network error - } - break; - } - },enable_cache{false}); - } - - bool is_small_file(std::ifstream* in,request& req) const { - auto file_begin = in->tellg(); - in->seekg(0, std::ios_base::end); - auto file_size = in->tellg(); - in->seekg(file_begin); - req.save_request_static_file_size(file_size); - return file_size <= 5 * 1024 * 1024; - } - - void send_small_file(response& res, std::ifstream* in, std::string_view mime) { - res.add_header("Access-Control-Allow-origin", "*"); - res.add_header("Content-type", std::string(mime.data(), mime.size()) + "; charset=utf8"); - std::stringstream file_buffer; - file_buffer << in->rdbuf(); - if (static_res_cache_max_age_>0) - { - std::string max_age = std::string("max-age=") + std::to_string(static_res_cache_max_age_); - res.add_header("Cache-Control", max_age.data()); - } -#ifdef CINATRA_ENABLE_GZIP - res.set_status_and_content(status_type::ok, file_buffer.str(), res_content_type::none, content_encoding::gzip); -#else - res.set_status_and_content(status_type::ok, file_buffer.str()); -#endif - } - - void write_chunked_header(request& req, std::shared_ptr in, std::string_view mime) { - auto range_header = req.get_header_value("range"); - req.set_range_flag(!range_header.empty()); - req.set_range_start_pos(range_header); - - std::string res_content_header = std::string(mime.data(), mime.size()) + "; charset=utf8"; - res_content_header += std::string("\r\n") + std::string("Access-Control-Allow-origin: *"); - res_content_header += std::string("\r\n") + std::string("Accept-Ranges: bytes"); - if (static_res_cache_max_age_>0) - { - std::string max_age = std::string("max-age=") + std::to_string(static_res_cache_max_age_); - res_content_header += std::string("\r\n") + std::string("Cache-Control: ") + max_age; - } - - if(req.is_range()) - { - std::int64_t file_pos = req.get_range_start_pos(); - in->seekg(file_pos); - auto end_str = std::to_string(req.get_request_static_file_size()); - res_content_header += std::string("\r\n") +std::string("Content-Range: bytes ")+std::to_string(file_pos)+std::string("-")+std::to_string(req.get_request_static_file_size()-1)+std::string("/")+end_str; - } - req.get_conn()->write_chunked_header(std::string_view(res_content_header),req.is_range()); - } - - void write_chunked_body(request& req) { - const size_t len = 3 * 1024 * 1024; - auto str = get_send_data(req, len); - auto read_len = str.size(); - bool eof = (read_len == 0 || read_len != len); - req.get_conn()->write_chunked_data(std::move(str), eof); - } - - void write_ranges_header(request& req, std::string_view mime, std::string filename, std::string file_size) { - std::string header_str = "HTTP/1.1 200 OK\r\nAccess-Control-Allow-origin: *\r\nAccept-Ranges: bytes\r\n"; - header_str.append("Content-Disposition: attachment;filename="); - header_str.append(std::move(filename)).append("\r\n"); - header_str.append("Connection: keep-alive\r\n"); - header_str.append("Content-Type: ").append(mime).append("\r\n"); - header_str.append("Content-Length: "); - header_str.append(file_size).append("\r\n\r\n"); - req.get_conn()->write_ranges_header(std::move(header_str)); - } - - void write_ranges_data(request& req) { - const size_t len = 3 * 1024 * 1024; - auto str = get_send_data(req, len); - auto read_len = str.size(); - bool eof = (read_len == 0 || read_len != len); - req.get_conn()->write_ranges_data(std::move(str), eof); - } - - std::string get_send_data(request& req, const size_t len) { - auto conn = req.get_conn(); - auto in = std::any_cast>(conn->get_tag()); - std::string str; - str.resize(len); - in->read(&str[0], len); - size_t read_len = (size_t)in->gcount(); - if (read_len != len) { - str.resize(read_len); + } + } + + return {r, std::move(err_msg)}; + } + + void stop() { io_service_pool_.stop(); } + + void run() { + init_dir(static_dir_); + init_dir(upload_dir_); + + io_service_pool_.run(); + } + + intptr_t run_one() { return io_service_pool_.run_one(); } + + intptr_t poll() { return io_service_pool_.poll(); } + + intptr_t poll_one() { return io_service_pool_.poll_one(); } + + void set_static_dir(std::string path) { + set_file_dir(std::move(path), static_dir_); + } + + void set_upload_dir(std::string path) { + set_file_dir(std::move(path), upload_dir_); + } + + const std::string &static_dir() const { return static_dir_; } + + // xM + void set_max_req_buf_size(std::size_t max_buf_size) { + max_req_buf_size_ = max_buf_size; + } + + void set_keep_alive_timeout(long seconds) { keep_alive_timeout_ = seconds; } + + template bool need_cache(T &&t) { + if constexpr (std::is_same_v>) { + return t.value; + } else { + return false; + } + } + + // set http handlers + template + void set_http_handler(std::string_view name, Function &&f, AP &&... ap) { + if constexpr (has_type, + std::tuple...>>::value) { // for cache + bool b = false; + ((!b && (b = need_cache(std::forward(ap)))), ...); + if (!b) { + http_cache::get().add_skip(name); + } else { + http_cache::get().add_single_cache(name); + } + auto tp = filter>(std::forward(ap)...); + auto lm = [this, name, f = std::move(f)](auto... ap) { + http_router_.register_handler(name, std::move(f), + std::move(ap)...); + }; + std::apply(lm, std::move(tp)); + } else { + http_router_.register_handler(name, std::forward(f), + std::forward(ap)...); + } + } + + void set_res_cache_max_age(std::time_t seconds) { + static_res_cache_max_age_ = seconds; + } + + std::time_t get_res_cache_max_age() { return static_res_cache_max_age_; } + + void set_cache_max_age(std::time_t seconds) { + http_cache::get().set_cache_max_age(seconds); + } + + std::time_t get_cache_max_age() { + return http_cache::get().get_cache_max_age(); + } + + void + set_download_check(std::function checker) { + download_check_ = std::move(checker); + } + + // should be called before listen + void + set_upload_check(std::function checker) { + upload_check_ = std::move(checker); + } + + void mapping_to_root_path(std::string relate_path) { + relate_paths_.emplace_back("." + std::move(relate_path)); + } + + void set_not_found_handler( + std::function not_found) { + not_found_ = std::move(not_found); + } + + void + set_multipart_begin(std::function begin) { + multipart_begin_ = std::move(begin); + } + + void set_validate(size_t max_header_len, check_header_cb check_headers) { + max_header_len_ = max_header_len; + check_headers_ = std::move(check_headers); + } + + void enable_timeout(bool enable) { enable_timeout_ = enable; } + + void enable_response_time(bool enable) { need_response_time_ = enable; } + + void set_transfer_type(transfer_type type) { transfer_type_ = type; } + + void on_connection( + std::function>)> on_conn) { + on_conn_ = std::move(on_conn); + } + +private: + void start_accept( + std::shared_ptr const &acceptor) { + auto new_conn = std::make_shared>( + io_service_pool_.get_io_service(), ssl_conf_, max_req_buf_size_, + keep_alive_timeout_, http_handler_, upload_dir_, + upload_check_ ? &upload_check_ : nullptr); + + acceptor->async_accept( + new_conn->tcp_socket(), + [this, new_conn, acceptor](const boost::system::error_code &e) { + if (!e) { + new_conn->tcp_socket().set_option( + boost::asio::ip::tcp::no_delay(true)); + if (multipart_begin_) { + new_conn->set_multipart_begin(multipart_begin_); } - return str; - } + new_conn->enable_response_time(need_response_time_); + new_conn->enable_timeout(enable_timeout_); - void init_conn_callback() { - set_static_res_handler(); - http_handler_ = [this](request& req, response& res) { - res.set_headers(req.get_headers()); - try { - bool success = http_router_.route(req.get_method(), req.get_url(), req, res); - if (!success) { - if (not_found_) { - not_found_(req, res); - return; - } - res.set_status_and_content(status_type::bad_request, "the url is not right"); - } - } - catch (const std::exception& ex) { - res.set_status_and_content(status_type::internal_server_error, ex.what()+std::string(" exception in business function")); - } - catch (...) { - res.set_status_and_content(status_type::internal_server_error, "unknown exception in business function"); - } - }; - } - - void set_file_dir(std::string&& path, std::string& dir) { - /* - default: current path + "www"/"upload" - "": current path - "./temp", "temp" : current path + temp - "/temp" : linux path; "C:/temp" : windows path - */ - if (path.empty()) { - dir = fs::current_path().string(); - return; + if (check_headers_) { + new_conn->set_validate(max_header_len_, check_headers_); } - if (path[0] == '/' || (path.length() >= 2 && path[1] == ':')) { - dir = std::move(path); + if (!on_conn_) { + new_conn->start(); + } else { + if (on_conn_(new_conn)) { + new_conn->start(); + } + } + } else { + // LOG_INFO << "server::handle_accept: " << e.message(); + } + + start_accept(acceptor); + }); + } + + void set_static_res_handler() { + set_http_handler( + STATIC_RESOURCE, + [this](request &req, response &res) { + if (download_check_) { + bool r = download_check_(req, res); + if (!r) { + res.set_status_and_content(status_type::bad_request); + return; } - else { - dir = fs::absolute(path).string(); + } + + auto state = req.get_state(); + switch (state) { + case cinatra::data_proc_state::data_begin: { + std::string relative_file_name = req.get_relative_filename(); + std::string fullpath = static_dir_ + relative_file_name; + + auto mime = req.get_mime(relative_file_name); + auto in = std::make_shared(fullpath, + std::ios_base::binary); + if (!in->is_open()) { + if (not_found_) { + not_found_(req, res); + return; + } + res.set_status_and_content(status_type::not_found, ""); + return; } - } - void init_dir(const std::string& dir) { - std::error_code ec; - bool r = fs::exists(dir, ec); - if (ec) { - std::cout << ec.message(); + auto start = req.get_header_value("cinatra_start_pos"); + if (!start.empty()) { + std::string start_str(start); + int64_t start = (int64_t)atoll(start_str.data()); + std::error_code code; + int64_t file_size = fs::file_size(fullpath, code); + if (start > 0 && !code && file_size >= start) { + in->seekg(start); + } } - if (!r) { - fs::create_directories(dir, ec); - if (ec) { - std::cout << ec.message(); - } - } + req.get_conn()->set_tag(in); + + // if(is_small_file(in.get(),req)){ + // send_small_file(res, in.get(), mime); + // return; + //} + + if (transfer_type_ == transfer_type::CHUNKED) + write_chunked_header(req, in, mime); + else + write_ranges_header( + req, mime, fs::path(relative_file_name).filename().string(), + std::to_string(fs::file_size(fullpath))); + } break; + case cinatra::data_proc_state::data_continue: { + if (transfer_type_ == transfer_type::CHUNKED) + write_chunked_body(req); + else + write_ranges_data(req); + } break; + case cinatra::data_proc_state::data_end: { + auto conn = req.get_conn(); + conn->on_close(); + } break; + case cinatra::data_proc_state::data_error: { + // network error + } break; + } + }, + enable_cache{false}); + } + + bool is_small_file(std::ifstream *in, request &req) const { + auto file_begin = in->tellg(); + in->seekg(0, std::ios_base::end); + auto file_size = in->tellg(); + in->seekg(file_begin); + req.save_request_static_file_size(file_size); + return file_size <= 5 * 1024 * 1024; + } + + void send_small_file(response &res, std::ifstream *in, + std::string_view mime) { + res.add_header("Access-Control-Allow-origin", "*"); + res.add_header("Content-type", + std::string(mime.data(), mime.size()) + "; charset=utf8"); + std::stringstream file_buffer; + file_buffer << in->rdbuf(); + if (static_res_cache_max_age_ > 0) { + std::string max_age = + std::string("max-age=") + std::to_string(static_res_cache_max_age_); + res.add_header("Cache-Control", max_age.data()); + } +#ifdef CINATRA_ENABLE_GZIP + res.set_status_and_content(status_type::ok, file_buffer.str(), + res_content_type::none, content_encoding::gzip); +#else + res.set_status_and_content(status_type::ok, file_buffer.str()); +#endif + } + + void write_chunked_header(request &req, std::shared_ptr in, + std::string_view mime) { + auto range_header = req.get_header_value("range"); + req.set_range_flag(!range_header.empty()); + req.set_range_start_pos(range_header); + + std::string res_content_header = + std::string(mime.data(), mime.size()) + "; charset=utf8"; + res_content_header += + std::string("\r\n") + std::string("Access-Control-Allow-origin: *"); + res_content_header += + std::string("\r\n") + std::string("Accept-Ranges: bytes"); + if (static_res_cache_max_age_ > 0) { + std::string max_age = + std::string("max-age=") + std::to_string(static_res_cache_max_age_); + res_content_header += + std::string("\r\n") + std::string("Cache-Control: ") + max_age; + } + + if (req.is_range()) { + std::int64_t file_pos = req.get_range_start_pos(); + in->seekg(file_pos); + auto end_str = std::to_string(req.get_request_static_file_size()); + res_content_header += + std::string("\r\n") + std::string("Content-Range: bytes ") + + std::to_string(file_pos) + std::string("-") + + std::to_string(req.get_request_static_file_size() - 1) + + std::string("/") + end_str; + } + req.get_conn()->write_chunked_header( + std::string_view(res_content_header), req.is_range()); + } + + void write_chunked_body(request &req) { + const size_t len = 3 * 1024 * 1024; + auto str = get_send_data(req, len); + auto read_len = str.size(); + bool eof = (read_len == 0 || read_len != len); + req.get_conn()->write_chunked_data(std::move(str), eof); + } + + void write_ranges_header(request &req, std::string_view mime, + std::string filename, std::string file_size) { + std::string header_str = "HTTP/1.1 200 OK\r\nAccess-Control-Allow-origin: *\r\nAccept-Ranges: bytes\r\n"; + header_str.append("Content-Disposition: attachment;filename="); + header_str.append(std::move(filename)).append("\r\n"); + header_str.append("Connection: keep-alive\r\n"); + header_str.append("Content-Type: ").append(mime).append("\r\n"); + header_str.append("Content-Length: "); + header_str.append(file_size).append("\r\n\r\n"); + req.get_conn()->write_ranges_header(std::move(header_str)); + } + + void write_ranges_data(request &req) { + const size_t len = 3 * 1024 * 1024; + auto str = get_send_data(req, len); + auto read_len = str.size(); + bool eof = (read_len == 0 || read_len != len); + req.get_conn()->write_ranges_data(std::move(str), eof); + } + + std::string get_send_data(request &req, const size_t len) { + auto conn = req.get_conn(); + auto in = std::any_cast>(conn->get_tag()); + std::string str; + str.resize(len); + in->read(&str[0], len); + size_t read_len = (size_t)in->gcount(); + if (read_len != len) { + str.resize(read_len); + } + + return str; + } + + void init_conn_callback() { + set_static_res_handler(); + http_handler_ = [this](request &req, response &res) { + res.set_headers(req.get_headers()); + try { + bool success = + http_router_.route(req.get_method(), req.get_url(), req, res); + if (!success) { + if (not_found_) { + not_found_(req, res); + return; + } + res.set_status_and_content(status_type::bad_request, + "the url is not right"); } - - service_pool_policy io_service_pool_; - - std::size_t max_req_buf_size_ = 3 * 1024 * 1024; //max request buffer size 3M - long keep_alive_timeout_ = 60; //max request timeout 60s - - http_router http_router_; - std::string static_dir_ = fs::absolute("www").string(); //default - std::string upload_dir_ = fs::absolute("www").string(); //default - std::time_t static_res_cache_max_age_ = 0; - - bool enable_timeout_ = true; - http_handler http_handler_ = nullptr; - std::function download_check_; - std::vector relate_paths_; - std::function upload_check_ = nullptr; - - std::function not_found_ = nullptr; - std::function multipart_begin_ = nullptr; - std::function>)> on_conn_ = nullptr; - - size_t max_header_len_; - check_header_cb check_headers_; - - transfer_type transfer_type_ = transfer_type::CHUNKED; - ssl_configure ssl_conf_; - bool need_response_time_ = false; - }; - - template - using http_server_proxy = http_server_; - - using http_server = http_server_proxy; - using http_ssl_server = http_server_proxy; + } catch (const std::exception &ex) { + res.set_status_and_content( + status_type::internal_server_error, + ex.what() + std::string(" exception in business function")); + } catch (...) { + res.set_status_and_content(status_type::internal_server_error, + "unknown exception in business function"); + } + }; + } + + void set_file_dir(std::string &&path, std::string &dir) { + /* + default: current path + "www"/"upload" + "": current path + "./temp", "temp" : current path + temp + "/temp" : linux path; "C:/temp" : windows path + */ + if (path.empty()) { + dir = fs::current_path().string(); + return; + } + + if (path[0] == '/' || (path.length() >= 2 && path[1] == ':')) { + dir = std::move(path); + } else { + dir = fs::absolute(path).string(); + } + } + + void init_dir(const std::string &dir) { + std::error_code ec; + bool r = fs::exists(dir, ec); + if (ec) { + std::cout << ec.message(); + } + + if (!r) { + fs::create_directories(dir, ec); + if (ec) { + std::cout << ec.message(); + } + } + } + + service_pool_policy io_service_pool_; + + std::size_t max_req_buf_size_ = 3 * 1024 * 1024; // max request buffer size 3M + long keep_alive_timeout_ = 60; // max request timeout 60s + + http_router http_router_; + std::string static_dir_ = fs::absolute("www").string(); // default + std::string upload_dir_ = fs::absolute("www").string(); // default + std::time_t static_res_cache_max_age_ = 0; + + bool enable_timeout_ = true; + http_handler http_handler_ = nullptr; + std::function download_check_; + std::vector relate_paths_; + std::function upload_check_ = nullptr; + + std::function not_found_ = nullptr; + std::function multipart_begin_ = nullptr; + std::function>)> on_conn_ = + nullptr; + + size_t max_header_len_; + check_header_cb check_headers_; + + transfer_type transfer_type_ = transfer_type::CHUNKED; + ssl_configure ssl_conf_; + bool need_response_time_ = false; +}; + +template +using http_server_proxy = http_server_; + +using http_server = http_server_proxy; +using http_ssl_server = http_server_proxy; } diff --git a/include/cinatra/request.hpp b/include/cinatra/request.hpp index 7e94ae1..fcbb4e0 100644 --- a/include/cinatra/request.hpp +++ b/include/cinatra/request.hpp @@ -14,948 +14,871 @@ #include "mime_types.hpp" #include "response.hpp" namespace cinatra { - enum class data_proc_state : int8_t { - data_begin, - data_continue, - data_end, - data_all_end, - data_close, - data_error - }; - - class base_connection; - template - class connection; - - using conn_type = std::weak_ptr; - class request; - using check_header_cb = std::function; - - class request { - public: - using event_call_back = std::function; - - request(response& res) : res_(res){ - buf_.resize(1024); - } - - void set_conn(conn_type conn) { - conn_ = std::move(conn); - } - - template - connection* get_raw_conn() { - static_assert(std::is_same_v || std::is_same_v, "invalid socket type, must be SSL or NonSSL"); - if (conn_.expired()) - return nullptr; - - if (auto base_conn = conn_.lock(); base_conn != nullptr) { - return (connection*)(base_conn.get()); - } - else { - return nullptr; - } - } - - template - std::shared_ptr> get_conn() { - static_assert(std::is_same_v || std::is_same_v, "invalid socket type, must be SSL or NonSSL"); - if (conn_.expired()) - return nullptr; - - if (auto base_conn = conn_.lock(); base_conn != nullptr) { - return std::static_pointer_cast>(base_conn); - } - else { - return nullptr; - } - } - - bool is_conn_alive() { - auto base_conn = conn_.lock(); - return base_conn != nullptr; - } - - conn_type get_weak_base_conn() { - return conn_; - } - - int parse_header(std::size_t last_len, size_t start=0) { - using namespace std::string_view_literals; - if(!copy_headers_.empty()) - copy_headers_.clear(); - num_headers_ = sizeof(headers_) / sizeof(headers_[0]); - header_len_ = phr_parse_request(buf_.data(), cur_size_, &method_, - &method_len_, &url_, &url_len_, - &minor_version_, headers_, &num_headers_, last_len); - - if (cur_size_ > max_header_len_) { - return -1; - } - - if (header_len_ <0 ) - return header_len_; - - if (!check_request()) { - return -1; - } - - check_gzip(); - auto header_value = get_header_value("content-length"); - if (header_value.empty()) { - auto transfer_encoding = get_header_value("transfer-encoding"); - if (transfer_encoding == "chunked"sv) { - is_chunked_ = true; - } - - body_len_ = 0; - } - else { - set_body_len(atoll(header_value.data())); - } - - auto cookie = get_header_value("cookie"); - if (!cookie.empty()) { - cookie_str_ = std::string(cookie.data(), cookie.length()); - } - - //parse url and queries - raw_url_ = {url_, url_len_}; - size_t npos = raw_url_.find('/'); - if (npos == std::string_view::npos) - return -1; - - size_t pos = raw_url_.find('?'); - if(pos!=std::string_view::npos){ - queries_ = parse_query(std::string_view{raw_url_}.substr(pos+1, url_len_-pos-1)); - url_len_ = pos; - } - - return header_len_; - } - - bool check_request() { - if (check_headers_) { - return check_headers_(*this); - } +enum class data_proc_state : int8_t { + data_begin, + data_continue, + data_end, + data_all_end, + data_close, + data_error +}; + +class base_connection; +template class connection; + +using conn_type = std::weak_ptr; +class request; +using check_header_cb = std::function; + +class request { +public: + using event_call_back = std::function; + + request(response &res) : res_(res) { buf_.resize(1024); } + + void set_conn(conn_type conn) { conn_ = std::move(conn); } + + template connection *get_raw_conn() { + static_assert(std::is_same_v || + std::is_same_v, + "invalid socket type, must be SSL or NonSSL"); + if (conn_.expired()) + return nullptr; + + if (auto base_conn = conn_.lock(); base_conn != nullptr) { + return (connection *)(base_conn.get()); + } else { + return nullptr; + } + } + + template std::shared_ptr> get_conn() { + static_assert(std::is_same_v || + std::is_same_v, + "invalid socket type, must be SSL or NonSSL"); + if (conn_.expired()) + return nullptr; + + if (auto base_conn = conn_.lock(); base_conn != nullptr) { + return std::static_pointer_cast>(base_conn); + } else { + return nullptr; + } + } + + bool is_conn_alive() { + auto base_conn = conn_.lock(); + return base_conn != nullptr; + } + + conn_type get_weak_base_conn() { return conn_; } + + int parse_header(std::size_t last_len, size_t start = 0) { + using namespace std::string_view_literals; + if (!copy_headers_.empty()) + copy_headers_.clear(); + num_headers_ = sizeof(headers_) / sizeof(headers_[0]); + header_len_ = phr_parse_request( + buf_.data(), cur_size_, &method_, &method_len_, &url_, &url_len_, + &minor_version_, headers_, &num_headers_, last_len); + + if (cur_size_ > max_header_len_) { + return -1; + } + + if (header_len_ < 0) + return header_len_; - return true; - } + if (!check_request()) { + return -1; + } + + check_gzip(); + auto header_value = get_header_value("content-length"); + if (header_value.empty()) { + auto transfer_encoding = get_header_value("transfer-encoding"); + if (transfer_encoding == "chunked"sv) { + is_chunked_ = true; + } + + body_len_ = 0; + } else { + set_body_len(atoll(header_value.data())); + } + + auto cookie = get_header_value("cookie"); + if (!cookie.empty()) { + cookie_str_ = std::string(cookie.data(), cookie.length()); + } + + // parse url and queries + raw_url_ = {url_, url_len_}; + size_t npos = raw_url_.find('/'); + if (npos == std::string_view::npos) + return -1; + + size_t pos = raw_url_.find('?'); + if (pos != std::string_view::npos) { + queries_ = parse_query( + std::string_view{raw_url_}.substr(pos + 1, url_len_ - pos - 1)); + url_len_ = pos; + } - std::string_view raw_url() { - return raw_url_; - } + return header_len_; + } - void set_body_len(size_t len) { - body_len_ = len; - left_body_len_ = body_len_; - } + bool check_request() { + if (check_headers_) { + return check_headers_(*this); + } - size_t total_len() { - return header_len_ + body_len_; - } + return true; + } - size_t header_len() const{ - return header_len_; - } + std::string_view raw_url() { return raw_url_; } - size_t body_len() const { - return body_len_; - } + void set_body_len(size_t len) { + body_len_ = len; + left_body_len_ = body_len_; + } - bool has_recieved_all() { - return (total_len() <= current_size()); - } + size_t total_len() { return header_len_ + body_len_; } - bool has_recieved_all_part() { - return (body_len_ == cur_size_ - header_len_); - } + size_t header_len() const { return header_len_; } - bool at_capacity() { - return (header_len_ + body_len_) > MaxSize; - } + size_t body_len() const { return body_len_; } - bool at_capacity(size_t size) { - return size > MaxSize; - } + bool has_recieved_all() { return (total_len() <= current_size()); } - size_t current_size() const{ - return cur_size_; - } + bool has_recieved_all_part() { + return (body_len_ == cur_size_ - header_len_); + } - size_t left_size() { - return buf_.size() - cur_size_; - } + bool at_capacity() { return (header_len_ + body_len_) > MaxSize; } - bool update_size(size_t size) { - cur_size_ += size; - if (cur_size_ > MaxSize) { - return true; - } + bool at_capacity(size_t size) { return size > MaxSize; } - return false; - } + size_t current_size() const { return cur_size_; } - bool update_and_expand_size(size_t size) { - if (update_size(size)) { //at capacity - return true; - } + size_t left_size() { return buf_.size() - cur_size_; } - if (cur_size_ >= buf_.size()) - resize_double(); + bool update_size(size_t size) { + cur_size_ += size; + if (cur_size_ > MaxSize) { + return true; + } - return false; - } + return false; + } - char* buffer() { - return &buf_[cur_size_]; - } + bool update_and_expand_size(size_t size) { + if (update_size(size)) { // at capacity + return true; + } - const char* data() { - return buf_.data(); - } + if (cur_size_ >= buf_.size()) + resize_double(); - const size_t last_len() const { - return last_len_; - } + return false; + } - std::string_view req_buf() { - return std::string_view(buf_.data() + last_len_, total_len()); - } + char *buffer() { return &buf_[cur_size_]; } - std::string_view head() { - return std::string_view(buf_.data() + last_len_, header_len_); - } + const char *data() { return buf_.data(); } - std::string_view body() { - return std::string_view(buf_.data() + last_len_ + header_len_, body_len_); - } + const size_t last_len() const { return last_len_; } - void set_left_body_size(size_t size) { - left_body_len_ = size; - } + std::string_view req_buf() { + return std::string_view(buf_.data() + last_len_, total_len()); + } - std::string_view body() const{ + std::string_view head() { + return std::string_view(buf_.data() + last_len_, header_len_); + } + + std::string_view body() { + return std::string_view(buf_.data() + last_len_ + header_len_, body_len_); + } + + void set_left_body_size(size_t size) { left_body_len_ = size; } + + std::string_view body() const { #ifdef CINATRA_ENABLE_GZIP - if (has_gzip_&&!gzip_str_.empty()) { - return { gzip_str_.data(), gzip_str_.length() }; - } + if (has_gzip_ && !gzip_str_.empty()) { + return {gzip_str_.data(), gzip_str_.length()}; + } #endif - return std::string_view(&buf_[header_len_], body_len_); - } - - const char* current_part() const { - return &buf_[header_len_]; - } - - const char* buffer(size_t size) const { - return &buf_[size]; - } - - void reset() { - cur_size_ = 0; - for (auto& file : files_) { - file.close(); - } - files_.clear(); - is_chunked_ = false; - state_ = data_proc_state::data_begin; - part_data_ = {}; - utf8_character_params_.clear(); - utf8_character_pathinfo_params_.clear(); - queries_.clear(); - cookie_str_.clear(); - form_url_map_.clear(); - multipart_form_map_.clear(); - is_range_resource_ = false; - range_start_pos_ = 0; - static_resource_file_size_ = 0; - copy_headers_.clear(); - } - - void fit_size() { - auto total = left_body_len_;// total_len(); - auto size = buf_.size(); - if (size == MaxSize) - return; - - if (total < MaxSize) { - if (total > size) - resize(total); - } - else { - resize(MaxSize); - } - } - - //refactor later - void expand_size(){ - auto total = total_len(); - auto size = buf_.size(); - if (size == MaxSize) - return; - - if (total < MaxSize) { - if (total > size) - resize(total); - } - else { - resize(MaxSize); - } - } - - bool has_body() const { - return body_len_ != 0 || is_chunked_; - } - - bool is_http11() { - return minor_version_ == 1; - } - - int minor_version() { - return minor_version_; - } - - size_t left_body_len() const{ - size_t size = buf_.size(); - return left_body_len_ > size ? size : left_body_len_; - } - - bool body_finished() { - return left_body_len_ == 0; - } - - bool is_chunked() const{ - return is_chunked_; - } - - bool has_gzip() const { - return has_gzip_; - } - - void reduce_left_body_size(size_t size) { - left_body_len_ -= size; - } - - size_t left_body_size() { - auto size = buf_.size(); - return left_body_len_ > size ? size : left_body_len_; - } - - void set_current_size(size_t size) { - cur_size_ = size; - if (size == 0) { - copy_method_url_headers(); - } - } - - std::string_view get_header_value(std::string_view key) const { - if (copy_headers_.empty()) { - for (size_t i = 0; i < num_headers_; i++) { - if (iequal(headers_[i].name, headers_[i].name_len, key.data(), key.length())) - return std::string_view(headers_[i].value, headers_[i].value_len); - } - - return {}; - } - - auto it = std::find_if(copy_headers_.begin(), copy_headers_.end(), [key] (auto& pair){ - if (iequal(pair.first.data(), pair.first.size(), key.data())) { - return true; - } - - return false; - }); - - if (it != copy_headers_.end()) { - return (*it).second; - } - - return {}; - } - - std::pair get_headers() { - if(copy_headers_.empty()) - return { headers_ , num_headers_ }; - - num_headers_ = copy_headers_.size(); - for (size_t i = 0; i < num_headers_; i++) { - headers_[i].name = copy_headers_[i].first.data(); - headers_[i].name_len = copy_headers_[i].first.size(); - headers_[i].value = copy_headers_[i].second.data(); - headers_[i].value_len = copy_headers_[i].second.size(); - } - return { headers_ , num_headers_ }; - } - - std::string get_multipart_field_name(const std::string& field_name) const { - if (multipart_headers_.empty()) - return {}; - - auto it = multipart_headers_.begin(); - auto val = it->second; - //auto pos = val.find("name"); - auto pos = val.find(field_name); - if (pos == std::string::npos) { - return {}; - } - - auto start = val.find('"', pos) + 1; - auto end = val.rfind('"'); - if (start == std::string::npos || end == std::string::npos || endsecond.find("filename") != std::string::npos) { - return true; - } - - return false; - } - - return has_content_type|| has_content_disposition; - } - - void set_multipart_headers(const multipart_headers& headers) { - for (auto pair : headers) { - multipart_headers_[std::string(pair.first.data(), pair.first.size())] = std::string(pair.second.data(), pair.second.size()); - } - } - - std::map parse_query(std::string_view str) { - std::map query; - std::string_view key; - std::string_view val; - size_t pos = 0; - size_t length = str.length(); - for (size_t i = 0; i < length; i++) { - char c = str[i]; - if (c == '=') { - key = { &str[pos], i - pos }; - key = trim(key); - pos = i + 1; - } - else if (c == '&') { - val = { &str[pos], i - pos }; - val = trim(val); - pos = i + 1; - //if (is_form_url_encode(key)) { - // auto s = form_urldecode(key); - //} - query.emplace(key, val); - } - } - - if (pos == 0) { - return {}; - } - - if ((length - pos) > 0) { - val = { &str[pos], length - pos }; - val = trim(val); - query.emplace(key, val); - } - else if((length - pos) == 0) { - query.emplace(key, ""); - } - - return query; - } - - bool parse_form_urlencoded() { - form_url_map_.clear(); + return std::string_view(&buf_[header_len_], body_len_); + } + + const char *current_part() const { return &buf_[header_len_]; } + + const char *buffer(size_t size) const { return &buf_[size]; } + + void reset() { + cur_size_ = 0; + for (auto &file : files_) { + file.close(); + } + files_.clear(); + is_chunked_ = false; + state_ = data_proc_state::data_begin; + part_data_ = {}; + utf8_character_params_.clear(); + utf8_character_pathinfo_params_.clear(); + queries_.clear(); + cookie_str_.clear(); + form_url_map_.clear(); + multipart_form_map_.clear(); + is_range_resource_ = false; + range_start_pos_ = 0; + static_resource_file_size_ = 0; + copy_headers_.clear(); + } + + void fit_size() { + auto total = left_body_len_; // total_len(); + auto size = buf_.size(); + if (size == MaxSize) + return; + + if (total < MaxSize) { + if (total > size) + resize(total); + } else { + resize(MaxSize); + } + } + + // refactor later + void expand_size() { + auto total = total_len(); + auto size = buf_.size(); + if (size == MaxSize) + return; + + if (total < MaxSize) { + if (total > size) + resize(total); + } else { + resize(MaxSize); + } + } + + bool has_body() const { return body_len_ != 0 || is_chunked_; } + + bool is_http11() { return minor_version_ == 1; } + + int minor_version() { return minor_version_; } + + size_t left_body_len() const { + size_t size = buf_.size(); + return left_body_len_ > size ? size : left_body_len_; + } + + bool body_finished() { return left_body_len_ == 0; } + + bool is_chunked() const { return is_chunked_; } + + bool has_gzip() const { return has_gzip_; } + + void reduce_left_body_size(size_t size) { left_body_len_ -= size; } + + size_t left_body_size() { + auto size = buf_.size(); + return left_body_len_ > size ? size : left_body_len_; + } + + void set_current_size(size_t size) { + cur_size_ = size; + if (size == 0) { + copy_method_url_headers(); + } + } + + std::string_view get_header_value(std::string_view key) const { + if (copy_headers_.empty()) { + for (size_t i = 0; i < num_headers_; i++) { + if (iequal(headers_[i].name, headers_[i].name_len, key.data(), + key.length())) + return std::string_view(headers_[i].value, headers_[i].value_len); + } + + return {}; + } + + auto it = std::find_if( + copy_headers_.begin(), copy_headers_.end(), [key](auto &pair) { + if (iequal(pair.first.data(), pair.first.size(), key.data())) { + return true; + } + + return false; + }); + + if (it != copy_headers_.end()) { + return (*it).second; + } + + return {}; + } + + std::pair get_headers() { + if (copy_headers_.empty()) + return {headers_, num_headers_}; + + num_headers_ = copy_headers_.size(); + for (size_t i = 0; i < num_headers_; i++) { + headers_[i].name = copy_headers_[i].first.data(); + headers_[i].name_len = copy_headers_[i].first.size(); + headers_[i].value = copy_headers_[i].second.data(); + headers_[i].value_len = copy_headers_[i].second.size(); + } + return {headers_, num_headers_}; + } + + std::string get_multipart_field_name(const std::string &field_name) const { + if (multipart_headers_.empty()) + return {}; + + auto it = multipart_headers_.begin(); + auto val = it->second; + // auto pos = val.find("name"); + auto pos = val.find(field_name); + if (pos == std::string::npos) { + return {}; + } + + auto start = val.find('"', pos) + 1; + auto end = val.rfind('"'); + if (start == std::string::npos || end == std::string::npos || end < start) { + return {}; + } + + auto key_name = val.substr(start, end - start); + return key_name; + } + + void save_multipart_key_value(const std::string &key, + const std::string &value) { + if (!key.empty()) + multipart_form_map_.emplace(key, value); + } + + void update_multipart_value(std::string key, const char *buf, size_t size) { + if (!key.empty()) { + last_multpart_key_ = key; + } else { + key = last_multpart_key_; + } + + auto it = multipart_form_map_.find(key); + if (it != multipart_form_map_.end()) { + multipart_form_map_[key] += std::string(buf, size); + } + } + + std::string get_multipart_value_by_key1(const std::string &key) { + if (!key.empty()) { + return multipart_form_map_[key]; + } + + return {}; + } + + void handle_multipart_key_value() { + if (multipart_form_map_.empty()) { + return; + } + + for (auto &pair : multipart_form_map_) { + form_url_map_.emplace( + std::string_view(pair.first.data(), pair.first.size()), + std::string_view(pair.second.data(), pair.second.size())); + } + } + + bool is_multipart_file() const { + if (multipart_headers_.empty()) { + return false; + } + + bool has_content_type = + (multipart_headers_.find("Content-Type") != multipart_headers_.end()); + auto it = multipart_headers_.find("Content-Disposition"); + bool has_content_disposition = (it != multipart_headers_.end()); + if (has_content_disposition) { + if (it->second.find("filename") != std::string::npos) { + return true; + } + + return false; + } + + return has_content_type || has_content_disposition; + } + + void set_multipart_headers(const multipart_headers &headers) { + for (auto pair : headers) { + multipart_headers_[std::string(pair.first.data(), pair.first.size())] = + std::string(pair.second.data(), pair.second.size()); + } + } + + std::map + parse_query(std::string_view str) { + std::map query; + std::string_view key; + std::string_view val; + size_t pos = 0; + size_t length = str.length(); + for (size_t i = 0; i < length; i++) { + char c = str[i]; + if (c == '=') { + key = {&str[pos], i - pos}; + key = trim(key); + pos = i + 1; + } else if (c == '&') { + val = {&str[pos], i - pos}; + val = trim(val); + pos = i + 1; + // if (is_form_url_encode(key)) { + // auto s = form_urldecode(key); + //} + query.emplace(key, val); + } + } + + if (pos == 0) { + return {}; + } + + if ((length - pos) > 0) { + val = {&str[pos], length - pos}; + val = trim(val); + query.emplace(key, val); + } else if ((length - pos) == 0) { + query.emplace(key, ""); + } + + return query; + } + + bool parse_form_urlencoded() { + form_url_map_.clear(); #ifdef CINATRA_ENABLE_GZIP - if (has_gzip_) { - bool r = uncompress(); - if (!r) - return false; - } + if (has_gzip_) { + bool r = uncompress(); + if (!r) + return false; + } #endif - auto body_str = body(); - form_url_map_ = parse_query(body_str); - if(form_url_map_.empty()) - return false; + auto body_str = body(); + form_url_map_ = parse_query(body_str); + if (form_url_map_.empty()) + return false; + + return true; + } - return true; - } + int parse_chunked(size_t bytes_transferred) { + auto str = + std::string_view(&buf_[header_len_], bytes_transferred - header_len_); - int parse_chunked(size_t bytes_transferred) { - auto str = std::string_view(&buf_[header_len_], bytes_transferred - header_len_); + return -1; + } - return -1; - } + std::string_view get_method() const { + if (method_len_ != 0) + return {method_, method_len_}; - std::string_view get_method() const{ - if (method_len_ != 0) - return { method_ , method_len_ }; + return {method_str_.data(), method_str_.length()}; + } - return { method_str_.data(), method_str_.length() }; - } + std::string_view get_url() const { + if (method_len_ != 0) + return {url_, url_len_}; - std::string_view get_url() const { - if (method_len_ != 0) - return { url_, url_len_ }; + return {url_str_.data(), url_str_.length()}; + } - return { url_str_.data(), url_str_.length() }; - } + std::string_view get_res_path() const { + auto url = get_url(); - std::string_view get_res_path() const { - auto url = get_url(); + return url.substr(1); + } - return url.substr(1); - } + std::string get_relative_filename() const { + auto file_name = get_url(); + if (is_form_url_encode(file_name)) { + return code_utils::get_string_by_urldecode(file_name); + } - std::string get_relative_filename() const { - auto file_name = get_url(); - if (is_form_url_encode(file_name)){ - return code_utils::get_string_by_urldecode(file_name); - } - - return std::string(file_name); - } + return std::string(file_name); + } - std::string get_filename_from_path() const { - auto file_name = get_res_path(); - if (is_form_url_encode(file_name)) { - return code_utils::get_string_by_urldecode(file_name); - } + std::string get_filename_from_path() const { + auto file_name = get_res_path(); + if (is_form_url_encode(file_name)) { + return code_utils::get_string_by_urldecode(file_name); + } - return std::string(file_name.data(), file_name.size()); - } + return std::string(file_name.data(), file_name.size()); + } - std::string_view get_mime(std::string_view filename) const{ - auto extension = get_extension(filename.data()); - auto mime = get_mime_type(extension); - return mime; - } + std::string_view get_mime(std::string_view filename) const { + auto extension = get_extension(filename.data()); + auto mime = get_mime_type(extension); + return mime; + } - std::map get_form_url_map() const{ - return form_url_map_; - } + std::map get_form_url_map() const { + return form_url_map_; + } - void set_state(data_proc_state state) { - state_ = state; - } + void set_state(data_proc_state state) { state_ = state; } - data_proc_state get_state() const{ - return state_; - } + data_proc_state get_state() const { return state_; } - void set_part_data(std::string_view data) { + void set_part_data(std::string_view data) { #ifdef CINATRA_ENABLE_GZIP - if (has_gzip_) { - bool r = uncompress(data); - if (!r) - return; - } + if (has_gzip_) { + bool r = uncompress(data); + if (!r) + return; + } #endif - - part_data_ = data; - } - - std::string_view get_part_data() const{ - if (has_gzip_) { - return { gzip_str_.data(), gzip_str_.length() }; - } - - return part_data_; - } - - void set_http_type(content_type type) { - http_type_ = type; - } - - content_type get_content_type() const { - return http_type_; - } - - const std::map& queries() const{ - return queries_; - } - - std::string_view get_query_value(size_t n) { - auto get_val = [&n](auto& map) { - auto it = map.begin(); - std::advance(it, n); - return it->second; - }; - - if (n >= queries_.size() ) { - if(n >= form_url_map_.size()) - return {}; - - return get_val(form_url_map_); - } - else { - return get_val(queries_); - } - } - - template - T get_query_value(std::string_view key) { - static_assert(std::is_arithmetic_v); - auto val = get_query_value(key); - if (val.empty()) { - throw std::logic_error("empty value"); - } - - if constexpr (std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v) { - int r = std::atoi(val.data()); - if (val[0] != '0' && r == 0) { - throw std::invalid_argument(std::string(val) +": is not an integer"); - } - return r; - } - else if constexpr (std::is_same_v|| std::is_same_v) { - auto r = std::atoll(val.data()); - if (val[0] != '0' && r == 0) { - throw std::invalid_argument(std::string(val) + ": is not an integer"); - } - return r; - } - else if constexpr (std::is_floating_point_v) { - char* end; - auto f = strtof(val.data(), &end); - if (val.back() != *(end-1)) { - throw std::invalid_argument(std::string(val) + ": is not a float"); - } - return f; - } - else { - throw std::invalid_argument("not support the value type"); - } - } - - std::string_view get_query_value(std::string_view key){ - auto url = get_url(); - url = url.length()>1 && url.back()=='/' ? url.substr(0,url.length()-1):url; - std::string map_key = std::string(url.data(),url.size())+std::string(key.data(),key.size()); - auto it = queries_.find(key); - if (it == queries_.end()) { - auto itf = form_url_map_.find(key); - if (itf == form_url_map_.end()) - return {}; - - if(code_utils::is_url_encode(itf->second)) - { - auto ret= utf8_character_params_.emplace(map_key, code_utils::get_string_by_urldecode(itf->second)); - return std::string_view(ret.first->second.data(), ret.first->second.size()); - } - return itf->second; - } - if(code_utils::is_url_encode(it->second)) - { - auto ret = utf8_character_params_.emplace(map_key, code_utils::get_string_by_urldecode(it->second)); - return std::string_view(ret.first->second.data(), ret.first->second.size()); - } - return it->second; - } - - bool uncompress(std::string_view str) { - if (str.empty()) - return false; - - bool r = true; + + part_data_ = data; + } + + std::string_view get_part_data() const { + if (has_gzip_) { + return {gzip_str_.data(), gzip_str_.length()}; + } + + return part_data_; + } + + void set_http_type(content_type type) { http_type_ = type; } + + content_type get_content_type() const { return http_type_; } + + const std::map &queries() const { + return queries_; + } + + std::string_view get_query_value(size_t n) { + auto get_val = [&n](auto &map) { + auto it = map.begin(); + std::advance(it, n); + return it->second; + }; + + if (n >= queries_.size()) { + if (n >= form_url_map_.size()) + return {}; + + return get_val(form_url_map_); + } else { + return get_val(queries_); + } + } + + template T get_query_value(std::string_view key) { + static_assert(std::is_arithmetic_v); + auto val = get_query_value(key); + if (val.empty()) { + throw std::logic_error("empty value"); + } + + if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v) { + int r = std::atoi(val.data()); + if (val[0] != '0' && r == 0) { + throw std::invalid_argument(std::string(val) + ": is not an integer"); + } + return r; + } else if constexpr (std::is_same_v || + std::is_same_v) { + auto r = std::atoll(val.data()); + if (val[0] != '0' && r == 0) { + throw std::invalid_argument(std::string(val) + ": is not an integer"); + } + return r; + } else if constexpr (std::is_floating_point_v) { + char *end; + auto f = strtof(val.data(), &end); + if (val.back() != *(end - 1)) { + throw std::invalid_argument(std::string(val) + ": is not a float"); + } + return f; + } else { + throw std::invalid_argument("not support the value type"); + } + } + + std::string_view get_query_value(std::string_view key) { + auto url = get_url(); + url = url.length() > 1 && url.back() == '/' + ? url.substr(0, url.length() - 1) + : url; + std::string map_key = std::string(url.data(), url.size()) + + std::string(key.data(), key.size()); + auto it = queries_.find(key); + if (it == queries_.end()) { + auto itf = form_url_map_.find(key); + if (itf == form_url_map_.end()) + return {}; + + if (code_utils::is_url_encode(itf->second)) { + auto ret = utf8_character_params_.emplace( + map_key, code_utils::get_string_by_urldecode(itf->second)); + return std::string_view(ret.first->second.data(), + ret.first->second.size()); + } + return itf->second; + } + if (code_utils::is_url_encode(it->second)) { + auto ret = utf8_character_params_.emplace( + map_key, code_utils::get_string_by_urldecode(it->second)); + return std::string_view(ret.first->second.data(), + ret.first->second.size()); + } + return it->second; + } + + bool uncompress(std::string_view str) { + if (str.empty()) + return false; + + bool r = true; #ifdef CINATRA_ENABLE_GZIP - gzip_str_.clear(); - r = gzip_codec::uncompress(str, gzip_str_); + gzip_str_.clear(); + r = gzip_codec::uncompress(str, gzip_str_); #endif - return r; - } + return r; + } - bool uncompress() { - bool r = true; + bool uncompress() { + bool r = true; #ifdef CINATRA_ENABLE_GZIP - gzip_str_.clear(); - r = gzip_codec::uncompress(std::string_view(&buf_[header_len_], body_len_), gzip_str_); + gzip_str_.clear(); + r = gzip_codec::uncompress(std::string_view(&buf_[header_len_], body_len_), + gzip_str_); #endif - return r; - } - - bool open_upload_file(const std::string& filename) { - upload_file file; - bool r = file.open(filename); - if (!r) - return false; - - files_.push_back(std::move(file)); - return true; - } - - void write_upload_data(const char* data, size_t size) { - if (size == 0) - return; - - assert(!files_.empty()); - - files_.back().write(data, size); - } - - void close_upload_file() { - if (files_.empty()) - return; - - files_.back().close(); - } - - const std::vector& get_upload_files() const { - return files_; - } - - upload_file* get_file() { - if(!files_.empty()) - return &files_.back(); - - return nullptr; - } - - std::map get_cookies() const - { - //auto cookies_str = get_header_value("cookie"); - auto cookies = get_cookies_map(cookie_str_); - return cookies; - } - - std::weak_ptr get_session(const std::string& name) - { - auto cookies = get_cookies(); - auto iter = cookies.find(name); - std::weak_ptr ref; - if(iter!=cookies.end()) - { - ref = session_manager::get().get_session(std::string(iter->second.data(), iter->second.length())); - } - res_.set_session(ref); - return ref; - } - - std::weak_ptr get_session() - { - return get_session(CSESSIONID); - } - - void set_range_flag(bool flag) - { - is_range_resource_ = flag; - } - - bool is_range() const - { - return is_range_resource_; - } - - void set_range_start_pos(std::string_view range_header) - { - if(is_range_resource_) - { - auto l_str_pos = range_header.find("="); - auto r_str_pos = range_header.rfind("-"); - auto pos_str = range_header.substr(l_str_pos+1,r_str_pos-l_str_pos-1); - range_start_pos_ = std::atoll(pos_str.data()); - } - } - - std::int64_t get_range_start_pos() const - { - if(is_range_resource_){ - return range_start_pos_; - } - return 0; - } - - void save_request_static_file_size(std::int64_t size) - { - static_resource_file_size_ = size; - } - - std::int64_t get_request_static_file_size() const - { - return static_resource_file_size_; - } - - void on(data_proc_state event_type, event_call_back&& event_call_back) - { - event_call_backs_[(size_t)event_type] = std::move(event_call_back); - } - - void call_event(data_proc_state event_type) { - if(event_call_backs_[(size_t)event_type]) - event_call_backs_[(size_t)event_type](*this); - } - - template - void set_aspect_data(T&&... data) { - (aspect_data_.push_back(std::forward(data)), ...); - } - - void set_aspect_data(std::vector&& data) { - aspect_data_ = std::move(data); - } - - std::vector get_aspect_data() { - return std::move(aspect_data_); - } - - void set_last_len(size_t len) { - last_len_ = len; - } - - void set_validate(size_t max_header_len, check_header_cb check_headers) { - max_header_len_ = max_header_len; - check_headers_ = std::move(check_headers); - } - - private: - void resize_double() { - size_t size = buf_.size(); - resize(2 * size); - } - - void resize(size_t size) { - copy_method_url_headers(); - buf_.resize(size); - } - - void copy_method_url_headers() { - if (method_len_ == 0) - return; - - method_str_ = std::string( method_, method_len_ ); - url_str_ = std::string(url_, url_len_); - method_len_ = 0; - url_len_ = 0; - - auto filename = get_multipart_field_name("filename"); - multipart_headers_.clear(); - if (!filename.empty()) { - copy_headers_.emplace_back("filename", std::move(filename)); - } - - if (header_len_ < 0) - return; - - for (size_t i = 0; i < num_headers_; i++) { - copy_headers_.emplace_back(std::string(headers_[i].name, headers_[i].name_len), - std::string(headers_[i].value, headers_[i].value_len)); - } - } - - void check_gzip() { - auto encoding = get_header_value("content-encoding"); - if (encoding.empty()) { - has_gzip_ = false; - } - else { - auto it = encoding.find("gzip"); - has_gzip_ = (it != std::string_view::npos); - } - } - - constexpr const static size_t MaxSize = 3 * 1024 * 1024; - conn_type conn_; - response& res_; - std::vector buf_; - - size_t num_headers_ = 0; - struct phr_header headers_[32]; - const char *method_ = nullptr; - size_t method_len_ = 0; - const char *url_ = nullptr; - size_t url_len_ = 0; - int minor_version_ = 0; - int header_len_ = 0; - size_t body_len_ = 0; - - std::string raw_url_; - std::string method_str_; - std::string url_str_; - std::string cookie_str_; - std::vector> copy_headers_; - - size_t cur_size_ = 0; - size_t left_body_len_ = 0; - - size_t last_len_ = 0; //for pipeline, last request buffer position - - std::map queries_; - std::map form_url_map_; - std::map multipart_form_map_; - bool has_gzip_ = false; - std::string gzip_str_; - - bool is_chunked_ = false; - - //validate - size_t max_header_len_ = 1024 * 1024; - check_header_cb check_headers_; - - data_proc_state state_ = data_proc_state::data_begin; - std::string_view part_data_; - content_type http_type_ = content_type::unknown; - - std::map multipart_headers_; - std::string last_multpart_key_; - std::vector files_; - std::map utf8_character_params_; - std::map utf8_character_pathinfo_params_; - std::int64_t range_start_pos_ = 0; - bool is_range_resource_ = 0; - std::int64_t static_resource_file_size_ = 0; - std::vector aspect_data_; - std::array event_call_backs_ = {}; - }; + return r; + } + + bool open_upload_file(const std::string &filename) { + upload_file file; + bool r = file.open(filename); + if (!r) + return false; + + files_.push_back(std::move(file)); + return true; + } + + void write_upload_data(const char *data, size_t size) { + if (size == 0) + return; + + assert(!files_.empty()); + + files_.back().write(data, size); + } + + void close_upload_file() { + if (files_.empty()) + return; + + files_.back().close(); + } + + const std::vector &get_upload_files() const { return files_; } + + upload_file *get_file() { + if (!files_.empty()) + return &files_.back(); + + return nullptr; + } + + std::map get_cookies() const { + // auto cookies_str = get_header_value("cookie"); + auto cookies = get_cookies_map(cookie_str_); + return cookies; + } + + std::weak_ptr get_session(const std::string &name) { + auto cookies = get_cookies(); + auto iter = cookies.find(name); + std::weak_ptr ref; + if (iter != cookies.end()) { + ref = session_manager::get().get_session( + std::string(iter->second.data(), iter->second.length())); + } + res_.set_session(ref); + return ref; + } + + std::weak_ptr get_session() { return get_session(CSESSIONID); } + + void set_range_flag(bool flag) { is_range_resource_ = flag; } + + bool is_range() const { return is_range_resource_; } + + void set_range_start_pos(std::string_view range_header) { + if (is_range_resource_) { + auto l_str_pos = range_header.find("="); + auto r_str_pos = range_header.rfind("-"); + auto pos_str = + range_header.substr(l_str_pos + 1, r_str_pos - l_str_pos - 1); + range_start_pos_ = std::atoll(pos_str.data()); + } + } + + std::int64_t get_range_start_pos() const { + if (is_range_resource_) { + return range_start_pos_; + } + return 0; + } + + void save_request_static_file_size(std::int64_t size) { + static_resource_file_size_ = size; + } + + std::int64_t get_request_static_file_size() const { + return static_resource_file_size_; + } + + void on(data_proc_state event_type, event_call_back &&event_call_back) { + event_call_backs_[(size_t)event_type] = std::move(event_call_back); + } + + void call_event(data_proc_state event_type) { + if (event_call_backs_[(size_t)event_type]) + event_call_backs_[(size_t)event_type](*this); + } + + template void set_aspect_data(T &&... data) { + (aspect_data_.push_back(std::forward(data)), ...); + } + + void set_aspect_data(std::vector &&data) { + aspect_data_ = std::move(data); + } + + std::vector get_aspect_data() { return std::move(aspect_data_); } + + void set_last_len(size_t len) { last_len_ = len; } + + void set_validate(size_t max_header_len, check_header_cb check_headers) { + max_header_len_ = max_header_len; + check_headers_ = std::move(check_headers); + } + +private: + void resize_double() { + size_t size = buf_.size(); + resize(2 * size); + } + + void resize(size_t size) { + copy_method_url_headers(); + buf_.resize(size); + } + + void copy_method_url_headers() { + if (method_len_ == 0) + return; + + method_str_ = std::string(method_, method_len_); + url_str_ = std::string(url_, url_len_); + method_len_ = 0; + url_len_ = 0; + + auto filename = get_multipart_field_name("filename"); + multipart_headers_.clear(); + if (!filename.empty()) { + copy_headers_.emplace_back("filename", std::move(filename)); + } + + if (header_len_ < 0) + return; + + for (size_t i = 0; i < num_headers_; i++) { + copy_headers_.emplace_back( + std::string(headers_[i].name, headers_[i].name_len), + std::string(headers_[i].value, headers_[i].value_len)); + } + } + + void check_gzip() { + auto encoding = get_header_value("content-encoding"); + if (encoding.empty()) { + has_gzip_ = false; + } else { + auto it = encoding.find("gzip"); + has_gzip_ = (it != std::string_view::npos); + } + } + + constexpr const static size_t MaxSize = 3 * 1024 * 1024; + conn_type conn_; + response &res_; + std::vector buf_; + + size_t num_headers_ = 0; + struct phr_header headers_[32]; + const char *method_ = nullptr; + size_t method_len_ = 0; + const char *url_ = nullptr; + size_t url_len_ = 0; + int minor_version_ = 0; + int header_len_ = 0; + size_t body_len_ = 0; + + std::string raw_url_; + std::string method_str_; + std::string url_str_; + std::string cookie_str_; + std::vector> copy_headers_; + + size_t cur_size_ = 0; + size_t left_body_len_ = 0; + + size_t last_len_ = 0; // for pipeline, last request buffer position + + std::map queries_; + std::map form_url_map_; + std::map multipart_form_map_; + bool has_gzip_ = false; + std::string gzip_str_; + + bool is_chunked_ = false; + + // validate + size_t max_header_len_ = 1024 * 1024; + check_header_cb check_headers_; + + data_proc_state state_ = data_proc_state::data_begin; + std::string_view part_data_; + content_type http_type_ = content_type::unknown; + + std::map multipart_headers_; + std::string last_multpart_key_; + std::vector files_; + std::map utf8_character_params_; + std::map utf8_character_pathinfo_params_; + std::int64_t range_start_pos_ = 0; + bool is_range_resource_ = 0; + std::int64_t static_resource_file_size_ = 0; + std::vector aspect_data_; + std::array + event_call_backs_ = {}; +}; } diff --git a/include/cinatra/response.hpp b/include/cinatra/response.hpp index 15866ad..8a96668 100644 --- a/include/cinatra/response.hpp +++ b/include/cinatra/response.hpp @@ -16,369 +16,355 @@ #include "session_manager.hpp" #include "http_cache.hpp" namespace cinatra { - class response { - public: - response() { - } - - std::string& response_str(){ - return rep_str_; - } +class response { +public: + response() {} + + std::string &response_str() { return rep_str_; } + + void enable_response_time(bool enable) { + need_response_time_ = enable; + if (need_response_time_) { + char mbstr[50]; + std::time_t tm = std::chrono::system_clock::to_time_t(last_time_); + std::strftime(mbstr, sizeof(mbstr), "%a, %d %b %Y %T GMT", + std::localtime(&tm)); + last_date_str_ = mbstr; + } + } + + template + constexpr auto + set_status_and_content(const char (&content)[N], + content_encoding encoding = content_encoding::none) { + constexpr auto status_str = to_rep_string(status); + constexpr auto type_str = to_content_type_str(content_type); + constexpr auto len_str = num_to_string::value; + + rep_str_.append(status_str) + .append(len_str.data(), len_str.size()) + .append(type_str) + .append(rep_server); + + if (need_response_time_) + append_date_time(); + else + rep_str_.append("\r\n"); + + rep_str_.append(content); + } + + void append_date_time() { + using namespace std::chrono_literals; + + auto t = std::chrono::system_clock::now(); + if (t - last_time_ > 1s) { + char mbstr[50]; + std::time_t tm = std::chrono::system_clock::to_time_t(t); + std::strftime(mbstr, sizeof(mbstr), "%a, %d %b %Y %T GMT", + std::localtime(&tm)); + last_date_str_ = mbstr; + rep_str_.append("Date: ").append(mbstr).append("\r\n\r\n"); + last_time_ = t; + } else { + rep_str_.append("Date: ").append(last_date_str_).append("\r\n\r\n"); + } + } + + void build_response_str() { + rep_str_.append(to_rep_string(status_)); + + // if (keep_alive) { + // rep_str_.append("Connection: keep-alive\r\n"); + // } + // else { + // rep_str_.append("Connection: close\r\n"); + // } + + if (!headers_.empty()) { + for (auto &header : headers_) { + rep_str_.append(header.first) + .append(":") + .append(header.second) + .append("\r\n"); + } + headers_.clear(); + } + + char temp[20] = {}; + itoa_fwd((int)content_.size(), temp); + rep_str_.append("Content-Length: ").append(temp).append("\r\n"); + if (res_type_ != req_content_type::none) { + rep_str_.append(get_content_type(res_type_)); + } + rep_str_.append("Server: cinatra\r\n"); + if (session_ != nullptr && session_->is_need_update()) { + auto cookie_str = session_->get_cookie().to_string(); + rep_str_.append("Set-Cookie: ").append(cookie_str).append("\r\n"); + session_->set_need_update(false); + } + + if (need_response_time_) + append_date_time(); + else + rep_str_.append("\r\n"); + + rep_str_.append(std::move(content_)); + } + + std::vector to_buffers() { + std::vector buffers; + add_header("Host", "cinatra"); + if (session_ != nullptr && session_->is_need_update()) { + auto cookie_str = session_->get_cookie().to_string(); + add_header("Set-Cookie", cookie_str.c_str()); + session_->set_need_update(false); + } + buffers.reserve(headers_.size() * 4 + 5); + buffers.emplace_back(to_buffer(status_)); + for (auto const &h : headers_) { + buffers.emplace_back(boost::asio::buffer(h.first)); + buffers.emplace_back(boost::asio::buffer(name_value_separator)); + buffers.emplace_back(boost::asio::buffer(h.second)); + buffers.emplace_back(boost::asio::buffer(crlf)); + } + + buffers.push_back(boost::asio::buffer(crlf)); + + if (body_type_ == content_type::string) { + buffers.emplace_back( + boost::asio::buffer(content_.data(), content_.size())); + } + + if (http_cache::get().need_cache(raw_url_)) { + cache_data.clear(); + for (auto &buf : buffers) { + cache_data.push_back( + std::string(boost::asio::buffer_cast(buf), + boost::asio::buffer_size(buf))); + } + } + + return buffers; + } + + void add_header(std::string &&key, std::string &&value) { + headers_.emplace_back(std::move(key), std::move(value)); + } + + void clear_headers() { headers_.clear(); } + + void set_status(status_type status) { status_ = status; } + + status_type get_status() const { return status_; } + + void set_delay(bool delay) { delay_ = delay; } + + void set_status_and_content(status_type status) { + status_ = status; + set_content(std::string(to_string(status))); + build_response_str(); + } + + void + set_status_and_content(status_type status, std::string &&content, + req_content_type res_type = req_content_type::none, + content_encoding encoding = content_encoding::none) { + status_ = status; + res_type_ = res_type; - void enable_response_time(bool enable) { - need_response_time_ = enable; - if (need_response_time_) { - char mbstr[50]; - std::time_t tm = std::chrono::system_clock::to_time_t(last_time_); - std::strftime(mbstr, sizeof(mbstr), "%a, %d %b %Y %T GMT", std::localtime(&tm)); - last_date_str_ = mbstr; - } +#ifdef CINATRA_ENABLE_GZIP + if (encoding == content_encoding::gzip) { + std::string encode_str; + bool r = gzip_codec::compress( + std::string_view(content.data(), content.length()), encode_str, true); + if (!r) { + set_status_and_content(status_type::internal_server_error, + "gzip compress error"); + } else { + add_header("Content-Encoding", "gzip"); + set_content(std::move(encode_str)); + } + } else +#endif + set_content(std::move(content)); + build_response_str(); + } + + std::string_view get_content_type(req_content_type type) { + switch (type) { + case cinatra::req_content_type::html: + return rep_html; + case cinatra::req_content_type::json: + return rep_json; + case cinatra::req_content_type::string: + return rep_string; + case cinatra::req_content_type::multipart: + return rep_multipart; + case cinatra::req_content_type::none: + default: + return ""; + } + } + + bool need_delay() const { return delay_; } + + void reset() { + if (headers_.empty()) + rep_str_.clear(); + res_type_ = req_content_type::none; + status_ = status_type::init; + proc_continue_ = true; + delay_ = false; + headers_.clear(); + content_.clear(); + session_ = nullptr; + + if (cache_data.empty()) + cache_data.clear(); + } + + void set_continue(bool con) { proc_continue_ = con; } + + bool need_continue() const { return proc_continue_; } + + void set_content(std::string &&content) { + body_type_ = content_type::string; + content_ = std::move(content); + } + + void set_chunked() { + //"Transfer-Encoding: chunked\r\n" + add_header("Transfer-Encoding", "chunked"); + } + + std::vector + to_chunked_buffers(const char *chunk_data, size_t length, bool eof) { + std::vector buffers; + + if (length > 0) { + // convert bytes transferred count to a hex string. + chunk_size_ = to_hex_string(length); + + // Construct chunk based on rfc2616 section 3.6.1 + buffers.push_back(boost::asio::buffer(chunk_size_)); + buffers.push_back(boost::asio::buffer(crlf)); + buffers.push_back(boost::asio::buffer(chunk_data, length)); + buffers.push_back(boost::asio::buffer(crlf)); + } + + // append last-chunk + if (eof) { + buffers.push_back(boost::asio::buffer(last_chunk)); + buffers.push_back(boost::asio::buffer(crlf)); + } + + return buffers; + } + + std::shared_ptr + start_session(const std::string &name, std::time_t expire = -1, + std::string_view domain = "", const std::string &path = "/") { + session_ = + session_manager::get().create_session(domain, name, expire, path); + return session_; + } + + std::shared_ptr start_session() { + if (domain_.empty()) { + auto host = get_header_value("host"); + if (!host.empty()) { + size_t pos = host.find(':'); + if (pos != std::string_view::npos) { + set_domain(host.substr(0, pos)); } + } + } - template - constexpr auto set_status_and_content(const char(&content)[N], content_encoding encoding = content_encoding::none) { - constexpr auto status_str = to_rep_string(status); - constexpr auto type_str = to_content_type_str(content_type); - constexpr auto len_str = num_to_string::value; - - rep_str_.append(status_str).append(len_str.data(), len_str.size()).append(type_str).append(rep_server); - - if(need_response_time_) - append_date_time(); - else - rep_str_.append("\r\n"); + session_ = session_manager::get().create_session(domain_, CSESSIONID); + return session_; + } - rep_str_.append(content); - } - - void append_date_time() { - using namespace std::chrono_literals; - - auto t = std::chrono::system_clock::now(); - if (t - last_time_ > 1s) { - char mbstr[50]; - std::time_t tm = std::chrono::system_clock::to_time_t(t); - std::strftime(mbstr, sizeof(mbstr), "%a, %d %b %Y %T GMT", std::localtime(&tm)); - last_date_str_ = mbstr; - rep_str_.append("Date: ").append(mbstr).append("\r\n\r\n"); - last_time_ = t; - } - else { - rep_str_.append("Date: ").append(last_date_str_).append("\r\n\r\n"); - } - } + void set_domain(std::string_view domain) { domain_ = domain; } - void build_response_str() { - rep_str_.append(to_rep_string(status_)); - -// if (keep_alive) { -// rep_str_.append("Connection: keep-alive\r\n"); -// } -// else { -// rep_str_.append("Connection: close\r\n"); -// } - - if (!headers_.empty()) { - for (auto& header : headers_) { - rep_str_.append(header.first).append(":").append(header.second).append("\r\n"); - } - headers_.clear(); - } - - char temp[20] = {}; - itoa_fwd((int)content_.size(), temp); - rep_str_.append("Content-Length: ").append(temp).append("\r\n"); - if(res_type_!= req_content_type::none){ - rep_str_.append(get_content_type(res_type_)); - } - rep_str_.append("Server: cinatra\r\n"); - if (session_ != nullptr && session_->is_need_update()) { - auto cookie_str = session_->get_cookie().to_string(); - rep_str_.append("Set-Cookie: ").append(cookie_str).append("\r\n"); - session_->set_need_update(false); - } - - if (need_response_time_) - append_date_time(); - else - rep_str_.append("\r\n"); - - rep_str_.append(std::move(content_)); - } - - std::vector to_buffers() { - std::vector buffers; - add_header("Host", "cinatra"); - if(session_ != nullptr && session_->is_need_update()) - { - auto cookie_str = session_->get_cookie().to_string(); - add_header("Set-Cookie",cookie_str.c_str()); - session_->set_need_update(false); - } - buffers.reserve(headers_.size() * 4 + 5); - buffers.emplace_back(to_buffer(status_)); - for (auto const& h : headers_) { - buffers.emplace_back(boost::asio::buffer(h.first)); - buffers.emplace_back(boost::asio::buffer(name_value_separator)); - buffers.emplace_back(boost::asio::buffer(h.second)); - buffers.emplace_back(boost::asio::buffer(crlf)); - } - - buffers.push_back(boost::asio::buffer(crlf)); - - if (body_type_ == content_type::string) { - buffers.emplace_back(boost::asio::buffer(content_.data(), content_.size())); - } - - if (http_cache::get().need_cache(raw_url_)) { - cache_data.clear(); - for (auto& buf : buffers) { - cache_data.push_back(std::string(boost::asio::buffer_cast(buf),boost::asio::buffer_size(buf))); - } - } - - return buffers; - } - - void add_header(std::string&& key, std::string&& value) { - headers_.emplace_back(std::move(key), std::move(value)); - } - - void clear_headers() { - headers_.clear(); - } + std::string_view get_domain() { return domain_; } - void set_status(status_type status) { - status_ = status; - } + void set_path(std::string_view path) { path_ = path; } - status_type get_status() const { - return status_; - } + std::string_view get_path() { return path_; } - void set_delay(bool delay) { - delay_ = delay; - } + void set_url(std::string_view url) { raw_url_ = url; } - void set_status_and_content(status_type status) { - status_ = status; - set_content(std::string(to_string(status))); - build_response_str(); - } + std::string_view get_url(std::string_view url) { return raw_url_; } - void set_status_and_content(status_type status, std::string&& content, req_content_type res_type = req_content_type::none, content_encoding encoding = content_encoding::none) { - status_ = status; - res_type_ = res_type; + void set_headers(std::pair headers) { + req_headers_ = headers; + } + void render_string(std::string &&content) { #ifdef CINATRA_ENABLE_GZIP - if (encoding == content_encoding::gzip) { - std::string encode_str; - bool r = gzip_codec::compress(std::string_view(content.data(), content.length()), encode_str, true); - if (!r) { - set_status_and_content(status_type::internal_server_error, "gzip compress error"); - } - else { - add_header("Content-Encoding", "gzip"); - set_content(std::move(encode_str)); - } - } - else -#endif - set_content(std::move(content)); - build_response_str(); - } - - std::string_view get_content_type(req_content_type type){ - switch (type) { - case cinatra::req_content_type::html: - return rep_html; - case cinatra::req_content_type::json: - return rep_json; - case cinatra::req_content_type::string: - return rep_string; - case cinatra::req_content_type::multipart: - return rep_multipart; - case cinatra::req_content_type::none: - default: - return ""; - } - } - - bool need_delay() const { - return delay_; - } - - void reset() { - if(headers_.empty()) - rep_str_.clear(); - res_type_ = req_content_type::none; - status_ = status_type::init; - proc_continue_ = true; - delay_ = false; - headers_.clear(); - content_.clear(); - session_ = nullptr; - - if(cache_data.empty()) - cache_data.clear(); - } - - void set_continue(bool con) { - proc_continue_ = con; - } - - bool need_continue() const { - return proc_continue_; - } - - void set_content(std::string&& content) { - body_type_ = content_type::string; - content_ = std::move(content); - } - - void set_chunked() { - //"Transfer-Encoding: chunked\r\n" - add_header("Transfer-Encoding", "chunked"); - } - - std::vector to_chunked_buffers(const char* chunk_data, size_t length, bool eof) { - std::vector buffers; - - if (length > 0) { - // convert bytes transferred count to a hex string. - chunk_size_ = to_hex_string(length); - - // Construct chunk based on rfc2616 section 3.6.1 - buffers.push_back(boost::asio::buffer(chunk_size_)); - buffers.push_back(boost::asio::buffer(crlf)); - buffers.push_back(boost::asio::buffer(chunk_data, length)); - buffers.push_back(boost::asio::buffer(crlf)); - } - - //append last-chunk - if (eof) { - buffers.push_back(boost::asio::buffer(last_chunk)); - buffers.push_back(boost::asio::buffer(crlf)); - } - - return buffers; - } - - std::shared_ptr start_session(const std::string& name, std::time_t expire = -1,std::string_view domain = "", const std::string &path = "/") - { - session_ = session_manager::get().create_session(domain, name, expire, path); - return session_; - } - - std::shared_ptr start_session() - { - if (domain_.empty()) { - auto host = get_header_value("host"); - if (!host.empty()) { - size_t pos = host.find(':'); - if (pos != std::string_view::npos) { - set_domain(host.substr(0, pos)); - } - } - } - - session_ = session_manager::get().create_session(domain_, CSESSIONID); - return session_; - } - - void set_domain(std::string_view domain) { - domain_ = domain; - } - - std::string_view get_domain() { - return domain_; - } - - void set_path(std::string_view path) { - path_ = path; - } - - std::string_view get_path() { - return path_; - } - - void set_url(std::string_view url) - { - raw_url_ = url; - } - - std::string_view get_url(std::string_view url) - { - return raw_url_; - } - - void set_headers(std::pair headers) { - req_headers_ = headers; - } - - void render_string(std::string&& content) - { -#ifdef CINATRA_ENABLE_GZIP - set_status_and_content(status_type::ok,std::move(content),res_content_type::string,content_encoding::gzip); + set_status_and_content(status_type::ok, std::move(content), + res_content_type::string, content_encoding::gzip); #else - set_status_and_content(status_type::ok,std::move(content),req_content_type::string,content_encoding::none); + set_status_and_content(status_type::ok, std::move(content), + req_content_type::string, content_encoding::none); #endif - } - - std::vector raw_content() { - return cache_data; - } - - void redirect(const std::string& url,bool is_forever = false) - { - add_header("Location",url.c_str()); - is_forever==false?set_status_and_content(status_type::moved_temporarily):set_status_and_content(status_type::moved_permanently); - } - - void redirect_post(const std::string& url) { - add_header("Location", url.c_str()); - set_status_and_content(status_type::temporary_redirect); - } - - void set_session(std::weak_ptr sessionref) - { - if(sessionref.lock()){ - session_ = sessionref.lock(); - } - } - - private: - std::string_view get_header_value(std::string_view key) const { - phr_header* headers = req_headers_.first; - size_t num_headers = req_headers_.second; - for (size_t i = 0; i < num_headers; i++) { - if (iequal(headers[i].name, headers[i].name_len, key.data(), key.length())) - return std::string_view(headers[i].value, headers[i].value_len); - } - - return {}; - } - - std::string_view raw_url_; - std::vector> headers_; - std::vector cache_data; - std::string content_; - content_type body_type_ = content_type::unknown; - status_type status_ = status_type::init; - bool proc_continue_ = true; - std::string chunk_size_; - - bool delay_ = false; - - std::pair req_headers_; - std::string_view domain_; - std::string_view path_; - std::shared_ptr session_ = nullptr; - std::string rep_str_; - std::chrono::system_clock::time_point last_time_ = std::chrono::system_clock::now(); - std::string last_date_str_; - req_content_type res_type_; - bool need_response_time_ = false; - }; + } + + std::vector raw_content() { return cache_data; } + + void redirect(const std::string &url, bool is_forever = false) { + add_header("Location", url.c_str()); + is_forever == false + ? set_status_and_content(status_type::moved_temporarily) + : set_status_and_content(status_type::moved_permanently); + } + + void redirect_post(const std::string &url) { + add_header("Location", url.c_str()); + set_status_and_content(status_type::temporary_redirect); + } + + void set_session(std::weak_ptr sessionref) { + if (sessionref.lock()) { + session_ = sessionref.lock(); + } + } + +private: + std::string_view get_header_value(std::string_view key) const { + phr_header *headers = req_headers_.first; + size_t num_headers = req_headers_.second; + for (size_t i = 0; i < num_headers; i++) { + if (iequal(headers[i].name, headers[i].name_len, key.data(), + key.length())) + return std::string_view(headers[i].value, headers[i].value_len); + } + + return {}; + } + + std::string_view raw_url_; + std::vector> headers_; + std::vector cache_data; + std::string content_; + content_type body_type_ = content_type::unknown; + status_type status_ = status_type::init; + bool proc_continue_ = true; + std::string chunk_size_; + + bool delay_ = false; + + std::pair req_headers_; + std::string_view domain_; + std::string_view path_; + std::shared_ptr session_ = nullptr; + std::string rep_str_; + std::chrono::system_clock::time_point last_time_ = + std::chrono::system_clock::now(); + std::string last_date_str_; + req_content_type res_type_; + bool need_response_time_ = false; +}; } #endif //CINATRA_RESPONSE_HPP -- GitLab