├── .gitignore ├── BasicAuth.h ├── Buffer.h ├── CMakeLists.txt ├── CgiHelper.h ├── CgiWrapper.cpp ├── CgiWrapper.h ├── ConnIf.h ├── CryptoUtil.h ├── Dispatcher.cpp ├── Dispatcher.h ├── Executor.cpp ├── Executor.h ├── Global.cpp ├── Global.h ├── HttpConf.h ├── HttpExecutor.cpp ├── HttpExecutor.h ├── HttpHandler.h ├── HttpParser.cpp ├── HttpParser.h ├── HttpProto.cpp ├── HttpProto.h ├── HttpReqInstance.h ├── HttpServer.cpp ├── HttpServer.h ├── LICENSE ├── ManagePage.cpp ├── README.md ├── ServiceIf.h ├── SlibLoader.h ├── TcpConnAsync.cpp ├── TcpConnAsync.h ├── cgi-handler ├── getdemo │ ├── CMakeLists.txt │ └── getdemo.cpp └── postdemo │ ├── CMakeLists.txt │ └── postdemo.cpp ├── examples ├── HttpUtil.h ├── build.sh ├── fast_client.cpp ├── libRoo.a ├── main.cpp └── setup.cpp ├── siege.png └── tzhttpd_example.conf /.gitignore: -------------------------------------------------------------------------------- 1 | _gsdata_/ 2 | *.vpj 3 | *.vpw 4 | *.vpwhist 5 | *.vpwhistu 6 | *.vtg 7 | .DS_Store 8 | include/config.h 9 | -------------------------------------------------------------------------------- /BasicAuth.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_BASIC_AUTH_H__ 9 | #define __TZHTTPD_BASIC_AUTH_H__ 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "CryptoUtil.h" 22 | 23 | namespace tzhttpd { 24 | 25 | // 每个Virtual Host持有一个这个认证结构,主要用户Http BasicAuth鉴权 26 | 27 | typedef std::vector>> BasicAuthContain; 28 | 29 | class BasicAuth { 30 | 31 | public: 32 | BasicAuth() : 33 | basic_auths_(new BasicAuthContain()) { 34 | } 35 | 36 | // strict == true,如果遇到错误的配置将会报错终止解析 37 | bool init(const libconfig::Setting& setting, bool strict = false) { 38 | 39 | if (!setting.exists("basic_auth")) { 40 | roo::log_err("setting does not contain basic_auth part, ignore following configuration."); 41 | return true; 42 | } 43 | 44 | std::shared_ptr basic_auths_load(new BasicAuthContain()); 45 | 46 | const libconfig::Setting& basic_auth = setting["basic_auth"]; 47 | for (int i = 0; i < basic_auth.getLength(); ++i) { 48 | const libconfig::Setting& basic_auths_item = basic_auth[i]; 49 | if (!basic_auths_item.exists("uri") || !basic_auths_item.exists("auth")) { 50 | roo::log_err("required uri and auth does not found, ignore this item."); 51 | continue; 52 | } 53 | 54 | std::string auth_uri_regex; 55 | basic_auths_item.lookupValue("uri", auth_uri_regex); 56 | auth_uri_regex = roo::StrUtil::pure_uri_path(auth_uri_regex); 57 | 58 | std::set auth_set{}; 59 | const libconfig::Setting& auth = basic_auths_item["auth"]; 60 | for (int j = 0; j < auth.getLength(); ++j) { 61 | const libconfig::Setting& auth_acct = auth[j]; 62 | std::string auth_user; 63 | std::string auth_passwd; 64 | 65 | auth_acct.lookupValue("user", auth_user); 66 | auth_acct.lookupValue("passwd", auth_passwd); 67 | 68 | if (auth_user.empty() || auth_passwd.empty()) { 69 | if (strict) { 70 | roo::log_err("basic_auth err account item %s, strict mode will treate as error return.", auth_user.c_str()); 71 | return false; 72 | } else { 73 | roo::log_err("basic_auth skip err account item %s, skip this.", auth_user.c_str()); 74 | continue; 75 | } 76 | } 77 | 78 | std::string auth_str = auth_user + ":" + auth_passwd; 79 | std::string auth_base = CryptoUtil::base64_encode(auth_str); 80 | 81 | auth_set.insert(auth_base); 82 | roo::log_info("basic_auth detected valid item for user %s.", auth_user.c_str()); 83 | } 84 | 85 | if (auth_set.empty()) { 86 | roo::log_warning("empty ruleset for %s, we will allow all access for this uri.", 87 | auth_uri_regex.c_str()); 88 | } 89 | 90 | roo::UriRegex rgx{auth_uri_regex}; 91 | basic_auths_load->push_back({ rgx, auth_set }); 92 | roo::log_info("successfully add %d auth items for uri %s.", 93 | static_cast(auth_set.size()), auth_uri_regex.c_str()); 94 | } 95 | 96 | roo::log_info("total valid auth rules count: %d detected.", 97 | static_cast(basic_auths_load->size())); 98 | 99 | { 100 | // update with new settings here 101 | std::lock_guard lock(lock_); 102 | basic_auths_.swap(basic_auths_load); 103 | } 104 | 105 | return true; 106 | } 107 | 108 | 109 | public: 110 | bool check_auth(const std::string& uri, const std::string& auth_str) { 111 | 112 | std::string auth_code{}; 113 | 114 | // 获取Http Header Auth字段 115 | { 116 | std::vector vec{}; 117 | boost::split(vec, auth_str, boost::is_any_of(" \t\n")); 118 | if (vec.size() == 2 && strcasestr(vec[0].c_str(), "Basic")) { 119 | auth_code = boost::algorithm::trim_copy(vec[1]); 120 | } 121 | } 122 | 123 | std::string pure_uri = roo::StrUtil::pure_uri_path(uri); 124 | 125 | std::shared_ptr auth_rule{}; 126 | { 127 | std::lock_guard lock(lock_); 128 | auth_rule = basic_auths_; 129 | } 130 | 131 | // 在配置文件中按照优先级的顺序向下检索,如果发现请求URI匹配了正则表达式 132 | // 如果检索到了账号,表示授权成功,返回true 133 | // 否则拒绝本次请求,不再尝试后续表达式匹配 134 | std::vector>>::const_iterator it; 135 | boost::smatch what; 136 | for (it = auth_rule->cbegin(); it != auth_rule->cend(); ++it) { 137 | if (boost::regex_match(pure_uri, what, it->first)) { 138 | 139 | // empty auth, we will allow all access 140 | if (it->second.empty()) { 141 | return true; 142 | } 143 | 144 | // normal rule check 145 | if (it->second.find(auth_code) == it->second.end()) { 146 | roo::log_err("reject access to %s with auth_str: %s", uri.c_str(), auth_str.c_str()); 147 | return false; 148 | } else { 149 | return true; 150 | } 151 | } 152 | } 153 | 154 | return true; 155 | } 156 | 157 | private: 158 | std::mutex lock_; 159 | std::shared_ptr basic_auths_; 160 | }; 161 | 162 | 163 | } // end namespace tzhttpd 164 | 165 | 166 | #endif //__TZHTTPD_BASIC_AUTH_H__ 167 | -------------------------------------------------------------------------------- /Buffer.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | 9 | #ifndef __TZHTTPD_BUFFER_H__ 10 | #define __TZHTTPD_BUFFER_H__ 11 | 12 | #include 13 | #include 14 | 15 | namespace tzhttpd { 16 | 17 | class Buffer { 18 | 19 | __noncopyable__(Buffer) 20 | 21 | public: 22 | // 构造函数 23 | 24 | Buffer() : 25 | data_({ }) { 26 | } 27 | 28 | explicit Buffer(const std::string& data) : 29 | data_(data.begin(), data.end()) { 30 | } 31 | 32 | // used internally, user should prefer Message 33 | uint32_t append_internal(const std::string& data) { 34 | std::copy(data.begin(), data.end(), std::back_inserter(data_)); 35 | return static_cast(data_.size()); 36 | } 37 | 38 | // 从队列的开头取出若干个(最多sz)字符,返回实际得到的字符数 39 | bool consume(std::string& store, uint32_t sz) { 40 | 41 | if (sz == 0 || data_.empty()) { 42 | return false; 43 | } 44 | 45 | // 全部取走 46 | if (sz >= data_.size()) { 47 | store = std::string(data_.begin(), data_.end()); 48 | data_.clear(); 49 | return true; 50 | } 51 | 52 | // 部分数据 53 | std::vector remain(data_.begin() + sz, data_.end()); // 剩余的数据 54 | store = std::string(data_.begin(), data_.begin() + sz); // 要取的数据 55 | 56 | data_.swap(remain); 57 | return true; 58 | } 59 | 60 | // 调用者需要保证至少能够容纳 sz 数据,拷贝的同时原始数据不会改动 61 | bool consume(char* store, uint32_t sz) { 62 | 63 | if (!store || sz == 0 || data_.empty()) { 64 | return false; 65 | } 66 | 67 | ::memcpy(store, data_.data(), sz); 68 | 69 | // 之前的设计思路: 70 | // 先将send_bound_中的数据拷贝到io_block_中进行发送,然后根据传输的结果 71 | // 从send_bound_中将这部份数据移走,没有发送成功的数据可以重发 72 | // 但是在异步调用中,调用1发起-调用2发起-调用1回调之间会产生竞争条件,导致 73 | // 调用2实际发送的是调用1没有移走的数据 74 | // 实际上再boost::asio中是通过transfer_exactly发送的,如果返回时没有发送 75 | // 这么多数据,那么应该是网络层出现问题了,此时就直接socket错误返回了,不再 76 | // 考虑发送量小于请求量这种部分发送的情形了。 77 | front_sink(sz); 78 | return true; 79 | } 80 | 81 | void front_sink(uint32_t sz) { 82 | 83 | if (sz >= data_.size()) { 84 | data_.clear(); 85 | return; 86 | } 87 | 88 | std::vector remain(data_.begin() + sz, data_.end()); // 剩余的数据 89 | data_.swap(remain); 90 | } 91 | 92 | // 访问成员数据 93 | char* get_data() { 94 | if (data_.empty()) { 95 | return static_cast(nullptr); 96 | } 97 | return static_cast(data_.data()); 98 | } 99 | 100 | uint32_t get_length() { 101 | return static_cast(data_.size()); 102 | } 103 | 104 | private: 105 | 106 | std::vector data_; 107 | }; 108 | 109 | } // end tzhttpd 110 | 111 | #endif // __TZHTTPD_BUFFER_H__ 112 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8.11) 2 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x " ) 3 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Woverloaded-virtual -Wpointer-arith -Wwrite-strings -Werror=format -march=native " ) 4 | 5 | 6 | # 精简日志 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__=\"$(notdir $<)\" " ) 8 | 9 | # set(CMAKE_BUILD_TYPE DEBUG) 10 | # set(CMAKE_BUILD_TYPE RELEASE) 11 | # set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -g") 12 | # set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O2 ") 13 | add_definitions(-DNP_DEBUG) 14 | 15 | set (INCLUDE_DIRS 16 | ${PROJECT_SOURCE_DIR}/include/ 17 | ${PROJECT_SOURCE_DIR}/source/ 18 | ${PROJECT_SOURCE_DIR}/../xtra_rhelz.x/include/ 19 | ${PROJECT_SOURCE_DIR}/../xtra_rhelz.x/include/google/ 20 | ${PROJECT_SOURCE_DIR}/../roo 21 | ) 22 | include_directories( ${INCLUDE_DIRS} ) 23 | 24 | # 如果集成在其他项目当中,上面的配置可以继承得到而不用显式配置了 25 | 26 | 27 | # 日志请参照example中的使用方法,设置log_store_func函数即可 28 | # 如果不设置,那么默认日志打印在终端上面 29 | 30 | # 下面测试案例使用 31 | # 正常情况下,tzhttpd是当作子模块使用的,所以最好不要在这个模块中添加业务逻辑代码 32 | # add_subdirectory( cgi-handler/getdemo ) 33 | # add_subdirectory( cgi-handler/postdemo ) 34 | 35 | aux_source_directory(. DIR_LIB_SRCS) 36 | add_library (tzhttpd STATIC ${DIR_LIB_SRCS}) 37 | -------------------------------------------------------------------------------- /CgiHelper.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_CGI_HELPER_H__ 9 | #define __TZHTTPD_CGI_HELPER_H__ 10 | 11 | // pure c interface with so 12 | 13 | #include 14 | #include 15 | 16 | #ifdef __cplusplus 17 | extern "C" 18 | { 19 | #endif 20 | 21 | typedef struct { 22 | char* data; 23 | size_t len; 24 | } msg_t; 25 | 26 | static inline 27 | int free_msg(msg_t* msg) { 28 | 29 | free(msg->data); 30 | msg->data = NULL; 31 | msg->len = 0; 32 | 33 | return 0; 34 | } 35 | 36 | static inline 37 | int fill_msg(msg_t* msg, const char* data, size_t len) { 38 | 39 | free_msg(msg); 40 | msg->data = (char*)malloc(len); 41 | if (!msg->data) { 42 | return -1; 43 | } 44 | 45 | memcpy(msg->data, data, len); 46 | msg->len = len; 47 | return 0; 48 | } 49 | 50 | // caller alloc req, caller free req 51 | // callee alloc resp, caller free resp 52 | typedef int (* cgi_get_handler_t)(const msg_t* params, msg_t* resp, msg_t* resp_header); 53 | typedef int (* cgi_post_handler_t)(const msg_t* params, const msg_t* postdata, msg_t* resp, msg_t* resp_header); 54 | 55 | typedef int (* module_init_t)(); 56 | typedef int (* module_exit_t)(); 57 | 58 | #ifdef __cplusplus 59 | } // end extern "C" 60 | #endif 61 | 62 | #endif // __TZHTTPD_CGI_HELPER_H__ 63 | -------------------------------------------------------------------------------- /CgiWrapper.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include "HttpParser.h" 9 | #include "HttpProto.h" 10 | 11 | #include "CgiHelper.h" 12 | #include "SlibLoader.h" 13 | #include "CgiWrapper.h" 14 | 15 | namespace tzhttpd { 16 | 17 | namespace http_handler { 18 | 19 | using namespace tzhttpd::http_proto; 20 | 21 | bool CgiWrapper::load_dl() { 22 | 23 | dl_ = std::make_shared(dl_path_); 24 | if (!dl_) { 25 | return false; 26 | } 27 | 28 | if (!dl_->init()) { 29 | roo::log_err("init dl %s failed!", dl_->get_dl_path().c_str()); 30 | return false; 31 | } 32 | 33 | return true; 34 | } 35 | 36 | 37 | // 38 | // GET 39 | 40 | bool CgiGetWrapper::init() { 41 | if (!load_dl()) { 42 | roo::log_err("load dl failed!"); 43 | return false; 44 | } 45 | if (!dl_->load_func("cgi_get_handler", &func_)) { 46 | roo::log_err("Load cgi_get_handler func for %s failed.", dl_path_.c_str()); 47 | return false; 48 | } 49 | return true; 50 | } 51 | 52 | int CgiGetWrapper::operator()(const HttpParser& http_parser, 53 | std::string& response, std::string& status_line, 54 | std::vector& add_header) { 55 | if (!func_) { 56 | roo::log_err("get func not initialized."); 57 | return -1; 58 | } 59 | 60 | msg_t param{}; 61 | std::string param_str = http_parser.get_request_uri_params_string(); 62 | fill_msg(¶m, param_str.c_str(), param_str.size()); 63 | 64 | int ret = -1; 65 | msg_t rsp{}; 66 | msg_t rsp_header{}; 67 | 68 | try { 69 | ret = func_(¶m, &rsp, &rsp_header); 70 | } catch (const std::exception& e) { 71 | roo::log_err("post func call std::exception detect: %s.", e.what()); 72 | } catch (...) { 73 | roo::log_err("get func call exception detect."); 74 | } 75 | 76 | if (ret == 0) { 77 | response = std::string(rsp.data, rsp.len); 78 | status_line = generate_response_status_line(http_parser.get_version(), 79 | StatusCode::success_ok); 80 | } else { 81 | roo::log_err("post func call return: %d", ret); 82 | response = http_proto::content_error; 83 | status_line = generate_response_status_line(http_parser.get_version(), 84 | StatusCode::server_error_internal_server_error); 85 | } 86 | 87 | std::string header(rsp_header.data, rsp_header.len); 88 | if (!header.empty()) { 89 | std::vector vec{}; 90 | boost::split(vec, header, boost::is_any_of("\n")); 91 | for (auto iter = vec.begin(); iter != vec.cend(); ++iter) { 92 | std::string str = boost::trim_copy(*iter); 93 | if (!str.empty()) { 94 | add_header.push_back(str); 95 | } 96 | } 97 | } 98 | 99 | roo::log_info("param: %s,\n" 100 | "response: %s, status: %s, add_header: %s", 101 | param_str.c_str(), 102 | response.c_str(), status_line.c_str(), header.c_str()); 103 | 104 | free_msg(¶m); 105 | free_msg(&rsp); 106 | free_msg(&rsp_header); 107 | return ret; 108 | } 109 | 110 | // 111 | // POST 112 | 113 | bool CgiPostWrapper::init() { 114 | if (!load_dl()) { 115 | roo::log_err("load dl failed!"); 116 | return false; 117 | } 118 | if (!dl_->load_func("cgi_post_handler", &func_)) { 119 | roo::log_err("Load cgi_post_handler func for %s failed.", dl_path_.c_str()); 120 | return false; 121 | } 122 | return true; 123 | } 124 | 125 | int CgiPostWrapper::operator()(const HttpParser& http_parser, const std::string& post_data, 126 | std::string& response, std::string& status_line, 127 | std::vector& add_header) { 128 | if (!func_) { 129 | roo::log_err("get func not initialized."); 130 | return -1; 131 | } 132 | 133 | msg_t param{}, post{}; 134 | std::string param_str = http_parser.get_request_uri_params_string(); 135 | fill_msg(¶m, param_str.c_str(), param_str.size()); 136 | fill_msg(&post, post_data.c_str(), post_data.size()); 137 | 138 | int ret = -1; 139 | msg_t rsp{}; 140 | msg_t rsp_header{}; 141 | 142 | try { 143 | ret = func_(¶m, &post, &rsp, &rsp_header); 144 | } catch (const std::exception& e) { 145 | roo::log_err("post func call std::exception detect: %s.", e.what()); 146 | } catch (...) { 147 | roo::log_err("post func call exception detect."); 148 | } 149 | 150 | if (ret == 0) { 151 | response = std::string(rsp.data, rsp.len); 152 | status_line = generate_response_status_line(http_parser.get_version(), 153 | StatusCode::success_ok); 154 | } else { 155 | roo::log_err("post func call return: %d", ret); 156 | response = http_proto::content_error; 157 | status_line = generate_response_status_line(http_parser.get_version(), 158 | StatusCode::server_error_internal_server_error); 159 | } 160 | 161 | std::string header(rsp_header.data, rsp_header.len); 162 | if (!header.empty()) { 163 | std::vector vec{}; 164 | boost::split(vec, header, boost::is_any_of("\n")); 165 | for (auto iter = vec.begin(); iter != vec.cend(); ++iter) { 166 | std::string str = boost::trim_copy(*iter); 167 | if (!str.empty()) { 168 | add_header.push_back(str); 169 | } 170 | } 171 | } 172 | 173 | roo::log_info("param: %s, post: %s,\n" 174 | "response: %s, status: %s, add_header: %s", 175 | param_str.c_str(), post_data.c_str(), 176 | response.c_str(), status_line.c_str(), header.c_str()); 177 | 178 | free_msg(¶m); 179 | free_msg(&post); 180 | free_msg(&rsp); 181 | free_msg(&rsp_header); 182 | return ret; 183 | return 0; 184 | } 185 | 186 | 187 | } // end namespace http_handler 188 | } // end namespace tzhttpd 189 | 190 | -------------------------------------------------------------------------------- /CgiWrapper.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_CGI_WRAPPER_H__ 9 | #define __TZHTTPD_CGI_WRAPPER_H__ 10 | 11 | // 所有的http uri 路由 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | namespace tzhttpd { 24 | 25 | class SLibLoader; 26 | class HttpParser; 27 | 28 | namespace http_handler { 29 | 30 | 31 | class CgiWrapper { 32 | public: 33 | explicit CgiWrapper(const std::string& dl_path) : 34 | dl_path_(dl_path), 35 | dl_({ }) { 36 | } 37 | 38 | bool load_dl(); 39 | 40 | protected: 41 | std::string dl_path_; 42 | std::shared_ptr dl_; 43 | }; 44 | 45 | 46 | class CgiGetWrapper : public CgiWrapper { 47 | 48 | public: 49 | explicit CgiGetWrapper(const std::string& dl_path) : 50 | CgiWrapper(dl_path) { 51 | } 52 | 53 | bool init(); 54 | 55 | int operator ()(const HttpParser& http_parser, 56 | std::string& response, std::string& status_line, 57 | std::vector& add_header); 58 | 59 | private: 60 | cgi_get_handler_t func_; 61 | }; 62 | 63 | 64 | 65 | class CgiPostWrapper : public CgiWrapper { 66 | 67 | public: 68 | 69 | explicit CgiPostWrapper(const std::string& dl_path) : 70 | CgiWrapper(dl_path) { 71 | } 72 | 73 | bool init(); 74 | 75 | int operator ()(const HttpParser& http_parser, const std::string& post_data, 76 | std::string& response, std::string& status_line, 77 | std::vector& add_header); 78 | 79 | private: 80 | cgi_post_handler_t func_; 81 | }; 82 | 83 | 84 | } // end namespace http_handler 85 | } // end namespace tzhttpd 86 | 87 | 88 | #endif //__TZHTTPD_CGI_WRAPPER_H__ 89 | -------------------------------------------------------------------------------- /ConnIf.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_CONN_IF_H__ 9 | #define __TZHTTPD_CONN_IF_H__ 10 | 11 | #include 12 | #include "Buffer.h" 13 | 14 | #include 15 | #include 16 | 17 | namespace tzhttpd { 18 | 19 | enum class ConnStat : uint8_t { 20 | kWorking = 1, 21 | kPending = 2, 22 | kError = 3, 23 | kClosed = 4, 24 | }; 25 | 26 | enum class ShutdownType : uint8_t { 27 | kSend = 1, 28 | kRecv = 2, 29 | kBoth = 3, 30 | }; 31 | 32 | class ConnIf { 33 | 34 | public: 35 | 36 | /// Construct a connection with the given socket. 37 | explicit ConnIf(std::shared_ptr sock) : 38 | conn_stat_(ConnStat::kPending), 39 | socket_(sock) { 40 | set_tcp_nonblocking(false); 41 | } 42 | 43 | virtual ~ConnIf() = default; 44 | 45 | public: 46 | 47 | // read write 接口也可能回用到同步操作中去,所以返回bool 48 | virtual bool do_read() = 0; 49 | virtual void read_handler(const boost::system::error_code& ec, std::size_t bytes_transferred) = 0; 50 | 51 | virtual bool do_write() = 0; 52 | virtual void write_handler(const boost::system::error_code& ec, std::size_t bytes_transferred) = 0; 53 | 54 | 55 | // some general tiny settings function 56 | 57 | bool set_tcp_nonblocking(bool set_value) { 58 | 59 | boost::system::error_code ignore_ec; 60 | 61 | boost::asio::socket_base::non_blocking_io command(set_value); 62 | socket_->io_control(command, ignore_ec); 63 | 64 | return true; 65 | } 66 | 67 | bool set_tcp_nodelay(bool set_value) { 68 | 69 | boost::system::error_code ignore_ec; 70 | 71 | boost::asio::ip::tcp::no_delay nodelay(set_value); 72 | socket_->set_option(nodelay, ignore_ec); 73 | boost::asio::ip::tcp::no_delay option; 74 | socket_->get_option(option, ignore_ec); 75 | 76 | return (option.value() == set_value); 77 | } 78 | 79 | bool set_tcp_keepalive(bool set_value) { 80 | 81 | boost::system::error_code ignore_ec; 82 | 83 | boost::asio::socket_base::keep_alive keepalive(set_value); 84 | socket_->set_option(keepalive, ignore_ec); 85 | boost::asio::socket_base::keep_alive option; 86 | socket_->get_option(option, ignore_ec); 87 | 88 | return (option.value() == set_value); 89 | } 90 | 91 | void sock_shutdown_and_close(enum ShutdownType s) { 92 | 93 | std::lock_guard lock(conn_mutex_); 94 | if (conn_stat_ == ConnStat::kClosed) 95 | return; 96 | 97 | boost::system::error_code ignore_ec; 98 | if (s == ShutdownType::kSend) { 99 | socket_->shutdown(boost::asio::socket_base::shutdown_send, ignore_ec); 100 | } else if (s == ShutdownType::kRecv) { 101 | socket_->shutdown(boost::asio::socket_base::shutdown_receive, ignore_ec); 102 | } else if (s == ShutdownType::kBoth) { 103 | socket_->shutdown(boost::asio::socket_base::shutdown_both, ignore_ec); 104 | } 105 | 106 | socket_->close(ignore_ec); 107 | conn_stat_ = ConnStat::kClosed; 108 | } 109 | 110 | void sock_cancel() { 111 | 112 | std::lock_guard lock(conn_mutex_); 113 | 114 | boost::system::error_code ignore_ec; 115 | socket_->cancel(ignore_ec); 116 | } 117 | 118 | void sock_close() { 119 | 120 | std::lock_guard lock(conn_mutex_); 121 | if (conn_stat_ == ConnStat::kClosed) 122 | return; 123 | 124 | boost::system::error_code ignore_ec; 125 | socket_->close(ignore_ec); 126 | conn_stat_ = ConnStat::kClosed; 127 | } 128 | 129 | enum ConnStat get_conn_stat() const { return conn_stat_; } 130 | void set_conn_stat(enum ConnStat stat) { conn_stat_ = stat; } 131 | 132 | private: 133 | std::mutex conn_mutex_; 134 | enum ConnStat conn_stat_; 135 | 136 | protected: 137 | std::shared_ptr socket_; 138 | }; 139 | 140 | // 固定的发送、接收缓冲区大小 141 | const static uint32_t kFixedIoBufferSize = 2048; 142 | 143 | struct IOBound { 144 | IOBound() : 145 | io_block_({ }), 146 | length_hint_({ 0 }), 147 | buffer_() { 148 | } 149 | 150 | char io_block_[kFixedIoBufferSize]; // 读写操作的固定缓存 151 | size_t length_hint_; 152 | Buffer buffer_; // 已经传输字节 153 | }; 154 | } // end namespace tzhttpd 155 | 156 | #endif //__TZHTTPD_CONN_IF_H__ 157 | -------------------------------------------------------------------------------- /CryptoUtil.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_CRYPTO_UTIL_H__ 9 | #define __TZHTTPD_CRYPTO_UTIL_H__ 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | // 类静态函数可以直接将函数定义丢在头文件中 27 | 28 | namespace tzhttpd { 29 | 30 | // struct相比于namespace,可以避免产生多个实体 31 | 32 | struct CryptoUtil { 33 | 34 | 35 | static std::string base64_encode(const std::string& ascii, bool newline = false) { 36 | 37 | std::string base64; 38 | 39 | BIO * bio,*b64; 40 | BUF_MEM* bptr = BUF_MEM_new(); 41 | 42 | b64 = BIO_new(BIO_f_base64()); 43 | if (!newline) { 44 | BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 45 | } 46 | bio = BIO_new(BIO_s_mem()); 47 | BIO_push(b64, bio); 48 | BIO_set_mem_buf(b64, bptr, BIO_CLOSE); 49 | 50 | // Write directly to base64-buffer to avoid copy 51 | auto base64_length = static_cast(round(4 * ceil(static_cast(ascii.size()) / 3.0))); 52 | base64.resize(base64_length); 53 | bptr->length = 0; 54 | bptr->max = base64_length + 1; 55 | bptr->data = &base64[0]; 56 | 57 | if (BIO_write(b64, &ascii[0], static_cast(ascii.size())) <= 0 || BIO_flush(b64) <= 0) 58 | base64.clear(); 59 | 60 | // To keep &base64[0] through BIO_free_all(b64) 61 | bptr->length = 0; 62 | bptr->max = 0; 63 | bptr->data = (char*)NULL; 64 | 65 | BIO_free_all(b64); 66 | 67 | return base64; 68 | } 69 | 70 | static std::string base64_decode(const std::string& base64, bool newline = false)noexcept { 71 | 72 | std::string ascii; 73 | 74 | // Resize ascii, however, the size is a up to two bytes too large. 75 | ascii.resize((6 * base64.size()) / 8); 76 | BIO * b64,*bio; 77 | 78 | b64 = BIO_new(BIO_f_base64()); 79 | if (!newline) { 80 | BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 81 | } 82 | // TODO: Remove in 2020 83 | #if OPENSSL_VERSION_NUMBER <= 0x1000115fL 84 | bio = BIO_new_mem_buf((char*)&base64[0], static_cast(base64.size())); 85 | #else 86 | bio = BIO_new_mem_buf(&base64[0], static_cast(base64.size())); 87 | #endif 88 | bio = BIO_push(b64, bio); 89 | 90 | auto decoded_length = BIO_read(bio, &ascii[0], static_cast(ascii.size())); 91 | if (decoded_length > 0) 92 | ascii.resize(static_cast(decoded_length)); 93 | else 94 | ascii.clear(); 95 | 96 | BIO_free_all(b64); 97 | 98 | return ascii; 99 | } 100 | 101 | /// Return hex string from bytes in input string. 102 | /// 比如签名的二进制结果进行HEX字符串表达显示 103 | static std::string to_hex_string(const std::string& input)noexcept { 104 | std::stringstream hex_stream; 105 | hex_stream << std::hex << std::internal << std::setfill('0'); 106 | for (size_t i = 0; i < input.size(); ++i) 107 | hex_stream << std::setw(2) << static_cast(static_cast(input[i])); 108 | 109 | return hex_stream.str(); 110 | } 111 | 112 | static std::string md5(const std::string& input, std::size_t iterations = 1)noexcept { 113 | std::string hash; 114 | 115 | hash.resize(128 / 8); 116 | ::MD5(reinterpret_cast(&input[0]), input.size(), 117 | reinterpret_cast(&hash[0])); 118 | 119 | for (std::size_t c = 1; c < iterations; ++c) 120 | ::MD5(reinterpret_cast(&hash[0]), hash.size(), 121 | reinterpret_cast(&hash[0])); 122 | 123 | return hash; 124 | } 125 | 126 | 127 | static std::string sha1(const std::string& input, std::size_t iterations = 1)noexcept { 128 | std::string hash; 129 | 130 | hash.resize(160 / 8); 131 | ::SHA1(reinterpret_cast(&input[0]), input.size(), 132 | reinterpret_cast(&hash[0])); 133 | 134 | for (std::size_t c = 1; c < iterations; ++c) 135 | ::SHA1(reinterpret_cast(&hash[0]), hash.size(), 136 | reinterpret_cast(&hash[0])); 137 | 138 | return hash; 139 | } 140 | 141 | 142 | static std::string sha256(const std::string& input, std::size_t iterations = 1)noexcept { 143 | std::string hash; 144 | 145 | hash.resize(256 / 8); 146 | ::SHA256(reinterpret_cast(&input[0]), input.size(), 147 | reinterpret_cast(&hash[0])); 148 | 149 | for (std::size_t c = 1; c < iterations; ++c) 150 | ::SHA256(reinterpret_cast(&hash[0]), hash.size(), 151 | reinterpret_cast(&hash[0])); 152 | 153 | return hash; 154 | } 155 | 156 | 157 | static std::string sha512(const std::string& input, std::size_t iterations = 1)noexcept { 158 | std::string hash; 159 | 160 | hash.resize(512 / 8); 161 | ::SHA512(reinterpret_cast(&input[0]), input.size(), 162 | reinterpret_cast(&hash[0])); 163 | 164 | for (std::size_t c = 1; c < iterations; ++c) 165 | ::SHA512(reinterpret_cast(&hash[0]), hash.size(), 166 | reinterpret_cast(&hash[0])); 167 | 168 | return hash; 169 | } 170 | 171 | static std::string char_to_hex(char c)noexcept { 172 | 173 | std::string result; 174 | char first, second; 175 | 176 | first = static_cast((c & 0xF0) / 16); 177 | first += static_cast(first > 9 ? 'A' - 10 : '0'); 178 | second = c & 0x0F; 179 | second += static_cast(second > 9 ? 'A' - 10 : '0'); 180 | 181 | result.append(1, first); 182 | result.append(1, second); 183 | return result; 184 | } 185 | 186 | 187 | static char hex_to_char(char first, char second)noexcept { 188 | int digit; 189 | 190 | digit = (first >= 'A' ? ((first & 0xDF) - 'A') + 10 : (first - '0')); 191 | digit *= 16; 192 | digit += (second >= 'A' ? ((second & 0xDF) - 'A') + 10 : (second - '0')); 193 | return static_cast(digit); 194 | } 195 | 196 | static std::string url_encode(const std::string& src)noexcept { 197 | 198 | std::string result; 199 | for (std::string::const_iterator iter = src.begin(); iter != src.end(); ++iter) { 200 | switch (*iter) { 201 | case ' ': 202 | result.append(1, '+'); 203 | break; 204 | 205 | // alnum 206 | case 'A': 207 | case 'B': 208 | case 'C': 209 | case 'D': 210 | case 'E': 211 | case 'F': 212 | case 'G': 213 | case 'H': 214 | case 'I': 215 | case 'J': 216 | case 'K': 217 | case 'L': 218 | case 'M': 219 | case 'N': 220 | case 'O': 221 | case 'P': 222 | case 'Q': 223 | case 'R': 224 | case 'S': 225 | case 'T': 226 | case 'U': 227 | case 'V': 228 | case 'W': 229 | case 'X': 230 | case 'Y': 231 | case 'Z': 232 | case 'a': 233 | case 'b': 234 | case 'c': 235 | case 'd': 236 | case 'e': 237 | case 'f': 238 | case 'g': 239 | case 'h': 240 | case 'i': 241 | case 'j': 242 | case 'k': 243 | case 'l': 244 | case 'm': 245 | case 'n': 246 | case 'o': 247 | case 'p': 248 | case 'q': 249 | case 'r': 250 | case 's': 251 | case 't': 252 | case 'u': 253 | case 'v': 254 | case 'w': 255 | case 'x': 256 | case 'y': 257 | case 'z': 258 | case '0': 259 | case '1': 260 | case '2': 261 | case '3': 262 | case '4': 263 | case '5': 264 | case '6': 265 | case '7': 266 | case '8': 267 | case '9': 268 | // mark 269 | case '-': 270 | case '_': 271 | case '.': 272 | case '!': 273 | case '~': 274 | case '*': 275 | case '\'': 276 | case '(': 277 | case ')': 278 | result.append(1, *iter); 279 | break; 280 | 281 | // escape 282 | default: 283 | result.append(1, '%'); 284 | result.append(char_to_hex(*iter)); 285 | break; 286 | } 287 | } 288 | 289 | return result; 290 | } 291 | 292 | static std::string url_decode(const std::string& src)noexcept { 293 | 294 | std::string result; 295 | char c; 296 | 297 | for (std::string::const_iterator iter = src.begin(); iter != src.end(); ++iter) { 298 | switch (*iter) { 299 | case '+': 300 | result.append(1, ' '); 301 | break; 302 | 303 | case '%': 304 | // Don't assume well-formed input 305 | if (std::distance(iter, src.end()) >= 2 && 306 | std::isxdigit(*(iter + 1)) && 307 | std::isxdigit(*(iter + 2))) { 308 | c = *(++iter); 309 | result.append(1, hex_to_char(c, *(++iter))); 310 | } 311 | // Just pass the % through untouched 312 | else { 313 | result.append(1, '%'); 314 | } 315 | break; 316 | 317 | default: 318 | result.append(1, *iter); 319 | break; 320 | } 321 | } 322 | 323 | return result; 324 | } 325 | 326 | static int Gzip(const std::string& src, std::string& store) { 327 | 328 | CryptoPP::Gzip zipper; 329 | 330 | try { 331 | zipper.Put((byte*)src.data(), src.size()); 332 | zipper.MessageEnd(); 333 | 334 | CryptoPP::word64 avail = zipper.MaxRetrievable(); 335 | if (avail) { 336 | store.resize(avail); 337 | zipper.Get((byte*)&store[0], store.size()); 338 | return 0; 339 | } 340 | } catch (std::exception& e) { 341 | std::cerr << "Gzip exception: " << e.what() << std::endl; 342 | } catch (...) { 343 | std::cerr << "Gzip exception: unknown" << std::endl; 344 | } 345 | 346 | return -1; 347 | } 348 | 349 | 350 | 351 | static int Gunzip(const std::string& src, std::string& store) { 352 | 353 | CryptoPP::Gunzip unzipper; 354 | 355 | try { 356 | unzipper.Put((byte*)src.data(), src.size()); 357 | unzipper.MessageEnd(); 358 | 359 | CryptoPP::word64 avail = unzipper.MaxRetrievable(); 360 | if (avail) { 361 | store.resize(avail); 362 | unzipper.Get((byte*)&store[0], store.size()); 363 | return 0; 364 | } 365 | } catch (std::exception& e) { 366 | std::cerr << "Gunzip exception: " << e.what() << std::endl; 367 | } catch (...) { 368 | std::cerr << "Gunzip exception: unknown" << std::endl; 369 | } 370 | 371 | return -1; 372 | } 373 | 374 | static int Deflator(const std::string& src, std::string& store) { 375 | 376 | CryptoPP::Deflator zipper; 377 | 378 | try { 379 | zipper.Put((byte*)src.data(), src.size()); 380 | zipper.MessageEnd(); 381 | 382 | CryptoPP::word64 avail = zipper.MaxRetrievable(); 383 | if (avail) { 384 | store.resize(avail); 385 | zipper.Get((byte*)&store[0], store.size()); 386 | return 0; 387 | } 388 | } catch (std::exception& e) { 389 | std::cerr << "Deflator exception: " << e.what() << std::endl; 390 | } catch (...) { 391 | std::cerr << "Deflator exception: unknown" << std::endl; 392 | } 393 | 394 | return -1; 395 | } 396 | 397 | static int Inflator(const std::string& src, std::string& store) { 398 | 399 | CryptoPP::Inflator unzipper; 400 | 401 | try { 402 | unzipper.Put((byte*)src.data(), src.size()); 403 | unzipper.MessageEnd(); 404 | 405 | CryptoPP::word64 avail = unzipper.MaxRetrievable(); 406 | if (avail) { 407 | store.resize(avail); 408 | unzipper.Get((byte*)&store[0], store.size()); 409 | return 0; 410 | } 411 | } catch (std::exception& e) { 412 | std::cerr << "Inflator exception: " << e.what() << std::endl; 413 | } catch (...) { 414 | std::cerr << "Inflator exception: unknown" << std::endl; 415 | } 416 | 417 | return -1; 418 | } 419 | 420 | 421 | // better usage 422 | 423 | 424 | static std::string Gzip(const std::string& src) { 425 | 426 | CryptoPP::Gzip zipper; 427 | std::string store; 428 | 429 | try { 430 | zipper.Put((byte*)src.data(), src.size()); 431 | zipper.MessageEnd(); 432 | 433 | CryptoPP::word64 avail = zipper.MaxRetrievable(); 434 | if (avail) { 435 | store.resize(avail); 436 | zipper.Get((byte*)&store[0], store.size()); 437 | } 438 | } catch (std::exception& e) { 439 | std::cerr << "Gzip exception: " << e.what() << std::endl; 440 | } catch (...) { 441 | std::cerr << "Gzip exception: unknown" << std::endl; 442 | } 443 | 444 | return store; 445 | } 446 | 447 | static std::string Gunzip(const std::string& src) { 448 | 449 | CryptoPP::Gunzip unzipper; 450 | std::string store; 451 | 452 | try { 453 | unzipper.Put((byte*)src.data(), src.size()); 454 | unzipper.MessageEnd(); 455 | 456 | CryptoPP::word64 avail = unzipper.MaxRetrievable(); 457 | if (avail) { 458 | store.resize(avail); 459 | unzipper.Get((byte*)&store[0], store.size()); 460 | } 461 | } catch (std::exception& e) { 462 | std::cerr << "Gunzip exception: " << e.what() << std::endl; 463 | } catch (...) { 464 | std::cerr << "Gunzip exception: unknown" << std::endl; 465 | } 466 | 467 | return store; 468 | } 469 | 470 | static std::string Deflator(const std::string& src) { 471 | 472 | CryptoPP::Deflator zipper; 473 | std::string store; 474 | 475 | try { 476 | zipper.Put((byte*)src.data(), src.size()); 477 | zipper.MessageEnd(); 478 | 479 | CryptoPP::word64 avail = zipper.MaxRetrievable(); 480 | if (avail) { 481 | store.resize(avail); 482 | zipper.Get((byte*)&store[0], store.size()); 483 | } 484 | } catch (std::exception& e) { 485 | std::cerr << "Deflator exception: " << e.what() << std::endl; 486 | } catch (...) { 487 | std::cerr << "Deflator exception: unknown" << std::endl; 488 | } 489 | 490 | return store; 491 | } 492 | 493 | static std::string Inflator(const std::string& src) { 494 | 495 | CryptoPP::Inflator unzipper; 496 | std::string store; 497 | 498 | try { 499 | unzipper.Put((byte*)src.data(), src.size()); 500 | unzipper.MessageEnd(); 501 | 502 | CryptoPP::word64 avail = unzipper.MaxRetrievable(); 503 | if (avail) { 504 | store.resize(avail); 505 | unzipper.Get((byte*)&store[0], store.size()); 506 | } 507 | } catch (std::exception& e) { 508 | std::cerr << "Inflator exception: " << e.what() << std::endl; 509 | } catch (...) { 510 | std::cerr << "Inflator exception: unknown" << std::endl; 511 | } 512 | 513 | return store; 514 | } 515 | 516 | 517 | 518 | static std::string CDeflator(const std::string& src) { 519 | std::string store; 520 | 521 | char ext[4096 * 16]{}; 522 | uLong srcSize = src.size(); 523 | //uLong dstSize = compressBound(srcSize); 524 | uLong dstSize = sizeof(ext); 525 | 526 | // Deflate 527 | // Upon exit, destLen is the actual size of the compressed data 528 | int ret = compress((Bytef*)ext, &dstSize, (const Bytef*)src.c_str(), srcSize); 529 | if (ret != Z_OK) { 530 | std::cerr << "compress error: " << ret << std::endl; 531 | } else { 532 | store = std::string(ext, dstSize); 533 | } 534 | return store; 535 | } 536 | 537 | 538 | static std::string CInflator(const std::string& src) { 539 | std::string store; 540 | 541 | char ext[4096 * 16]{}; 542 | uLong srcSize = src.size(); 543 | uLong dstSize = sizeof(ext); 544 | 545 | // Inflate 546 | int ret = uncompress((Bytef*)ext, &dstSize, (const Bytef*)src.c_str(), srcSize); 547 | if (ret != Z_OK) { 548 | std::cerr << "uncompress error: " << ret << std::endl; 549 | } else { 550 | store = std::string(ext, dstSize); 551 | } 552 | 553 | return store; 554 | } 555 | 556 | static std::string hex_string(const char* data, size_t len)noexcept { 557 | 558 | static const char* hexmap = "0123456789ABCDEF"; 559 | std::string result(len * 2, ' '); 560 | 561 | for (size_t i = 0; i < len; ++i) { 562 | result[2 * i] = hexmap[(data[i] & 0xF0) >> 4]; 563 | result[2 * i + 1] = hexmap[data[i] & 0x0F]; 564 | } 565 | 566 | return result; 567 | } 568 | 569 | 570 | }; 571 | 572 | } // end namespace tzhttpd 573 | 574 | #endif // __TZHTTPD_CRYPTO_UTIL_H__ 575 | -------------------------------------------------------------------------------- /Dispatcher.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include 9 | 10 | #include "Global.h" 11 | 12 | #include "Executor.h" 13 | #include "HttpExecutor.h" 14 | #include "HttpReqInstance.h" 15 | 16 | #include "Dispatcher.h" 17 | 18 | namespace tzhttpd { 19 | 20 | Dispatcher& Dispatcher::instance() { 21 | static Dispatcher dispatcher; 22 | return dispatcher; 23 | } 24 | 25 | bool Dispatcher::init() { 26 | 27 | initialized_ = true; 28 | 29 | // 注册默认default vhost 30 | SAFE_ASSERT(!default_service_); 31 | 32 | // 创建默认虚拟主机 default virtual host 33 | auto default_http_impl = std::make_shared("[default]"); 34 | 35 | // HttpExecutor层次的初始化,包括了虚拟主机配置文件的解析 36 | // 同时Executor需要的配置信息通过ExecuteConf传递过来 37 | if (!default_http_impl || !default_http_impl->init()) { 38 | roo::log_err("create and initialize HttpExecutor for host [default] failed."); 39 | return false; 40 | } 41 | 42 | // http executor 43 | default_service_.reset(new Executor(default_http_impl)); 44 | // 进行业务层无关的初始化,比如创建工作线程组,维护请求队列等 45 | if (!default_service_ || !default_service_->init()) { 46 | roo::log_err("create and initialize host [default] executor failed."); 47 | return false; 48 | } 49 | 50 | default_service_->executor_start(); 51 | roo::log_info("start host [default] Executor: %s success", default_service_->instance_name().c_str()); 52 | 53 | 54 | // HttpExecutor和Executor在register_virtual_host的时候已经进行初始化(并成功)了 55 | // 此处开启虚拟主机的工作线程组,开始接收请求 56 | for (auto iter = services_.begin(); iter != services_.end(); ++iter) { 57 | auto executor = iter->second; 58 | executor->executor_start(); 59 | roo::log_info("start Executor for host %s success", executor->instance_name().c_str()); 60 | } 61 | 62 | // 注册配置动态配置更新接口,由此处分发到各个虚拟主机,不再每个虚拟主机自己注册 63 | Global::instance().setting_ptr()->attach_runtime_callback( 64 | "tzhttpd-Dispatcher", 65 | std::bind(&Dispatcher::module_runtime, this, 66 | std::placeholders::_1)); 67 | 68 | 69 | return true; 70 | } 71 | 72 | void Dispatcher::handle_http_request(std::shared_ptr http_req_instance) { 73 | 74 | std::shared_ptr service; 75 | auto it = services_.find(http_req_instance->hostname_); 76 | if (it != services_.end()) { 77 | service = it->second; 78 | } 79 | 80 | if (!service) { 81 | roo::log_info("find http service_impl (virtualhost) for %s failed, using default.", 82 | http_req_instance->hostname_.c_str()); 83 | service = default_service_; 84 | } 85 | 86 | service->handle_http_request(http_req_instance); 87 | } 88 | 89 | 90 | int Dispatcher::add_virtual_host(const std::string& hostname) { 91 | 92 | if (initialized_) { 93 | roo::log_err("Dispatcher has already been initialized, but we does not support add virtualhost dynamiclly."); 94 | return -1; 95 | } 96 | 97 | if (services_.find(hostname) != services_.end()) { 98 | roo::log_err("already found host %s added, please check.", hostname.c_str()); 99 | return -1; 100 | } 101 | 102 | // 此处加载HTTP的相关配置 103 | auto default_http_impl = std::make_shared(hostname); 104 | if (!default_http_impl || !default_http_impl->init()) { 105 | roo::log_err("create and initialize HttpExecutor for host %s failed.", hostname.c_str()); 106 | return -1; 107 | } 108 | 109 | auto default_http = std::make_shared(default_http_impl); 110 | if (!default_http || !default_http->init()) { 111 | roo::log_err("create and initialize Executor for host %s failed.", hostname.c_str()); 112 | return -1; 113 | } 114 | 115 | services_[hostname] = default_http; 116 | roo::log_info("successful add service %s ", default_http->instance_name().c_str()); 117 | 118 | return 0; 119 | } 120 | 121 | 122 | int Dispatcher::add_http_get_handler(const std::string& hostname, const std::string& uri_regex, 123 | const HttpGetHandler& handler, bool built_in) { 124 | 125 | std::shared_ptr service; 126 | 127 | if (hostname.empty() || hostname == "[default]") { 128 | service = default_service_; 129 | } else { 130 | auto it = services_.find(hostname); 131 | if (it != services_.end()) { 132 | service = it->second; 133 | } else { 134 | roo::log_err("hostname %s not found.", hostname.c_str()); 135 | return -1; 136 | } 137 | } 138 | 139 | SAFE_ASSERT(service); 140 | 141 | return service->add_get_handler(uri_regex, handler, built_in); 142 | } 143 | 144 | int Dispatcher::add_http_post_handler(const std::string& hostname, const std::string& uri_regex, 145 | const HttpPostHandler& handler, bool built_in) { 146 | 147 | std::shared_ptr service; 148 | 149 | if (hostname.empty() || hostname == "[default]") { 150 | service = default_service_; 151 | } else { 152 | auto it = services_.find(hostname); 153 | if (it != services_.end()) { 154 | service = it->second; 155 | } else { 156 | roo::log_err("hostname %s not found.", hostname.c_str()); 157 | return -1; 158 | } 159 | } 160 | 161 | SAFE_ASSERT(service); 162 | return service->add_post_handler(uri_regex, handler, built_in); 163 | } 164 | 165 | int Dispatcher::drop_http_handler(const std::string& hostname, const std::string& uri_regex, enum HTTP_METHOD method) { 166 | 167 | std::shared_ptr service; 168 | 169 | if (hostname.empty() || hostname == "[default]") { 170 | service = default_service_; 171 | } else { 172 | auto it = services_.find(hostname); 173 | if (it != services_.end()) { 174 | service = it->second; 175 | } else { 176 | roo::log_err("hostname %s not found.", hostname.c_str()); 177 | return -1; 178 | } 179 | } 180 | 181 | SAFE_ASSERT(service); 182 | return service->drop_handler(uri_regex, method); 183 | } 184 | 185 | 186 | // 依次调用触发进行默认、其他虚拟主机的配置更新 187 | int Dispatcher::module_runtime(const libconfig::Config& conf) { 188 | 189 | int ret_sum = 0; 190 | int ret = 0; 191 | 192 | roo::log_warning("module_runtime begin to handle host [default]."); 193 | ret = default_service_->module_runtime(conf); 194 | roo::log_warning("module_runtime for host [default] return: %d", ret); 195 | ret_sum += ret; 196 | 197 | for (auto iter = services_.begin(); iter != services_.end(); ++iter) { 198 | 199 | auto executor = iter->second; 200 | ret = executor->module_runtime(conf); 201 | roo::log_warning("module_runtime for host %s return: %d", 202 | executor->instance_name().c_str(), ret); 203 | ret_sum += ret; 204 | } 205 | 206 | return ret_sum; 207 | } 208 | 209 | 210 | } // end namespace tzhttpd 211 | 212 | -------------------------------------------------------------------------------- /Dispatcher.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_DISPATCHER_H__ 9 | #define __TZHTTPD_DISPATCHER_H__ 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include "HttpHandler.h" 19 | 20 | namespace tzhttpd { 21 | 22 | class HttpReqInstance; 23 | class Executor; 24 | 25 | class Dispatcher { 26 | 27 | __noncopyable__(Dispatcher) 28 | 29 | public: 30 | static Dispatcher& instance(); 31 | 32 | bool init(); 33 | 34 | void handle_http_request(std::shared_ptr http_req_instance); 35 | 36 | // 注册虚拟主机 37 | int add_virtual_host(const std::string& hostname); 38 | 39 | // 外部注册http handler的接口 40 | int add_http_get_handler(const std::string& hostname, const std::string& uri_regex, 41 | const HttpGetHandler& handler, bool built_in); 42 | int add_http_post_handler(const std::string& hostname, const std::string& uri_regex, 43 | const HttpPostHandler& handler, bool built_in); 44 | 45 | int drop_http_handler(const std::string& hostname, const std::string& uri_regex, enum HTTP_METHOD method); 46 | 47 | int module_runtime(const libconfig::Config& conf); 48 | 49 | private: 50 | 51 | Dispatcher() : 52 | initialized_(false), 53 | services_({ }) { 54 | } 55 | 56 | ~Dispatcher() = default; 57 | 58 | bool initialized_; 59 | 60 | // 系统在启动的时候进行注册初始化,然后再提供服务,所以 61 | // 这边就不使用锁结构进行保护了,防止影响性能 62 | std::map> services_; 63 | 64 | // 默认的http虚拟主机 65 | std::shared_ptr default_service_; 66 | }; 67 | 68 | } // end namespace tzhttpd 69 | 70 | 71 | #endif // __TZHTTPD_SERVICE_DISPATCHER_H__ 72 | -------------------------------------------------------------------------------- /Executor.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include "Dispatcher.h" 9 | #include "HttpReqInstance.h" 10 | #include "HttpExecutor.h" 11 | 12 | #include "Executor.h" 13 | #include "Global.h" 14 | 15 | namespace tzhttpd { 16 | 17 | 18 | bool Executor::init() { 19 | 20 | std::lock_guard lock(conf_lock_); 21 | 22 | if (auto http_executor = dynamic_cast(service_impl_.get())) { 23 | conf_ = http_executor->get_executor_conf(); 24 | } else { 25 | roo::log_err("cast instance failed."); 26 | return false; 27 | } 28 | 29 | if (conf_.exec_thread_number_hard_ < conf_.exec_thread_number_) { 30 | conf_.exec_thread_number_hard_ = conf_.exec_thread_number_; 31 | } 32 | 33 | if (conf_.exec_thread_number_ <= 0 || conf_.exec_thread_number_ > 100 || 34 | conf_.exec_thread_number_hard_ > 100 || 35 | conf_.exec_thread_number_hard_ < conf_.exec_thread_number_) { 36 | roo::log_err("invalid exec_thread_pool_size setting: %d, %d", 37 | conf_.exec_thread_number_, conf_.exec_thread_number_hard_); 38 | return false; 39 | } 40 | 41 | if (conf_.exec_thread_step_queue_size_ < 0) { 42 | roo::log_err("invalid exec_thread_step_queue_size setting: %d", 43 | conf_.exec_thread_step_queue_size_); 44 | return false; 45 | } 46 | 47 | if (!executor_threads_.init_threads( 48 | std::bind(&Executor::executor_service_run, this, std::placeholders::_1), conf_.exec_thread_number_)) { 49 | roo::log_err("executor_service_run init task for %s failed!", instance_name().c_str()); 50 | return false; 51 | } 52 | 53 | if (conf_.exec_thread_number_hard_ > conf_.exec_thread_number_ && 54 | conf_.exec_thread_step_queue_size_ > 0) { 55 | roo::log_info("we will support thread adjust for %s, with param hard %d, queue_step %d", 56 | instance_name().c_str(), 57 | conf_.exec_thread_number_hard_, conf_.exec_thread_step_queue_size_); 58 | 59 | if (!Global::instance().timer_ptr()->add_timer( 60 | std::bind(&Executor::executor_threads_adjust, shared_from_this(), std::placeholders::_1), 61 | 1 * 1000, true)) { 62 | roo::log_err("create thread adjust timer failed."); 63 | return false; 64 | } 65 | } 66 | 67 | Global::instance().status_ptr()->attach_status_callback( 68 | "tzhttpd-executor_" + instance_name(), 69 | std::bind(&Executor::module_status, shared_from_this(), 70 | std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); 71 | 72 | 73 | return true; 74 | } 75 | 76 | 77 | void Executor::executor_service_run(roo::ThreadObjPtr ptr) { 78 | 79 | roo::log_warning("executor_service thread %#lx about to loop ...", (long)pthread_self()); 80 | 81 | while (true) { 82 | 83 | std::shared_ptr http_req_instance{}; 84 | 85 | if (unlikely(ptr->status_ == roo::ThreadStatus::kTerminating)) { 86 | roo::log_err("thread %#lx is about to terminating...", (long)pthread_self()); 87 | break; 88 | } 89 | 90 | // 线程启动 91 | if (unlikely(ptr->status_ == roo::ThreadStatus::kSuspend)) { 92 | ::usleep(1 * 1000 * 1000); 93 | continue; 94 | } 95 | 96 | if (!http_req_queue_.POP(http_req_instance, 1000 /*1s*/) || !http_req_instance) { 97 | continue; 98 | } 99 | 100 | // execute RPC handler 101 | service_impl_->handle_http_request(http_req_instance); 102 | } 103 | 104 | ptr->status_ = roo::ThreadStatus::kDead; 105 | roo::log_warning("io_service thread %#lx is about to terminate ... ", (long)pthread_self()); 106 | 107 | return; 108 | 109 | } 110 | 111 | 112 | void Executor::executor_threads_adjust(const boost::system::error_code& ec) { 113 | 114 | ExecutorConf conf{}; 115 | 116 | { 117 | std::lock_guard lock(conf_lock_); 118 | conf = conf_; 119 | } 120 | 121 | SAFE_ASSERT(conf.exec_thread_step_queue_size_ > 0); 122 | if (!conf.exec_thread_step_queue_size_) { 123 | return; 124 | } 125 | 126 | // 进行检查,看是否需要伸缩线程池 127 | int expect_thread = conf.exec_thread_number_; 128 | 129 | int queueSize = http_req_queue_.SIZE(); 130 | if (queueSize > conf.exec_thread_step_queue_size_) { 131 | expect_thread += queueSize / conf.exec_thread_step_queue_size_; 132 | } 133 | if (expect_thread > conf.exec_thread_number_hard_) { 134 | expect_thread = conf.exec_thread_number_hard_; 135 | } 136 | 137 | if (expect_thread != conf.exec_thread_number_) { 138 | roo::log_warning("start thread number: %d, expect resize to %d", 139 | conf.exec_thread_number_, expect_thread); 140 | } 141 | 142 | // 如果当前运行的线程和实际的线程一样,就不会伸缩 143 | executor_threads_.resize_threads(expect_thread); 144 | 145 | return; 146 | } 147 | 148 | int Executor::module_status(std::string& module, std::string& key, std::string& value) { 149 | 150 | module = "tzhttpd"; 151 | key = "executor_" + instance_name(); 152 | 153 | std::stringstream ss; 154 | 155 | ss << "\t" << "instance_name: " << instance_name() << std::endl; 156 | ss << "\t" << "exec_thread_number: " << conf_.exec_thread_number_ << std::endl; 157 | ss << "\t" << "exec_thread_number_hard(maxium): " << conf_.exec_thread_number_hard_ << std::endl; 158 | ss << "\t" << "exec_thread_step_queue_size: " << conf_.exec_thread_step_queue_size_ << std::endl; 159 | 160 | ss << "\t" << std::endl; 161 | 162 | ss << "\t" << "current_thread_number: " << executor_threads_.get_pool_size() << std::endl; 163 | ss << "\t" << "current_queue_size: " << http_req_queue_.SIZE() << std::endl; 164 | 165 | std::string nullModule; 166 | std::string subKey; 167 | std::string subValue; 168 | service_impl_->module_status(nullModule, subKey, subValue); 169 | 170 | // collect 171 | value = ss.str() + subValue; 172 | 173 | return 0; 174 | } 175 | 176 | 177 | int Executor::module_runtime(const libconfig::Config& conf) { 178 | 179 | int ret = service_impl_->module_runtime(conf); 180 | 181 | // 如果返回0,表示配置文件已经正确解析了,同时ExecutorConf也重新初始化了 182 | if (ret == 0) { 183 | if (auto http_executor = dynamic_cast(service_impl_.get())) { 184 | 185 | roo::log_warning("update ExecutorConf for host %s", instance_name().c_str()); 186 | 187 | std::lock_guard lock(conf_lock_); 188 | conf_ = http_executor->get_executor_conf(); 189 | } 190 | } 191 | return ret; 192 | } 193 | 194 | } // end namespace tzhttpd 195 | -------------------------------------------------------------------------------- /Executor.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_EXECUTOR_H__ 9 | #define __TZHTTPD_EXECUTOR_H__ 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "ServiceIf.h" 18 | 19 | #include "Global.h" 20 | 21 | namespace tzhttpd { 22 | 23 | // 简短的结构体,用来从HttpExecutor传递配置信息 24 | // 因为主机相关的信息是在HttpExecutor中解析的 25 | 26 | struct ExecutorConf { 27 | int exec_thread_number_; 28 | int exec_thread_number_hard_; // 允许最大的线程数目 29 | int exec_thread_step_queue_size_; 30 | }; 31 | 32 | class Executor : public ServiceIf, 33 | public std::enable_shared_from_this { 34 | 35 | public: 36 | 37 | explicit Executor(std::shared_ptr service_impl) : 38 | service_impl_(service_impl), 39 | http_req_queue_(), 40 | conf_lock_(), 41 | conf_({ }) { 42 | } 43 | 44 | void handle_http_request(std::shared_ptr http_req_instance)override { 45 | http_req_queue_.PUSH(http_req_instance); 46 | } 47 | 48 | std::string instance_name()override { 49 | return service_impl_->instance_name(); 50 | } 51 | 52 | int add_get_handler(const std::string& uri_regex, const HttpGetHandler& handler, bool built_in)override { 53 | return service_impl_->add_get_handler(uri_regex, handler, built_in); 54 | } 55 | 56 | int add_post_handler(const std::string& uri_regex, const HttpPostHandler& handler, bool built_in)override { 57 | return service_impl_->add_post_handler(uri_regex, handler, built_in); 58 | } 59 | 60 | bool exist_handler(const std::string& uri_regex, enum HTTP_METHOD method)override { 61 | return service_impl_->exist_handler(uri_regex, method); 62 | } 63 | 64 | int drop_handler(const std::string& uri_regex, enum HTTP_METHOD method)override { 65 | return service_impl_->drop_handler(uri_regex, method); 66 | } 67 | 68 | 69 | 70 | bool init(); 71 | int module_runtime(const libconfig::Config& conf)override; 72 | int module_status(std::string& module, std::string& key, std::string& value)override; 73 | 74 | private: 75 | // point to HttpExecutor, forward some request 76 | std::shared_ptr service_impl_; 77 | roo::EQueue> http_req_queue_; 78 | 79 | 80 | private: 81 | // 这个锁保护conf_使用的,因为使用频率不是很高,所以所有访问 82 | // conf_的都使用这个锁也不会造成问题 83 | std::mutex conf_lock_; 84 | ExecutorConf conf_; 85 | 86 | roo::ThreadPool executor_threads_; 87 | void executor_service_run(roo::ThreadObjPtr ptr); // main task loop 88 | 89 | public: 90 | 91 | int executor_start() { 92 | 93 | roo::log_warning("about to start executor for host %s ... ", instance_name().c_str()); 94 | executor_threads_.start_threads(); 95 | return 0; 96 | } 97 | 98 | int executor_stop_graceful() { 99 | 100 | roo::log_warning("about to stop executor for host %s ... ", instance_name().c_str()); 101 | executor_threads_.graceful_stop_threads(); 102 | 103 | return 0; 104 | } 105 | 106 | int executor_join() { 107 | 108 | roo::log_warning("about to join executor for host %s ... ", instance_name().c_str()); 109 | executor_threads_.join_threads(); 110 | return 0; 111 | } 112 | 113 | private: 114 | void executor_threads_adjust(const boost::system::error_code& ec); 115 | 116 | }; 117 | 118 | } // end namespace tzhttpd 119 | 120 | 121 | #endif // __TZHTTPD_EXECUTOR_H__ 122 | -------------------------------------------------------------------------------- /Global.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include "Global.h" 15 | 16 | namespace tzhttpd { 17 | 18 | Global& Global::instance() { 19 | static Global instance; 20 | return instance; 21 | } 22 | 23 | bool Global::init(const std::string& setting_file) { 24 | 25 | initialized_ = true; 26 | 27 | timer_ptr_ = make_unique(); 28 | if (!timer_ptr_ || !timer_ptr_->init()) { 29 | roo::log_err("Create and init roo::Timer service failed."); 30 | return false; 31 | } 32 | 33 | status_ptr_ = make_unique(); 34 | if (!status_ptr_) { 35 | roo::log_err("Create roo::Status failed."); 36 | return false; 37 | } 38 | 39 | setting_ptr_ = make_unique(); 40 | if (!setting_ptr_ || !setting_ptr_->init(setting_file)) { 41 | roo::log_err("Create and init roo::Setting with cfg %s failed.", setting_file.c_str()); 42 | return false; 43 | } 44 | 45 | return true; 46 | } 47 | 48 | 49 | 50 | } // end namespace tzhttpd 51 | -------------------------------------------------------------------------------- /Global.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_GLOBAL_H__ 9 | #define __TZHTTPD_GLOBAL_H__ 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace tzhttpd { 18 | 19 | class Global { 20 | 21 | __noncopyable__(Global) 22 | 23 | public: 24 | static Global& instance(); 25 | bool init(const std::string& setting_file); 26 | 27 | private: 28 | Global() : 29 | initialized_(false) { 30 | } 31 | 32 | ~Global() = default; 33 | 34 | bool initialized_; 35 | 36 | public: 37 | std::unique_ptr& setting_ptr() { 38 | SAFE_ASSERT(setting_ptr_); 39 | return setting_ptr_; 40 | } 41 | std::unique_ptr& status_ptr() { 42 | SAFE_ASSERT(status_ptr_); 43 | return status_ptr_; 44 | } 45 | 46 | std::unique_ptr& timer_ptr() { 47 | SAFE_ASSERT(timer_ptr_); 48 | return timer_ptr_; 49 | } 50 | 51 | private: 52 | // 使用Roo中的实现,但是都是内部持有独立的实例 53 | std::unique_ptr setting_ptr_; 54 | std::unique_ptr status_ptr_; 55 | std::unique_ptr timer_ptr_; 56 | 57 | }; 58 | 59 | } // end namespace tzhttpd 60 | 61 | #endif // __TZHTTPD_GLOBAL_H__ 62 | -------------------------------------------------------------------------------- /HttpConf.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_HTTP_CONF_H__ 9 | #define __TZHTTPD_HTTP_CONF_H__ 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | namespace tzhttpd { 21 | 22 | 23 | extern void init_http_version_once(const std::string& server_version); 24 | 25 | class HttpServer; 26 | class HttpServerImpl; 27 | 28 | class HttpConf { 29 | 30 | friend class HttpServer; 31 | friend class HttpServerImpl; 32 | 33 | bool service_enabled_; // 服务开关 34 | int32_t service_speed_; 35 | 36 | int32_t service_token_; 37 | 38 | int32_t service_concurrency_; // 最大连接并发控制 39 | 40 | int32_t session_cancel_time_out_; // session间隔会话时长 41 | int32_t ops_cancel_time_out_; // ops操作超时时长 42 | 43 | // 加载、更新配置的时候保护竞争状态 44 | // 这里保护主要是非atomic的原子结构 45 | std::mutex lock_; 46 | std::set safe_ip_; 47 | 48 | std::string bind_addr_; 49 | int32_t bind_port_; 50 | 51 | int32_t backlog_size_; 52 | int32_t io_thread_number_; 53 | 54 | 55 | 56 | bool load_setting(std::shared_ptr setting_ptr) { 57 | const auto& setting = *setting_ptr; 58 | return load_setting(setting); 59 | } 60 | 61 | bool load_setting(const libconfig::Config& setting) { 62 | 63 | setting.lookupValue("http.bind_addr", bind_addr_); 64 | setting.lookupValue("http.bind_port", bind_port_); 65 | if (bind_addr_.empty() || bind_port_ <= 0) { 66 | roo::log_err("invalid http.bind_addr %s & http.bind_port %d found!", 67 | bind_addr_.c_str(), bind_port_); 68 | return false; 69 | } 70 | 71 | 72 | // IP访问白名单 73 | std::string ip_list; 74 | setting.lookupValue("http.safe_ip", ip_list); 75 | if (!ip_list.empty()) { 76 | std::vector ip_vec; 77 | std::set ip_set; 78 | boost::split(ip_vec, ip_list, boost::is_any_of(";")); 79 | for (auto it = ip_vec.begin(); it != ip_vec.cend(); ++it) { 80 | std::string tmp = boost::trim_copy(*it); 81 | if (tmp.empty()) 82 | continue; 83 | 84 | ip_set.insert(tmp); 85 | } 86 | 87 | std::swap(ip_set, safe_ip_); 88 | } 89 | if (!safe_ip_.empty()) { 90 | roo::log_warning("please notice safe_ip not empty, totally contain %d items", 91 | static_cast(safe_ip_.size())); 92 | } 93 | 94 | setting.lookupValue("http.backlog_size", backlog_size_); 95 | if (backlog_size_ < 0) { 96 | roo::log_err("invalid http.backlog_size %d found.", backlog_size_); 97 | return false; 98 | } 99 | 100 | setting.lookupValue("http.io_thread_pool_size", io_thread_number_); 101 | if (io_thread_number_ < 0) { 102 | roo::log_err("invalid http.io_thread_number %d", io_thread_number_); 103 | return false; 104 | } 105 | 106 | // once init,可以保证只被调用一次 107 | std::string server_version; 108 | setting.lookupValue("http.version", server_version); 109 | init_http_version_once(server_version); 110 | 111 | setting.lookupValue("http.ops_cancel_time_out", ops_cancel_time_out_); 112 | if (ops_cancel_time_out_ < 0) { 113 | roo::log_err("invalid http ops_cancel_time_out: %d", ops_cancel_time_out_); 114 | return false; 115 | } 116 | 117 | setting.lookupValue("http.session_cancel_time_out", session_cancel_time_out_); 118 | if (session_cancel_time_out_ < 0) { 119 | roo::log_err("invalid http session_cancel_time_out: %d", session_cancel_time_out_); 120 | return false; 121 | } 122 | 123 | setting.lookupValue("http.service_enable", service_enabled_); 124 | setting.lookupValue("http.service_speed", service_speed_); 125 | if (service_speed_ < 0) { 126 | roo::log_err("invalid http.service_speed: %d.", service_speed_); 127 | return false; 128 | } 129 | 130 | setting.lookupValue("http.service_concurrency", service_concurrency_); 131 | if (service_concurrency_ < 0) { 132 | roo::log_err("invalid http.service_concurrency: %d.", service_concurrency_); 133 | return false; 134 | } 135 | 136 | roo::log_info("HttpConf parse settings successfully!"); 137 | return true; 138 | } 139 | 140 | 141 | 142 | // 如果通过检查,不在受限列表中,就返回true放行 143 | bool check_safe_ip(const std::string& ip) { 144 | std::lock_guard lock(lock_); 145 | return (safe_ip_.empty() || (safe_ip_.find(ip) != safe_ip_.cend())); 146 | } 147 | 148 | // 限流模式使用 149 | bool get_http_service_token() { 150 | 151 | // 注意: 152 | // 如果关闭这个选项,则整个服务都不可用了(包括管理页面) 153 | // 此时如果需要变更除非重启整个服务,或者采用非web方式(比如通过发送命令)来恢复配置 154 | 155 | if (!service_enabled_) { 156 | roo::log_warning("http_service not enabled ..."); 157 | return false; 158 | } 159 | 160 | // 下面就不使用锁来保证严格的一致性了,因为非关键参数,过多的锁会影响性能 161 | if (service_speed_ == 0) // 没有限流 162 | return true; 163 | 164 | if (service_token_ <= 0) { 165 | roo::log_warning("http_service not speed over ..."); 166 | return false; 167 | } 168 | 169 | --service_token_; 170 | return true; 171 | } 172 | 173 | void withdraw_http_service_token() { // 支持将令牌还回去 174 | ++service_token_; 175 | } 176 | 177 | // 采用定时器来喂狗 178 | void feed_http_service_token() { 179 | service_token_ = service_speed_; 180 | } 181 | 182 | std::shared_ptr timed_feed_token_; 183 | void timed_feed_token_handler(const boost::system::error_code& ec) { 184 | 185 | if (service_speed_ == 0) { 186 | roo::log_warning("unlock speed jail, close the timer."); 187 | timed_feed_token_.reset(); 188 | return; 189 | } 190 | 191 | // 恢复token 192 | feed_http_service_token(); 193 | 194 | // 再次启动定时器 195 | timed_feed_token_->expires_from_now(boost::chrono::seconds(1)); // 1sec 196 | timed_feed_token_->async_wait( 197 | std::bind(&HttpConf::timed_feed_token_handler, this, std::placeholders::_1)); 198 | } 199 | 200 | public: 201 | 202 | // 构造函数和析构函数不能用friend改变访问性 203 | // 默认初始化,生成良好行为的数据 204 | HttpConf() : 205 | service_enabled_(true), 206 | service_speed_(0), 207 | service_token_(0), 208 | service_concurrency_(0), 209 | session_cancel_time_out_(0), 210 | ops_cancel_time_out_(0), 211 | lock_(), 212 | safe_ip_({ }), 213 | bind_addr_(), 214 | bind_port_(0), 215 | backlog_size_(0), 216 | io_thread_number_(0) { 217 | } 218 | 219 | ~HttpConf() = default; 220 | 221 | } __attribute__((aligned(4))); // end class HttpConf 222 | 223 | 224 | } // end namespace tzhttpd 225 | 226 | #endif //__TZHTTPD_HTTP_CONF_H__ 227 | -------------------------------------------------------------------------------- /HttpExecutor.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_HTTP_EXECUTOR_H__ 9 | #define __TZHTTPD_HTTP_EXECUTOR_H__ 10 | 11 | 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include "Executor.h" 20 | #include "HttpProto.h" 21 | #include "ServiceIf.h" 22 | #include "HttpHandler.h" 23 | 24 | 25 | namespace tzhttpd { 26 | 27 | class BasicAuth; 28 | 29 | class HttpExecutor : public ServiceIf { 30 | 31 | public: 32 | 33 | explicit HttpExecutor(const std::string& hostname) : 34 | hostname_(hostname), 35 | EMPTY_STRING(), 36 | conf_lock_(), 37 | conf_ptr_(), 38 | default_get_handler_(), 39 | redirect_handler_(), 40 | rwlock_(), 41 | handlers_() { 42 | } 43 | 44 | 45 | bool init(); 46 | 47 | void handle_http_request(std::shared_ptr http_req_instance)override; 48 | 49 | std::string instance_name()override { 50 | return hostname_; 51 | } 52 | 53 | ExecutorConf get_executor_conf() { 54 | return conf_ptr_->executor_conf_; 55 | } 56 | 57 | 58 | // override 59 | int add_get_handler(const std::string& uri_regex, const HttpGetHandler& handler, bool built_in)override; 60 | int add_post_handler(const std::string& uri_regex, const HttpPostHandler& handler, bool built_in)override; 61 | 62 | bool exist_handler(const std::string& uri_regex, enum HTTP_METHOD method)override; 63 | 64 | // 对于任何uri,可以先用这个接口进行卸载,然后再使用动态配置增加接口,借此实现接口的动态更新 65 | int drop_handler(const std::string& uri_regex, enum HTTP_METHOD method)override; 66 | 67 | 68 | 69 | int module_runtime(const libconfig::Config& conf)override; 70 | int module_status(std::string& module, std::string& key, std::string& value)override; 71 | 72 | 73 | private: 74 | 75 | struct CgiHandlerCfg { 76 | std::string url_; 77 | std::string dl_path_; 78 | }; 79 | bool parse_http_cgis(const libconfig::Setting& setting, const std::string& key, 80 | std::map& handlerCfg); 81 | 82 | bool load_http_cgis(const libconfig::Setting& setting); 83 | bool handle_virtual_host_conf(const libconfig::Setting& setting); 84 | 85 | // 基本按照handle_virtual_host_conf的思路进行类似处理, 86 | // 不过动态更新可能考虑会忽略一些配置和错误 87 | int handle_virtual_host_runtime_conf(const libconfig::Setting& setting); 88 | 89 | 90 | // 路由选择算法 91 | int do_find_handler(const enum HTTP_METHOD& method, 92 | const std::string& uri, 93 | HttpHandlerObjectPtr& handler); 94 | 95 | private: 96 | 97 | std::string hostname_; 98 | const std::string EMPTY_STRING; 99 | 100 | struct HttpExecutorConf { 101 | 102 | // 用来返回给Executor使用的,主要是线程伸缩相关的东西 103 | ExecutorConf executor_conf_; 104 | 105 | std::string http_docu_root_; 106 | std::vector http_docu_index_; 107 | 108 | std::string redirect_str_; 109 | std::string redirect_code_; 110 | std::string redirect_uri_; 111 | 112 | // 缓存策略规则 113 | std::map cache_controls_; 114 | 115 | // 压缩控制 116 | std::set compress_controls_; 117 | 118 | // 认证支持 119 | std::unique_ptr http_auth_; 120 | }; 121 | 122 | std::mutex conf_lock_; 123 | std::shared_ptr conf_ptr_; 124 | 125 | bool pass_basic_auth(const std::string& uri, const std::string auth_str); 126 | 127 | // default http get handler, important for web_server 128 | HttpHandlerObjectPtr default_get_handler_; 129 | int default_get_handler(const HttpParser& http_parser, std::string& response, 130 | std::string& status_line, std::vector& add_header); 131 | 132 | // 30x 重定向使用 133 | // http redirect part 134 | HttpHandlerObjectPtr redirect_handler_; 135 | int http_redirect_handler(const HttpParser& http_parser, const std::string& post_data, 136 | std::string& response, 137 | std::string& status_line, std::vector& add_header); 138 | 139 | 140 | // 使用vector保存handler,保证是先注册handler具有高优先级 141 | boost::shared_mutex rwlock_; 142 | std::vector> handlers_; 143 | 144 | }; 145 | 146 | } // end namespace tzhttpd 147 | 148 | 149 | #endif // __TZHTTPD_HTTP_EXECUTOR_H__ 150 | 151 | -------------------------------------------------------------------------------- /HttpHandler.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_HTTP_HANDLER_H__ 9 | #define __TZHTTPD_HTTP_HANDLER_H__ 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | 18 | #include "HttpProto.h" 19 | 20 | namespace tzhttpd { 21 | 22 | 23 | // 在HttpExecutor中保留的Handler 24 | 25 | class HttpParser; 26 | 27 | typedef std::function& add_header)> HttpGetHandler; 29 | typedef std::function& add_header)> HttpPostHandler; 31 | 32 | struct HttpHandlerObject { 33 | 34 | const std::string path_; 35 | 36 | int32_t success_count_; 37 | int32_t fail_count_; 38 | 39 | bool built_in_; // built_in handler,无法被卸载更新 40 | 41 | 42 | HttpGetHandler http_get_handler_; 43 | HttpPostHandler http_post_handler_; 44 | 45 | HttpHandlerObject(const std::string& path, 46 | const HttpGetHandler& get_handler, 47 | bool built_in = false) : 48 | path_(path), 49 | success_count_(0), fail_count_(0), 50 | built_in_(built_in), 51 | http_get_handler_(get_handler) { 52 | } 53 | 54 | HttpHandlerObject(const std::string& path, 55 | const HttpPostHandler& post_handler, 56 | bool built_in = false) : 57 | path_(path), 58 | success_count_(0), fail_count_(0), 59 | built_in_(built_in), 60 | http_post_handler_(post_handler) { 61 | } 62 | 63 | HttpHandlerObject(const std::string& path, 64 | const HttpGetHandler& get_handler, 65 | const HttpPostHandler& post_handler, 66 | bool built_in = false) : 67 | path_(path), 68 | success_count_(0), fail_count_(0), 69 | built_in_(built_in), 70 | http_get_handler_(get_handler), 71 | http_post_handler_(post_handler) { 72 | } 73 | 74 | void update_get_handler(const HttpGetHandler& get_handler) { 75 | http_get_handler_ = get_handler; 76 | } 77 | 78 | void update_post_handler(const HttpPostHandler& post_handler) { 79 | http_post_handler_ = post_handler; 80 | } 81 | }; 82 | 83 | typedef std::shared_ptr HttpHandlerObjectPtr; 84 | 85 | } // end namespace tzhttpd 86 | 87 | 88 | #endif // __TZHTTPD_HTTP_HANDLER_H__ 89 | 90 | -------------------------------------------------------------------------------- /HttpParser.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | // The GNU C++ standard library supports , but not until version 4.9.0. 16 | // (The headers were present in earlier versions, but were unusable.) 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include "CryptoUtil.h" 23 | #include "HttpProto.h" 24 | 25 | #include "HttpParser.h" 26 | 27 | namespace tzhttpd { 28 | 29 | 30 | 31 | std::string HttpParser::find_request_header(std::string option_name) const { 32 | 33 | if (option_name.empty()) 34 | return ""; 35 | 36 | std::map::const_iterator it; 37 | for (it = request_headers_.cbegin(); it != request_headers_.cend(); ++it) { 38 | if (boost::iequals(option_name, it->first)) 39 | return it->second; 40 | } 41 | 42 | return ""; 43 | } 44 | 45 | bool HttpParser::parse_request_uri() { 46 | 47 | // clear it first! 48 | request_uri_params_.CLEAR(); 49 | 50 | std::string uri = find_request_header(http_proto::header_options::request_uri); 51 | if (uri.empty()) { 52 | roo::log_err("Error found, head uri empty!"); 53 | return false; 54 | } 55 | 56 | std::string::size_type item_idx = 0; 57 | item_idx = uri.find_first_of("?"); 58 | if (item_idx == std::string::npos) { 59 | request_headers_.insert(std::make_pair(http_proto::header_options::request_path_info, 60 | CryptoUtil::url_decode(uri))); 61 | return true; 62 | } 63 | 64 | request_headers_.insert(std::make_pair(http_proto::header_options::request_path_info, 65 | CryptoUtil::url_decode(uri.substr(0, item_idx)))); 66 | request_headers_.insert(std::make_pair(http_proto::header_options::request_query_str, 67 | uri.substr(item_idx + 1))); 68 | 69 | // do query string parse, from cgicc 70 | std::string name, value; 71 | std::string::size_type pos; 72 | std::string::size_type oldPos = 0; 73 | std::string query_str = find_request_header(http_proto::header_options::request_query_str); 74 | 75 | while (true) { 76 | 77 | // Find the '=' separating the name from its value, 78 | // also have to check for '&' as its a common misplaced delimiter but is a delimiter none the less 79 | pos = query_str.find_first_of("&=", oldPos); 80 | 81 | // If no '=', we're finished 82 | if (std::string::npos == pos) 83 | break; 84 | 85 | // Decode the name 86 | // pos == '&', that means whatever is in name is the only name/value 87 | if (query_str.at(pos) == '&') { 88 | 89 | const char* pszData = query_str.c_str() + oldPos; 90 | while (*pszData == '&') { // eat up extraneous '&' 91 | ++pszData; 92 | ++oldPos; 93 | } 94 | 95 | if (oldPos >= pos) { // its all &'s 96 | oldPos = ++pos; 97 | continue; 98 | } 99 | 100 | // this becomes an name with an empty value 101 | name = CryptoUtil::url_decode(query_str.substr(oldPos, pos - oldPos)); 102 | request_uri_params_.PUSH_BACK(name, std::string("")); 103 | oldPos = ++pos; 104 | continue; 105 | } 106 | 107 | // else find the value 108 | name = CryptoUtil::url_decode(query_str.substr(oldPos, pos - oldPos)); 109 | oldPos = ++pos; 110 | 111 | // Find the '&' or ';' separating subsequent name/value pairs 112 | pos = query_str.find_first_of(";&", oldPos); 113 | 114 | // Even if an '&' wasn't found the rest of the string is a value 115 | value = CryptoUtil::url_decode(query_str.substr(oldPos, pos - oldPos)); 116 | 117 | // Store the pair 118 | request_uri_params_.PUSH_BACK(name, value); 119 | 120 | if (std::string::npos == pos) 121 | break; 122 | 123 | // Update parse position 124 | oldPos = ++pos; 125 | } 126 | 127 | return true; 128 | } 129 | 130 | std::string HttpParser::normalize_request_uri(const std::string& uri) { 131 | 132 | // 因为Linux文件系统是大小写敏感的,所以这里不会进行uri大小写的规则化 133 | const std::string src = boost::algorithm::trim_copy(uri); 134 | std::string result; 135 | result.reserve(src.size()); 136 | 137 | for (std::string::const_iterator iter = src.begin(); iter != src.end(); ++iter) { 138 | if (*iter == '/') { 139 | while (std::distance(iter, src.end()) >= 1 && *(iter + 1) == '/') 140 | ++iter; 141 | } 142 | 143 | result.append(1, *iter); //store it! 144 | } 145 | 146 | return result; 147 | } 148 | 149 | bool HttpParser::do_parse_request(const std::string& header) { 150 | 151 | request_headers_.clear(); 152 | request_headers_.insert(std::make_pair(http_proto::header_options::request_body, 153 | header.substr(header.find("\r\n\r\n") + 4))); 154 | std::string header_part = header.substr(0, header.find("\r\n\r\n") + 4); 155 | 156 | std::istringstream resp(header_part); 157 | std::string item; 158 | std::string::size_type index; 159 | 160 | while (std::getline(resp, item) && item != "\r") { 161 | 162 | // 重新编写,支持url中带有特殊字符 163 | if (boost::istarts_with(item, "GET ") || 164 | boost::istarts_with(item, "HEAD ") || 165 | boost::istarts_with(item, "POST ") || 166 | boost::istarts_with(item, "PUT ") || 167 | boost::istarts_with(item, "DELETE ") || 168 | boost::istarts_with(item, "CONNECT ") || 169 | boost::istarts_with(item, "OPTIONS ") || 170 | boost::istarts_with(item, "TRACE ") || 171 | boost::istarts_with(item, "MOVE ") || 172 | boost::istarts_with(item, "COPY ") || 173 | boost::istarts_with(item, "LINK ") || 174 | boost::istarts_with(item, "UNLINK ") || 175 | boost::istarts_with(item, "WRAPPED ") || 176 | boost::istarts_with(item, "Extension-method ") 177 | ) { 178 | // HTTP 标准头 179 | boost::smatch what; 180 | if (boost::regex_match(item, what, 181 | boost::regex("([a-zA-Z]+)[ ]+([^ ]+)([ ]+(.*))?"))) { 182 | request_headers_.insert(std::make_pair(http_proto::header_options::request_method, 183 | boost::algorithm::trim_copy( 184 | boost::to_upper_copy(std::string(what[1]))))); 185 | 186 | // HTTP Method 187 | if (boost::iequals(find_request_header(http_proto::header_options::request_method), "GET")) { 188 | method_ = HTTP_METHOD::GET; 189 | } else if (boost::iequals(find_request_header(http_proto::header_options::request_method), "POST")) { 190 | method_ = HTTP_METHOD::POST; 191 | } else if (boost::iequals(find_request_header(http_proto::header_options::request_method), "OPTIONS")) { 192 | method_ = HTTP_METHOD::OPTIONS; 193 | } else { 194 | method_ = HTTP_METHOD::UNDETECTED; 195 | } 196 | 197 | uri_ = normalize_request_uri(std::string(what[2])); 198 | request_headers_.insert(std::make_pair(http_proto::header_options::request_uri, uri_)); 199 | request_headers_.insert(std::make_pair(http_proto::header_options::http_version, 200 | boost::algorithm::trim_copy(std::string(what[3])))); 201 | 202 | version_ = boost::algorithm::trim_copy(std::string(what[3])); 203 | } 204 | } else { 205 | index = item.find(':', 0); 206 | if (index != std::string::npos) { // 直接Key-Value 207 | request_headers_.insert(std::make_pair( 208 | boost::algorithm::trim_copy(item.substr(0, index)), 209 | boost::algorithm::trim_copy(item.substr(index + 1)))); 210 | } else { 211 | roo::log_err("unabled to handle line: %s", item.c_str()); 212 | } 213 | } 214 | } 215 | 216 | return true; 217 | } 218 | 219 | } // end namespace tzhttpd 220 | -------------------------------------------------------------------------------- /HttpParser.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_HTTP_PARSER_H__ 9 | #define __TZHTTPD_HTTP_PARSER_H__ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include "CryptoUtil.h" 23 | #include "HttpProto.h" 24 | 25 | namespace tzhttpd { 26 | 27 | typedef roo::PairVec UriParamContainer; 28 | 29 | class HttpParser { 30 | 31 | __noncopyable__(HttpParser) 32 | 33 | public: 34 | HttpParser() : 35 | request_headers_(), 36 | request_uri_params_(), 37 | method_(HTTP_METHOD::UNDETECTED) { 38 | } 39 | 40 | enum HTTP_METHOD get_method() const { 41 | return method_; 42 | } 43 | 44 | std::string get_uri() const { 45 | return uri_; 46 | } 47 | 48 | std::string get_version() const { 49 | return version_; 50 | } 51 | 52 | bool parse_request_header(const char* header_ptr) { 53 | if (!header_ptr || !strlen(header_ptr) || !strstr(header_ptr, "\r\n\r\n")) { 54 | roo::log_err("check raw header package failed ..."); 55 | return false; 56 | } 57 | 58 | return do_parse_request(std::string(header_ptr)); 59 | } 60 | 61 | bool parse_request_header(const std::string& header) { 62 | return do_parse_request(header); 63 | } 64 | 65 | std::string find_request_header(std::string option_name) const; 66 | bool parse_request_uri(); 67 | 68 | const UriParamContainer& get_request_uri_params() const { 69 | return request_uri_params_; 70 | } 71 | 72 | std::string get_request_uri_params_string() const { 73 | return request_uri_params_.SERIALIZE(); 74 | } 75 | 76 | bool get_request_uri_param(const std::string& key, std::string& value) const { 77 | return request_uri_params_.FIND(key, value); 78 | } 79 | 80 | std::string char_to_hex(char c) const { 81 | 82 | std::string result; 83 | char first, second; 84 | 85 | first = static_cast((c & 0xF0) / 16); 86 | first += static_cast(first > 9 ? 'A' - 10 : '0'); 87 | second = c & 0x0F; 88 | second += static_cast(second > 9 ? 'A' - 10 : '0'); 89 | 90 | result.append(1, first); 91 | result.append(1, second); 92 | return result; 93 | } 94 | 95 | char hex_to_char(char first, char second) const { 96 | int digit; 97 | 98 | digit = (first >= 'A' ? ((first & 0xDF) - 'A') + 10 : (first - '0')); 99 | digit *= 16; 100 | digit += (second >= 'A' ? ((second & 0xDF) - 'A') + 10 : (second - '0')); 101 | return static_cast(digit); 102 | } 103 | 104 | 105 | private: 106 | std::string normalize_request_uri(const std::string& uri); 107 | bool do_parse_request(const std::string& header); 108 | 109 | private: 110 | 111 | std::map request_headers_; 112 | UriParamContainer request_uri_params_; 113 | 114 | enum HTTP_METHOD method_; 115 | std::string version_; 116 | std::string uri_; 117 | 118 | public: 119 | boost::asio::ip::tcp::endpoint remote_; 120 | }; 121 | 122 | } // end namespace tzhttpd 123 | 124 | #endif // __TZHTTPD_HTTP_PARSER_H__ 125 | -------------------------------------------------------------------------------- /HttpProto.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include "HttpProto.h" 14 | 15 | 16 | namespace tzhttpd { 17 | 18 | namespace http_handler { 19 | extern std::string http_server_version; 20 | } // end namespace http_handler 21 | 22 | namespace http_proto { 23 | 24 | struct header { 25 | std::string name; 26 | std::string value; 27 | }; 28 | 29 | 30 | /** 31 | * 由于最终的底层都是调用c_str()发送的,所以这里不添加额外的字符 32 | */ 33 | string http_response_generate(const string& content, const string& stat_str, 34 | bool keepalive, const std::vector& additional_header) { 35 | 36 | std::vector
headers(5); 37 | 38 | // reply fixed header 39 | headers[0].name = "Server"; 40 | headers[0].value = "tzhttpd server/" + http_handler::http_server_version; 41 | headers[1].name = "Date"; 42 | 43 | std::time_t now = boost::chrono::system_clock::to_time_t(boost::chrono::system_clock::now()); 44 | // std::string time_str = std::string(std::ctime(&now, NULL)); 45 | char mbstr[32]{}; 46 | std::strftime(mbstr, sizeof(mbstr), "%F %T", std::localtime(&now)); 47 | headers[1].value = std::string(mbstr); 48 | // headers[1].value = time_str.erase(time_str.find('\n')); // ctime 会在末尾增加一个 \n 49 | // headers[1].value = to_simple_string(second_clock::universal_time()) + " GMT"; 50 | 51 | headers[2].name = "Content-Length"; 52 | headers[2].value = std::to_string(static_cast(content.size())); 53 | 54 | headers[3].name = "Connection"; 55 | if (keepalive) { 56 | headers[3].value = "keep-alive"; // 长连接 57 | } else { 58 | headers[3].value = "close"; // 短连接 59 | } 60 | 61 | headers[4].name = "Access-Control-Allow-Origin"; 62 | headers[4].value = "*"; 63 | 64 | string str = stat_str; 65 | str += header_crlf_str; 66 | for (size_t i = 0; i < headers.size(); ++i) { 67 | str += headers[i].name; 68 | str += header_name_value_separator_str; 69 | str += headers[i].value; 70 | str += header_crlf_str; 71 | } 72 | 73 | for (auto iter = additional_header.begin(); iter != additional_header.end(); ++iter) { 74 | str += *iter; 75 | str += header_crlf_str; 76 | } 77 | 78 | str += header_crlf_str; 79 | str += content; 80 | 81 | return str; 82 | } 83 | 84 | string http_response_generate(const char* data, size_t len, const string& stat_str, 85 | bool keepalive, const std::vector& additional_header) { 86 | 87 | std::string content(data, len); 88 | return http_response_generate(content, stat_str, keepalive, additional_header); 89 | } 90 | 91 | 92 | static std::string get_status_content(enum StatusCode code) { 93 | const auto iter = status_code_strings.find(code); 94 | if (iter != status_code_strings.end()) { 95 | return iter->second; 96 | } 97 | 98 | return ""; 99 | } 100 | 101 | string http_std_response_generate(const std::string& http_ver, const std::string& stat_str, 102 | enum StatusCode code, bool keepalive) { 103 | 104 | // 不需要返回body响应内容的情况 105 | if (code == StatusCode::success_no_content) { 106 | 107 | // 返回的内容允许no-cors访问 108 | std::vector hd { 109 | // "Access-Control-Allow-Origin: *", // 已经在默认Header中了 110 | "Access-Control-Allow-Methods: POST, GET, OPTIONS", 111 | "Access-Control-Allow-Headers: Accept, Accept-Language, Content-Language, Content-Type, X-PINGOTHER, " 112 | "Reserved-Header1, Reserved-Header2, Reserved-Header3, Reserved-Header4, Reserved-Header5 ", 113 | "Access-Control-Max-Age: 86400", 114 | "Vary: Accept-Encoding, Origin", 115 | }; 116 | 117 | return http_response_generate("", stat_str, keepalive, hd); 118 | } 119 | 120 | 121 | std::stringstream content_ss; 122 | content_ss << "" 123 | << stat_str 124 | << "" 125 | << "

" 126 | << stat_str 127 | << "

"; 128 | 129 | std::string content = content_ss.str(); 130 | std::vector hd {"Content-Type: text/html"}; 131 | 132 | return http_response_generate(content, stat_str, keepalive, hd); 133 | } 134 | 135 | 136 | static const std::map content_types = { 137 | { ".avi", "Content-Type: video/x-msvideo" }, 138 | { ".bin", "Content-Type: application/octet-stream" }, 139 | { ".bmp", "Content-Type: image/bmp" }, 140 | { ".bz", "Content-Type: application/x-bzip" }, 141 | { ".bz2", "Content-Type: application/x-bzip2" }, 142 | { ".csh", "Content-Type: application/x-csh" }, 143 | { ".css", "Content-Type: text/css" }, 144 | { ".csv", "Content-Type: text/csv" }, 145 | { ".doc", "Content-Type: application/msword" }, 146 | { ".docx", "Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, 147 | { ".eot", "Content-Type: application/vnd.ms-fontobject" }, 148 | { ".epub", "Content-Type: application/epub+zip" }, 149 | { ".gif", "Content-Type: image/gif" }, 150 | { ".htm", "Content-Type: text/html" }, 151 | { ".html", "Content-Type: text/html" }, 152 | { ".ico", "Content-Type: image/x-icon" }, 153 | { ".jar", "Content-Type: application/java-archive" }, 154 | { ".jpeg", "Content-Type: image/jpeg" }, 155 | { ".jpg", "Content-Type: image/jpeg" }, 156 | { ".js", "Content-Type: application/javascript" }, 157 | { ".json", "Content-Type: application/json" }, 158 | { ".mid", "Content-Type: audio/midi" }, 159 | { ".midi", "Content-Type: audio/midi" }, 160 | { ".mpeg", "Content-Type: video/mpeg" }, 161 | { ".odp", "Content-Type: application/vnd.oasis.opendocument.presentation" }, 162 | { ".ods", "Content-Type: application/vnd.oasis.opendocument.spreadsheet" }, 163 | { ".odt", "Content-Type: application/vnd.oasis.opendocument.text" }, 164 | { ".oga", "Content-Type: audio/ogg" }, 165 | { ".ogv", "Content-Type: video/ogg" }, 166 | { ".ogx", "Content-Type: application/ogg" }, 167 | { ".otf", "Content-Type: font/otf" }, 168 | { ".png", "Content-Type: image/png" }, 169 | { ".pdf", "Content-Type: application/pdf" }, 170 | { ".ppt", "Content-Type: application/vnd.ms-powerpoint" }, 171 | { ".pptx", "Content-Type: application/vnd.openxmlformats-officedocument.presentationml.presentation" }, 172 | { ".rar", "Content-Type: application/x-rar-compressed" }, 173 | { ".rtf", "Content-Type: application/rtf" }, 174 | { ".sh", "Content-Type: application/x-sh" }, 175 | { ".svg", "Content-Type: image/svg+xml" }, 176 | { ".swf", "Content-Type: application/x-shockwave-flash" }, 177 | { ".tar", "Content-Type: application/x-tar" }, 178 | { ".tif", "Content-Type: image/tiff" }, 179 | { ".tiff", "Content-Type: image/tiff" }, 180 | { ".ts", "Content-Type: application/typescript" }, 181 | { ".ttf", "Content-Type: font/ttf" }, 182 | { ".txt", "Content-Type: text/plain" }, 183 | { ".vsd", "Content-Type: application/vnd.visio" }, 184 | { ".wav", "Content-Type: audio/wav" }, 185 | { ".woff", "Content-Type: font/woff" }, 186 | { ".woff2", "Content-Type: font/woff2" }, 187 | { ".xhtml", "Content-Type: application/xhtml+xml" }, 188 | { ".xls", "Content-Type: application/vnd.ms-excel" }, 189 | { ".xlsx", "Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, 190 | { ".xml", "Content-Type: application/xml" }, 191 | { ".zip", "Content-Type: application/zip" }, 192 | { ".3gp", "Content-Type: video/3gpp" }, 193 | { ".7z", "Content-Type: application/x-7z-compressed" }, 194 | }; 195 | 196 | std::string find_content_type(const std::string& suffix) { 197 | auto iter = content_types.find(suffix); 198 | if (iter != content_types.cend()) { 199 | return iter->second; 200 | } 201 | 202 | return ""; 203 | } 204 | 205 | } // end namespace http_proto 206 | } // end namespace tzhttpd 207 | 208 | -------------------------------------------------------------------------------- /HttpProto.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_HTTP_PROTO_H__ 9 | #define __TZHTTPD_HTTP_PROTO_H__ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace tzhttpd { 16 | 17 | // currently supported http methods 18 | enum class HTTP_METHOD : uint8_t { 19 | GET = 1, 20 | POST = 2, 21 | OPTIONS = 3, 22 | 23 | ALL = 98, // internal use 24 | UNDETECTED = 99, 25 | }; 26 | 27 | static inline 28 | std::string HTTP_METHOD_STRING(enum HTTP_METHOD method) { 29 | if (method == HTTP_METHOD::GET) { 30 | return "GET"; 31 | } else if (method == HTTP_METHOD::POST) { 32 | return "POST"; 33 | } else if (method == HTTP_METHOD::OPTIONS) { 34 | return "OPTIONS"; 35 | } 36 | 37 | return "UNDETECTED_METHOD"; 38 | } 39 | 40 | namespace http_proto { 41 | 42 | using std::string; 43 | 44 | static const string content_ok = "{OK}"; 45 | 46 | static const string content_error = 47 | "" 48 | "500 Internel Server Error" 49 | "" 50 | "

500 Internel Server Error

" 51 | "
tzhttpd
" 52 | "" 53 | ""; 54 | 55 | static const string content_bad_request = 56 | "" 57 | "400 Bad Request" 58 | "" 59 | "

400 Bad Request

" 60 | "
tzhttpd
" 61 | "" 62 | ""; 63 | 64 | static const string content_forbidden = 65 | "" 66 | "403 Forbidden" 67 | "" 68 | "

403 Forbidden

" 69 | "
tzhttpd
" 70 | "" 71 | ""; 72 | 73 | static const string content_not_found = 74 | "" 75 | "404 Not Found" 76 | "" 77 | "

404 Not Found

" 78 | "
tzhttpd
" 79 | "" 80 | ""; 81 | 82 | static const string content_301 = 83 | "" 84 | "301 Moved Permanently" 85 | "" 86 | "

301 Moved Permanently

" 87 | "
tzhttpd
" 88 | "" 89 | ""; 90 | 91 | // 实际在HTTP协议上,302不应该改变请求的方式,303明确表示应该用 92 | // GET方式访问重定向的Location。现在的实际上很多302被当做303处理了 93 | static const string content_302 = 94 | "" 95 | "302 Found" 96 | "" 97 | "

302 Found

" 98 | "
tzhttpd
" 99 | "" 100 | ""; 101 | 102 | namespace header_options { // header key words 103 | 104 | static const std::string request_method("request_method_"); // (GET/POST) 105 | static const std::string request_uri("request_uri_"); // raw uri, http://127.0.0.1:8900/docs/index.html?aa=bb 106 | static const std::string request_path_info("request_path_info_"); // docs/index.html 107 | static const std::string request_query_str("request_query_str_"); // aa == bb 108 | static const std::string http_version("http_version_"); // HTTP/1.0|HTTP/1.1 109 | static const std::string request_body("request_body_"); // used for post 110 | 111 | static const std::string host("Host"); 112 | static const std::string accept("Accept"); 113 | static const std::string auth("Authorization"); 114 | static const std::string range("Range"); 115 | static const std::string cookie("Cookie"); 116 | static const std::string referer("Referer"); 117 | static const std::string user_agent("User-Agent"); 118 | static const std::string content_type("Content-Type"); 119 | static const std::string content_length("Content-Length"); 120 | static const std::string content_range("Content-Range"); 121 | static const std::string connection("Connection"); 122 | static const std::string proxy_connection("Proxy-Connection"); 123 | static const std::string accept_encoding("Accept-Encoding"); 124 | static const std::string transfer_encoding("Transfer-Encoding"); 125 | static const std::string content_encoding("Content-Encoding"); 126 | 127 | } // namespace header_options 128 | 129 | 130 | enum class StatusCode : uint16_t { 131 | unknown = 0, 132 | information_continue = 100, 133 | information_switching_protocols, 134 | information_processing, 135 | success_ok = 200, 136 | success_created, 137 | success_accepted, 138 | success_non_authoritative_information, 139 | success_no_content, 140 | success_reset_content, 141 | success_partial_content, 142 | success_multi_status, 143 | success_already_reported, 144 | success_im_used = 226, 145 | redirection_multiple_choices = 300, 146 | redirection_moved_permanently, 147 | redirection_found, 148 | redirection_see_other, 149 | redirection_not_modified, 150 | redirection_use_proxy, 151 | redirection_switch_proxy, 152 | redirection_temporary_redirect, 153 | redirection_permanent_redirect, 154 | client_error_bad_request = 400, 155 | client_error_unauthorized, 156 | client_error_payment_required, 157 | client_error_forbidden, 158 | client_error_not_found, 159 | client_error_method_not_allowed, 160 | client_error_not_acceptable, 161 | client_error_proxy_authentication_required, 162 | client_error_request_timeout, 163 | client_error_conflict, 164 | client_error_gone, 165 | client_error_length_required, 166 | client_error_precondition_failed, 167 | client_error_payload_too_large, 168 | client_error_uri_too_long, 169 | client_error_unsupported_media_type, 170 | client_error_range_not_satisfiable, 171 | client_error_expectation_failed, 172 | client_error_im_a_teapot, 173 | client_error_misdirection_required = 421, 174 | client_error_unprocessable_entity, 175 | client_error_locked, 176 | client_error_failed_dependency, 177 | client_error_upgrade_required = 426, 178 | client_error_precondition_required = 428, 179 | client_error_too_many_requests, 180 | client_error_request_header_fields_too_large = 431, 181 | client_error_unavailable_for_legal_reasons = 451, 182 | server_error_internal_server_error = 500, 183 | server_error_not_implemented, 184 | server_error_bad_gateway, 185 | server_error_service_unavailable, 186 | server_error_gateway_timeout, 187 | server_error_http_version_not_supported, 188 | server_error_variant_also_negotiates, 189 | server_error_insufficient_storage, 190 | server_error_loop_detected, 191 | server_error_not_extended = 510, 192 | server_error_network_authentication_required 193 | }; 194 | 195 | struct StatusCodeCompare { 196 | bool operator ()(const enum StatusCode& lhs, const enum StatusCode& rhs) const { 197 | return static_cast(lhs) < static_cast(rhs); 198 | } 199 | }; 200 | 201 | static const std::map status_code_strings = { 202 | { StatusCode::unknown, "" }, 203 | { StatusCode::information_continue, "100 Continue" }, 204 | { StatusCode::information_switching_protocols, "101 Switching Protocols" }, 205 | { StatusCode::information_processing, "102 Processing" }, 206 | { StatusCode::success_ok, "200 OK" }, 207 | { StatusCode::success_created, "201 Created" }, 208 | { StatusCode::success_accepted, "202 Accepted" }, 209 | { StatusCode::success_non_authoritative_information, "203 Non-Authoritative Information" }, 210 | { StatusCode::success_no_content, "204 No Content" }, 211 | { StatusCode::success_reset_content, "205 Reset Content" }, 212 | { StatusCode::success_partial_content, "206 Partial Content" }, 213 | { StatusCode::success_multi_status, "207 Multi-Status" }, 214 | { StatusCode::success_already_reported, "208 Already Reported" }, 215 | { StatusCode::success_im_used, "226 IM Used" }, 216 | { StatusCode::redirection_multiple_choices, "300 Multiple Choices" }, 217 | { StatusCode::redirection_moved_permanently, "301 Moved Permanently" }, 218 | { StatusCode::redirection_found, "302 Found" }, 219 | { StatusCode::redirection_see_other, "303 See Other" }, 220 | { StatusCode::redirection_not_modified, "304 Not Modified" }, 221 | { StatusCode::redirection_use_proxy, "305 Use Proxy" }, 222 | { StatusCode::redirection_switch_proxy, "306 Switch Proxy" }, 223 | { StatusCode::redirection_temporary_redirect, "307 Temporary Redirect" }, 224 | { StatusCode::redirection_permanent_redirect, "308 Permanent Redirect" }, 225 | { StatusCode::client_error_bad_request, "400 Bad Request" }, 226 | { StatusCode::client_error_unauthorized, "401 Unauthorized" }, 227 | { StatusCode::client_error_payment_required, "402 Payment Required" }, 228 | { StatusCode::client_error_forbidden, "403 Forbidden" }, 229 | { StatusCode::client_error_not_found, "404 Not Found" }, 230 | { StatusCode::client_error_method_not_allowed, "405 Method Not Allowed" }, 231 | { StatusCode::client_error_not_acceptable, "406 Not Acceptable" }, 232 | { StatusCode::client_error_proxy_authentication_required, "407 Proxy Authentication Required" }, 233 | { StatusCode::client_error_request_timeout, "408 Request Timeout" }, 234 | { StatusCode::client_error_conflict, "409 Conflict" }, 235 | { StatusCode::client_error_gone, "410 Gone" }, 236 | { StatusCode::client_error_length_required, "411 Length Required" }, 237 | { StatusCode::client_error_precondition_failed, "412 Precondition Failed" }, 238 | { StatusCode::client_error_payload_too_large, "413 Payload Too Large" }, 239 | { StatusCode::client_error_uri_too_long, "414 URI Too Long" }, 240 | { StatusCode::client_error_unsupported_media_type, "415 Unsupported Media Type" }, 241 | { StatusCode::client_error_range_not_satisfiable, "416 Range Not Satisfiable" }, 242 | { StatusCode::client_error_expectation_failed, "417 Expectation Failed" }, 243 | { StatusCode::client_error_im_a_teapot, "418 I'm a teapot" }, 244 | { StatusCode::client_error_misdirection_required, "421 Misdirected Request" }, 245 | { StatusCode::client_error_unprocessable_entity, "422 Unprocessable Entity" }, 246 | { StatusCode::client_error_locked, "423 Locked" }, 247 | { StatusCode::client_error_failed_dependency, "424 Failed Dependency" }, 248 | { StatusCode::client_error_upgrade_required, "426 Upgrade Required" }, 249 | { StatusCode::client_error_precondition_required, "428 Precondition Required" }, 250 | { StatusCode::client_error_too_many_requests, "429 Too Many Requests" }, 251 | { StatusCode::client_error_request_header_fields_too_large, "431 Request Header Fields Too Large" }, 252 | { StatusCode::client_error_unavailable_for_legal_reasons, "451 Unavailable For Legal Reasons" }, 253 | { StatusCode::server_error_internal_server_error, "500 Internal Server Error" }, 254 | { StatusCode::server_error_not_implemented, "501 Not Implemented" }, 255 | { StatusCode::server_error_bad_gateway, "502 Bad Gateway" }, 256 | { StatusCode::server_error_service_unavailable, "503 Service Unavailable" }, 257 | { StatusCode::server_error_gateway_timeout, "504 Gateway Timeout" }, 258 | { StatusCode::server_error_http_version_not_supported, "505 HTTP Version Not Supported" }, 259 | { StatusCode::server_error_variant_also_negotiates, "506 Variant Also Negotiates" }, 260 | { StatusCode::server_error_insufficient_storage, "507 Insufficient Storage" }, 261 | { StatusCode::server_error_loop_detected, "508 Loop Detected" }, 262 | { StatusCode::server_error_not_extended, "510 Not Extended" }, 263 | { StatusCode::server_error_network_authentication_required, "511 Network Authentication Required" } 264 | }; 265 | 266 | 267 | inline static std::string generate_response_status_line(const std::string http_version, enum StatusCode code) { 268 | const auto iter = status_code_strings.find(code); 269 | if (iter != status_code_strings.end()) { 270 | return http_version + " " + iter->second; 271 | } 272 | 273 | return ""; 274 | } 275 | 276 | static const char header_name_value_separator[] = { ':', ' ' }; 277 | static const char header_crlf[] = { '\r', '\n' }; 278 | static const char header_crlfcrlf[] = { '\r', '\n', '\r', '\n' }; 279 | 280 | static string header_name_value_separator_str = ": "; 281 | static string header_crlf_str = "\r\n"; 282 | static string header_crlfcrlf_str = "\r\n\r\n"; 283 | 284 | std::string find_content_type(const std::string& suffix); 285 | 286 | /** 287 | * 由于最终的底层都是调用c_str()发送的,所以这里不添加额外的字符 288 | */ 289 | string http_response_generate(const std::string& content, const std::string& stat_str, 290 | bool keepalive, const std::vector& additional_header); 291 | string http_response_generate(const char* data, size_t len, const string& stat_str, 292 | bool keepalive, const std::vector& additional_header); 293 | 294 | string http_std_response_generate(const std::string& http_ver, const std::string& stat_str, 295 | enum StatusCode code, bool keepalive); 296 | 297 | } // end namespace http_proto 298 | 299 | } // end namespace tzhttpd 300 | 301 | #endif //__TZHTTPD_HTTP_PROTO_H__ 302 | -------------------------------------------------------------------------------- /HttpReqInstance.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_HTTP_REQ_INSTANCE_H__ 9 | #define __TZHTTPD_HTTP_REQ_INSTANCE_H__ 10 | 11 | #include 12 | 13 | #include "TcpConnAsync.h" 14 | #include "HttpProto.h" 15 | 16 | namespace tzhttpd { 17 | 18 | struct HttpReqInstance { 19 | HttpReqInstance(enum HTTP_METHOD method, 20 | std::shared_ptr socket, 21 | const std::string& hostname, 22 | const std::string& uri, 23 | std::shared_ptr http_parser, 24 | const std::string& data) : 25 | method_(method), 26 | hostname_(hostname), 27 | uri_(uri), 28 | http_parser_(http_parser), 29 | data_(data), 30 | start_(::time(NULL)), 31 | full_socket_(socket) { 32 | } 33 | 34 | const HTTP_METHOD method_; 35 | const std::string hostname_; 36 | const std::string uri_; 37 | std::shared_ptr http_parser_; // move here 38 | std::string data_; // post data, 如果有的话 39 | 40 | time_t start_; // 请求创建的时间 41 | std::weak_ptr full_socket_; // 可能socket提前在网络层已经释放了 42 | 43 | 44 | std::string str() { 45 | std::stringstream ss; 46 | 47 | ss << "HTTP: "; 48 | ss << "method:" << HTTP_METHOD_STRING(method_) << ", "; 49 | ss << "hostname:" << hostname_ << ", "; 50 | ss << "uri:" << uri_ << ", "; 51 | ss << "data:" << data_; 52 | 53 | return ss.str(); 54 | } 55 | 56 | void http_std_response(enum http_proto::StatusCode code) { 57 | 58 | if (auto sock = full_socket_.lock()) { 59 | sock->fill_std_http_for_send(http_parser_, code); 60 | sock->do_write(http_parser_); 61 | return; 62 | } 63 | 64 | roo::log_err("connection already released before."); 65 | } 66 | 67 | void http_response(const std::string& response_str, 68 | const std::string& status_str, 69 | const std::vector& headers) { 70 | 71 | if (auto sock = full_socket_.lock()) { 72 | sock->fill_http_for_send(http_parser_, response_str, status_str, headers); 73 | sock->do_write(http_parser_); 74 | return; 75 | } 76 | 77 | roo::log_err("connection already released before."); 78 | } 79 | }; 80 | 81 | } // tzhttpd 82 | 83 | 84 | #endif // __TZHTTPD_HTTP_REQ_INSTANCE_H__ 85 | -------------------------------------------------------------------------------- /HttpServer.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "TcpConnAsync.h" 26 | 27 | #include "HttpProto.h" 28 | #include "HttpParser.h" 29 | #include "HttpHandler.h" 30 | #include "Dispatcher.h" 31 | 32 | #include "Global.h" 33 | 34 | 35 | #include "HttpConf.h" 36 | #include "HttpServer.h" 37 | 38 | using namespace boost::asio; 39 | 40 | namespace tzhttpd { 41 | 42 | typedef TcpConnAsync ConnType; 43 | 44 | // HTTP_SERVER的版本信息 45 | namespace http_handler { 46 | extern std::string http_server_version; 47 | } 48 | 49 | static void init_http_version(const std::string& server_version) { 50 | http_handler::http_server_version = server_version; 51 | } 52 | 53 | std::once_flag http_version_once; 54 | void init_http_version_once(const std::string& server_version) { 55 | if (!server_version.empty()) 56 | std::call_once(http_version_once, init_http_version, server_version); 57 | } 58 | 59 | // Hash使用 60 | static const size_t bucket_size_ = 0xFF; 61 | static size_t bucket_hash_index_call(const std::shared_ptr& ptr) { 62 | return std::hash()(ptr.get()); 63 | } 64 | 65 | 66 | ////// 67 | class HttpServerImpl { 68 | 69 | __noncopyable__(HttpServerImpl) 70 | 71 | friend class TcpConnAsync; // can not work with typedef, ugly ... 72 | friend class HttpServer; 73 | 74 | public: 75 | 76 | // 77 | // ALL AVAILABLE PUBLIC API CALL HERE 78 | // 79 | 80 | /// Construct the server to listen on the specified TCP address and port 81 | HttpServerImpl(const std::string& cfgfile, const std::string& instance_name, 82 | HttpServer& server); 83 | ~HttpServerImpl() = default; 84 | 85 | bool init(); 86 | int service_start(); 87 | int service_stop_graceful(); 88 | int service_join(); 89 | 90 | private: 91 | const std::string instance_name_; 92 | HttpServer& super_server_; 93 | 94 | // boost::asio 95 | io_service io_service_; 96 | ip::tcp::endpoint ep_; // 侦听地址信息 97 | std::unique_ptr acceptor_; 98 | 99 | const std::string cfgfile_; 100 | std::shared_ptr conf_ptr_; 101 | 102 | void do_accept(); 103 | void accept_handler(const boost::system::error_code& ec, 104 | std::shared_ptr ptr); 105 | 106 | public: 107 | 108 | roo::ThreadPool io_service_threads_; 109 | void io_service_run(roo::ThreadObjPtr ptr); // main task loop 110 | 111 | public: 112 | int module_runtime(const libconfig::Config& setting); 113 | int module_status(std::string& module, std::string& key, std::string& value); 114 | }; 115 | 116 | 117 | HttpServerImpl::HttpServerImpl(const std::string& cfgfile, const std::string& instance_name, 118 | HttpServer& server) : 119 | instance_name_(instance_name), 120 | super_server_(server), 121 | io_service_(), 122 | ep_(), 123 | acceptor_(), 124 | cfgfile_(cfgfile), 125 | conf_ptr_(std::make_shared()) { 126 | 127 | (void)Global::instance(); 128 | (void)Dispatcher::instance(); 129 | 130 | if (!conf_ptr_) 131 | throw roo::ConstructException("Create HttpConf failed."); 132 | 133 | if (!Global::instance().init(cfgfile_)) 134 | throw roo::ConstructException("Init Global instance failed."); 135 | } 136 | 137 | 138 | extern 139 | bool system_manage_page_init(); 140 | 141 | bool HttpServerImpl::init() { 142 | 143 | auto setting_ptr = Global::instance().setting_ptr()->get_setting(); 144 | if (!setting_ptr) { 145 | roo::log_err("Setting return null pointer, maybe your conf file ill???"); 146 | return false; 147 | } 148 | 149 | // protect cfg race conditon, just in case 150 | __auto_lock__(conf_ptr_->lock_); 151 | if (!conf_ptr_->load_setting(setting_ptr)) { 152 | roo::log_err("Load http conf failed!"); 153 | return false; 154 | } 155 | 156 | ep_ = ip::tcp::endpoint(ip::address::from_string(conf_ptr_->bind_addr_), conf_ptr_->bind_port_); 157 | roo::log_warning("create listen endpoint for %s:%d", 158 | conf_ptr_->bind_addr_.c_str(), conf_ptr_->bind_port_); 159 | 160 | roo::log_info("socket/session conn cancel time_out: %d secs, enabled: %s", 161 | conf_ptr_->ops_cancel_time_out_, 162 | conf_ptr_->ops_cancel_time_out_ > 0 ? "true" : "false"); 163 | 164 | if (conf_ptr_->service_speed_) { 165 | conf_ptr_->timed_feed_token_.reset(new steady_timer(io_service_)); // 1sec 166 | if (!conf_ptr_->timed_feed_token_) { 167 | roo::log_err("Create timed_feed_token_ failed!"); 168 | return false; 169 | } 170 | 171 | conf_ptr_->timed_feed_token_->expires_from_now(boost::chrono::seconds(1)); 172 | conf_ptr_->timed_feed_token_->async_wait( 173 | std::bind(&HttpConf::timed_feed_token_handler, conf_ptr_, std::placeholders::_1)); 174 | } 175 | roo::log_info("http service enabled: %s, speed: %d tps", 176 | conf_ptr_->service_enabled_ ? "true" : "false", 177 | conf_ptr_->service_speed_); 178 | 179 | if (!io_service_threads_.init_threads( 180 | std::bind(&HttpServerImpl::io_service_run, this, std::placeholders::_1), 181 | conf_ptr_->io_thread_number_)) { 182 | roo::log_err("HttpServer::io_service_run init task failed!"); 183 | return false; 184 | } 185 | 186 | // 执行任务分发使用 187 | if (!Dispatcher::instance().init()) { 188 | roo::log_err("Init HttpDispatcher failed."); 189 | return false; 190 | } 191 | 192 | // 注册配置动态更新的回调函数 193 | Global::instance().setting_ptr()->attach_runtime_callback( 194 | "tzhttpd-HttpServer", 195 | std::bind(&HttpServerImpl::module_runtime, this, 196 | std::placeholders::_1)); 197 | 198 | // 系统状态展示相关的初始化 199 | Global::instance().status_ptr()->attach_status_callback( 200 | "tzhttpd-HttpServer", 201 | std::bind(&HttpServerImpl::module_status, this, 202 | std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); 203 | 204 | if (!system_manage_page_init()) { 205 | roo::log_err("init system manage page failed, treat as fatal."); 206 | return false; 207 | } 208 | 209 | roo::log_info("HttpServer initialized successfully!"); 210 | return true; 211 | } 212 | 213 | 214 | 215 | // main task loop 216 | void HttpServerImpl::io_service_run(roo::ThreadObjPtr ptr) { 217 | 218 | roo::log_warning("HttpServerImpl IoService %#lx work... ", (long)pthread_self()); 219 | 220 | while (true) { 221 | 222 | if (unlikely(ptr->status_ == roo::ThreadStatus::kTerminating)) { 223 | roo::log_err("HttpServerImpl IoService %#lx terminating...", (long)pthread_self()); 224 | break; 225 | } 226 | 227 | // 线程启动 228 | if (unlikely(ptr->status_ == roo::ThreadStatus::kSuspend)) { 229 | ::usleep(1 * 1000 * 1000); 230 | continue; 231 | } 232 | 233 | boost::system::error_code ec; 234 | io_service_.run(ec); 235 | 236 | if (ec) { 237 | roo::log_err("HttpServerImpl io_service %#lx stopped...", (long)pthread_self()); 238 | break; 239 | } 240 | } 241 | 242 | ptr->status_ = roo::ThreadStatus::kDead; 243 | roo::log_warning("HttpServerImpl IoService %#lx terminated ... ", (long)pthread_self()); 244 | 245 | return; 246 | } 247 | 248 | int HttpServerImpl::service_start() { 249 | 250 | io_service_threads_.start_threads(); 251 | 252 | acceptor_.reset(new ip::tcp::acceptor(io_service_)); 253 | acceptor_->open(ep_.protocol()); 254 | 255 | acceptor_->set_option(ip::tcp::acceptor::reuse_address(true)); 256 | acceptor_->bind(ep_); 257 | acceptor_->listen(conf_ptr_->backlog_size_ > 0 ? conf_ptr_->backlog_size_ : socket_base::max_connections); 258 | 259 | do_accept(); 260 | 261 | return 0; 262 | } 263 | 264 | int HttpServerImpl::service_stop_graceful() { 265 | 266 | roo::log_err("about to stop io_service... "); 267 | 268 | io_service_.stop(); 269 | io_service_threads_.graceful_stop_threads(); 270 | return 0; 271 | } 272 | 273 | int HttpServerImpl::service_join() { 274 | 275 | roo::log_err("about to join io_service... "); 276 | 277 | io_service_threads_.join_threads(); 278 | return 0; 279 | } 280 | 281 | 282 | 283 | void HttpServerImpl::do_accept() { 284 | 285 | auto sock_ptr = std::make_shared(io_service_); 286 | if (!sock_ptr) { 287 | roo::log_err("create new socket for acceptor failed!"); 288 | return; 289 | } 290 | 291 | acceptor_->async_accept(*sock_ptr, 292 | std::bind(&HttpServerImpl::accept_handler, this, 293 | std::placeholders::_1, sock_ptr)); 294 | } 295 | 296 | void HttpServerImpl::accept_handler(const boost::system::error_code& ec, 297 | std::shared_ptr sock_ptr) { 298 | 299 | do { 300 | 301 | if (ec) { 302 | roo::log_err("Error during accept with %d, %s", ec.value(), ec.message().c_str()); 303 | break; 304 | } 305 | 306 | boost::system::error_code ignore_ec; 307 | auto remote = sock_ptr->remote_endpoint(ignore_ec); 308 | if (ignore_ec) { 309 | roo::log_err("get remote info failed:%d, %s", ignore_ec.value(), ignore_ec.message().c_str()); 310 | break; 311 | } 312 | 313 | // 远程访问客户端的地址信息 314 | std::string remote_ip = remote.address().to_string(ignore_ec); 315 | roo::log_info("Remote Client Info: %s:%d", remote_ip.c_str(), remote.port()); 316 | 317 | if (!conf_ptr_->check_safe_ip(remote_ip)) { 318 | roo::log_err("check safe_ip failed for: %s", remote_ip.c_str()); 319 | 320 | sock_ptr->shutdown(boost::asio::socket_base::shutdown_both, ignore_ec); 321 | sock_ptr->close(ignore_ec); 322 | break; 323 | } 324 | 325 | if (!conf_ptr_->get_http_service_token()) { 326 | roo::log_err("request http service token failed, enabled: %s, speed: %d", 327 | conf_ptr_->service_enabled_ ? "true" : "false", conf_ptr_->service_speed_); 328 | 329 | sock_ptr->shutdown(boost::asio::socket_base::shutdown_both, ignore_ec); 330 | sock_ptr->close(ignore_ec); 331 | break; 332 | } 333 | 334 | if (conf_ptr_->service_concurrency_ != 0 && 335 | conf_ptr_->service_concurrency_ < TcpConnAsync::current_concurrency_) { 336 | roo::log_err("service_concurrency_ error, limit: %d, current: %d", 337 | conf_ptr_->service_concurrency_, TcpConnAsync::current_concurrency_.load()); 338 | sock_ptr->shutdown(boost::asio::socket_base::shutdown_both, ignore_ec); 339 | sock_ptr->close(ignore_ec); 340 | break; 341 | } 342 | 343 | std::shared_ptr new_conn = std::make_shared(sock_ptr, super_server_); 344 | 345 | new_conn->start(); 346 | 347 | } while (0); 348 | 349 | // 再次启动接收异步请求 350 | do_accept(); 351 | } 352 | 353 | 354 | int HttpServerImpl::module_status(std::string& module, std::string& key, std::string& value) { 355 | 356 | module = "tzhttpd"; 357 | key = "http_server"; 358 | 359 | std::stringstream ss; 360 | 361 | ss << "\t" << "instance_name: " << instance_name_ << std::endl; 362 | ss << "\t" << "service_addr: " << conf_ptr_->bind_addr_ << "@" << conf_ptr_->bind_port_ << std::endl; 363 | ss << "\t" << "backlog_size: " << conf_ptr_->backlog_size_ << std::endl; 364 | ss << "\t" << "io_thread_pool_size: " << conf_ptr_->io_thread_number_ << std::endl; 365 | ss << "\t" << "safe_ips: "; 366 | 367 | { 368 | // protect cfg race conditon 369 | __auto_lock__(conf_ptr_->lock_); 370 | for (auto iter = conf_ptr_->safe_ip_.begin(); iter != conf_ptr_->safe_ip_.end(); ++iter) { 371 | ss << *iter << ", "; 372 | } 373 | ss << std::endl; 374 | } 375 | 376 | ss << "\t" << std::endl; 377 | 378 | ss << "\t" << "service_enabled: " << (conf_ptr_->service_enabled_ ? "true" : "false") << std::endl; 379 | ss << "\t" << "service_speed(tps): " << conf_ptr_->service_speed_ << std::endl; 380 | ss << "\t" << "service_concurrency: " << conf_ptr_->service_concurrency_ << std::endl; 381 | ss << "\t" << "session_cancel_time_out: " << conf_ptr_->session_cancel_time_out_ << std::endl; 382 | ss << "\t" << "ops_cancel_time_out: " << conf_ptr_->ops_cancel_time_out_ << std::endl; 383 | 384 | value = ss.str(); 385 | return 0; 386 | } 387 | 388 | 389 | int HttpServerImpl::module_runtime(const libconfig::Config& setting) { 390 | 391 | std::shared_ptr conf_ptr = std::make_shared(); 392 | if (!conf_ptr || !conf_ptr->load_setting(setting)) { 393 | roo::log_err("create or load conf for HttpConf failed."); 394 | return -1; 395 | } 396 | 397 | if (conf_ptr_->session_cancel_time_out_ != conf_ptr->session_cancel_time_out_) { 398 | roo::log_warning("update session_cancel_time_out from %d to %d", 399 | conf_ptr_->session_cancel_time_out_, conf_ptr->session_cancel_time_out_); 400 | conf_ptr_->session_cancel_time_out_ = conf_ptr->session_cancel_time_out_; 401 | } 402 | 403 | if (conf_ptr_->ops_cancel_time_out_ != conf_ptr->ops_cancel_time_out_) { 404 | roo::log_warning("update ops_cancel_time_out from %d to %d", 405 | conf_ptr_->ops_cancel_time_out_, conf_ptr->ops_cancel_time_out_); 406 | conf_ptr_->ops_cancel_time_out_ = conf_ptr->ops_cancel_time_out_; 407 | } 408 | 409 | 410 | roo::log_warning("swap safe_ips..."); 411 | 412 | { 413 | // protect cfg race conditon 414 | __auto_lock__(conf_ptr_->lock_); 415 | conf_ptr_->safe_ip_.swap(conf_ptr->safe_ip_); 416 | } 417 | 418 | if (conf_ptr_->service_speed_ != conf_ptr->service_speed_) { 419 | roo::log_warning("update http_service_speed from %d to %d", 420 | conf_ptr_->service_speed_, conf_ptr->service_speed_); 421 | conf_ptr_->service_speed_ = conf_ptr->service_speed_; 422 | 423 | // 检查定时器是否存在 424 | if (conf_ptr_->service_speed_) { 425 | 426 | // 直接重置定时器,无论有没有 427 | conf_ptr_->timed_feed_token_.reset(new steady_timer(io_service_)); // 1sec 428 | if (!conf_ptr_->timed_feed_token_) { 429 | roo::log_err("Create timed_feed_token_ failed!"); 430 | return -1; 431 | } 432 | 433 | conf_ptr_->timed_feed_token_->expires_from_now(boost::chrono::seconds(1)); 434 | conf_ptr_->timed_feed_token_->async_wait( 435 | std::bind(&HttpConf::timed_feed_token_handler, conf_ptr_, std::placeholders::_1)); 436 | } else { // speed == 0 437 | if (conf_ptr_->timed_feed_token_) { 438 | boost::system::error_code ignore_ec; 439 | conf_ptr_->timed_feed_token_->cancel(ignore_ec); 440 | conf_ptr_->timed_feed_token_.reset(); 441 | } 442 | } 443 | } 444 | 445 | if (conf_ptr_->service_concurrency_ != conf_ptr->service_concurrency_) { 446 | roo::log_err("update service_concurrency from %d to %d.", 447 | conf_ptr_->service_concurrency_, conf_ptr->service_concurrency_); 448 | conf_ptr_->service_concurrency_ = conf_ptr->service_concurrency_; 449 | } 450 | 451 | roo::log_warning("http service enabled: %s, speed: %d", conf_ptr_->service_enabled_ ? "true" : "false", 452 | conf_ptr_->service_speed_); 453 | 454 | return 0; 455 | } 456 | 457 | 458 | 459 | 460 | //// 461 | // HttpServer Call Forward 462 | 463 | HttpServer::HttpServer(const std::string& cfgfile, const std::string& instance_name) { 464 | 465 | ::signal(SIGPIPE, SIG_IGN); 466 | roo::Ssl_thread_setup(); 467 | 468 | impl_ = make_unique(cfgfile, instance_name, *this); 469 | if (!impl_) 470 | throw roo::ConstructException("Create HttpServerImpl failed."); 471 | } 472 | 473 | HttpServer::~HttpServer() { 474 | roo::Ssl_thread_clean(); 475 | } 476 | 477 | 478 | bool HttpServer::init() { 479 | return impl_->init(); 480 | } 481 | 482 | int HttpServer::service_start() { 483 | return impl_->service_start(); 484 | } 485 | 486 | int HttpServer::service_stop_graceful() { 487 | return impl_->service_stop_graceful(); 488 | } 489 | 490 | int HttpServer::service_join() { 491 | return impl_->service_join(); 492 | } 493 | 494 | int HttpServer::add_http_vhost(const std::string& hostname) { 495 | return Dispatcher::instance().add_virtual_host(hostname); 496 | } 497 | 498 | int HttpServer::add_http_get_handler(const std::string& uri_regex, const HttpGetHandler& handler, 499 | bool built_in, const std::string hostname) { 500 | return Dispatcher::instance().add_http_get_handler(hostname, uri_regex, handler, built_in); 501 | } 502 | 503 | int HttpServer::add_http_post_handler(const std::string& uri_regex, const HttpPostHandler& handler, 504 | bool built_in, const std::string hostname) { 505 | return Dispatcher::instance().add_http_post_handler(hostname, uri_regex, handler, built_in); 506 | } 507 | 508 | int HttpServer::register_http_status_callback(const std::string& name, roo::StatusCallable func) { 509 | return Global::instance().status_ptr()->attach_status_callback(name, func); 510 | } 511 | int HttpServer::register_http_runtime_callback(const std::string& name, roo::SettingUpdateCallable func) { 512 | return Global::instance().setting_ptr()->attach_runtime_callback(name, func); 513 | } 514 | int HttpServer::update_http_runtime() { 515 | return Global::instance().setting_ptr()->update_runtime_setting(); 516 | } 517 | 518 | 519 | int HttpServer::ops_cancel_time_out() const { 520 | return impl_->conf_ptr_->ops_cancel_time_out_; 521 | } 522 | int HttpServer::session_cancel_time_out() const { 523 | return impl_->conf_ptr_->session_cancel_time_out_; 524 | } 525 | 526 | boost::asio::io_service& HttpServer::io_service() const { 527 | return impl_->io_service_; 528 | } 529 | 530 | int HttpServer::module_runtime(const libconfig::Config& setting) { 531 | return impl_->module_runtime(setting); 532 | } 533 | int HttpServer::module_status(std::string& module, std::string& key, std::string& value) { 534 | return impl_->module_status(module, key, value); 535 | } 536 | 537 | 538 | 539 | } // end namespace tzhttpd 540 | -------------------------------------------------------------------------------- /HttpServer.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_HTTP_SERVER_H__ 9 | #define __TZHTTPD_HTTP_SERVER_H__ 10 | 11 | #include 12 | #include 13 | 14 | #include "HttpHandler.h" 15 | 16 | 17 | 18 | namespace boost { 19 | namespace asio { 20 | class io_service; 21 | } 22 | } 23 | 24 | namespace tzhttpd { 25 | 26 | 27 | class HttpServerImpl; 28 | 29 | class HttpServer { 30 | 31 | __noncopyable__(HttpServer) 32 | 33 | public: 34 | 35 | // 36 | // ALL AVAILABLE PUBLIC API CALL HERE 37 | // 38 | 39 | /// Construct the server to listen on the specified TCP address and port 40 | explicit HttpServer(const std::string& cfgfile, const std::string& instance_name); 41 | ~HttpServer(); 42 | 43 | // 先构造,再增加主机、handler等信息,再调用该初始化 44 | bool init(); 45 | 46 | int service_start(); 47 | int service_join(); 48 | int service_stop_graceful(); 49 | 50 | // Proxy to Dispatcher ... 51 | int add_http_vhost( 52 | const std::string& hostname); 53 | int add_http_get_handler( 54 | const std::string& uri_regex, const HttpGetHandler& handler, 55 | bool built_in = false, const std::string hostname = ""); 56 | int add_http_post_handler( 57 | const std::string& uri_regex, const HttpPostHandler& handler, 58 | bool built_in = false, const std::string hostname = ""); 59 | 60 | // Proxy to Global ... 61 | int register_http_status_callback(const std::string& name, roo::StatusCallable func); 62 | int register_http_runtime_callback(const std::string& name, roo::SettingUpdateCallable func); 63 | int update_http_runtime(); 64 | 65 | public: 66 | 67 | int ops_cancel_time_out() const; 68 | int session_cancel_time_out() const; 69 | boost::asio::io_service& io_service() const; 70 | 71 | int module_runtime(const libconfig::Config& setting); 72 | int module_status(std::string& module, std::string& key, std::string& value); 73 | 74 | private: 75 | std::shared_ptr impl_; 76 | }; 77 | 78 | 79 | } // end namespace tzhttpd 80 | 81 | #endif //__TZHTTPD_HTTP_SERVER_H__ 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-3-Clause 3 | * 4 | * Copyright (c) 2018 taozhijiang 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the University nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software 16 | * without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | * 30 | */ 31 | -------------------------------------------------------------------------------- /ManagePage.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include "HttpParser.h" 9 | #include "HttpProto.h" 10 | #include "HttpServer.h" 11 | 12 | #include "Dispatcher.h" 13 | #include "Global.h" 14 | 15 | namespace tzhttpd { 16 | 17 | using namespace http_proto; 18 | 19 | // internal/status 20 | static int system_status_handler(const HttpParser& http_parser, 21 | std::string& response, std::string& status_line, std::vector& add_header); 22 | // internal/updateconf 23 | static int system_updateconf_handler(const HttpParser& http_parser, 24 | std::string& response, std::string& status_line, std::vector& add_header); 25 | 26 | // internal/drop?hostname=aaa&uri=bbb&method=GET/POST/ALL 27 | static int system_drop_handler(const HttpParser& http_parser, 28 | std::string& response, std::string& status_line, std::vector& add_header); 29 | 30 | bool system_manage_page_init() { 31 | 32 | if (Dispatcher::instance().add_http_get_handler("", "^/internal/status$", system_status_handler, true) != 0) { 33 | roo::log_err("register system status module failed, treat as fatal."); 34 | return false; 35 | } 36 | 37 | if (Dispatcher::instance().add_http_get_handler("", "^/internal/updateconf$", system_updateconf_handler, true) != 0) { 38 | roo::log_err("register system update runtime conf module failed, treat as fatal."); 39 | return false; 40 | } 41 | 42 | if (Dispatcher::instance().add_http_get_handler("", "^/internal/drop$", system_drop_handler, true) != 0) { 43 | roo::log_err("register system handler control failed, treat as fatal."); 44 | return false; 45 | } 46 | 47 | return true; 48 | } 49 | 50 | 51 | 52 | static 53 | int system_updatesetting_handler(const HttpParser& http_parser, 54 | std::string& response, std::string& status_line, std::vector& add_header) { 55 | 56 | int ret = Global::instance().setting_ptr()->update_runtime_setting(); 57 | 58 | string http_ver = http_parser.get_version(); 59 | if (ret == 0) { 60 | status_line = generate_response_status_line(http_ver, StatusCode::success_ok); 61 | response = content_ok; 62 | } else { 63 | status_line = generate_response_status_line(http_ver, StatusCode::server_error_internal_server_error); 64 | response = content_error; 65 | } 66 | 67 | return 0; 68 | } 69 | 70 | static 71 | int system_status_handler(const HttpParser& http_parser, 72 | std::string& response, std::string& status_line, std::vector& add_header) { 73 | 74 | std::string result; 75 | Global::instance().status_ptr()->collect_status(result); 76 | 77 | response = result; 78 | status_line = http_proto::generate_response_status_line(http_parser.get_version(), http_proto::StatusCode::success_ok); 79 | 80 | return 0; 81 | } 82 | 83 | static 84 | int system_drop_handler(const HttpParser& http_parser, 85 | std::string& response, std::string& status_line, std::vector& add_header) { 86 | 87 | const UriParamContainer& params = http_parser.get_request_uri_params(); 88 | 89 | std::string hostname = params.VALUE("hostname"); 90 | std::string uri = params.VALUE("uri"); 91 | std::string method = params.VALUE("method"); 92 | if (uri.empty() || (!method.empty() && method != "GET" && method != "POST" && method != "ALL")) { 93 | roo::log_err("param check failed!"); 94 | response = content_bad_request; 95 | status_line = generate_response_status_line(http_parser.get_version(), 96 | StatusCode::client_error_bad_request); 97 | return 0; 98 | } 99 | 100 | string http_ver = http_parser.get_version(); 101 | enum HTTP_METHOD h_method = HTTP_METHOD::ALL; 102 | if (method == "GET") { 103 | h_method = HTTP_METHOD::GET; 104 | } else if (method == "POST") { 105 | h_method = HTTP_METHOD::POST; 106 | } 107 | 108 | int ret = Dispatcher::instance().drop_http_handler(hostname, uri, h_method); 109 | 110 | if (ret == 0) { 111 | status_line = generate_response_status_line(http_ver, StatusCode::success_ok); 112 | response = content_ok; 113 | } else { 114 | status_line = generate_response_status_line(http_ver, StatusCode::server_error_internal_server_error); 115 | response = content_error; 116 | } 117 | 118 | return 0; 119 | } 120 | 121 | } // end namespace tzhttpd 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### tzhttpd 2 | This is a high-performance while easy-to-be-used HTTP service framework, which can help you develop a HTTP-featured server easily and quickly. 3 | 4 | ### Key Points of tzhttpd 5 | 1. Developed with Boost.Asio, which means high-concurrency and high-performance. My little very poor virtual machine (1C1G) can support up to 1.5K QPS, so I believe it can satisfy performance requirement for most cases in production. 6 | 2. Just supporting HTTP basic GET/POST methods, but can feed the need of most backend application gateway development. Parameters and post body are well handled and structed. Routing handlers based on uri regex-match, easy for configuration. 7 | 3. Connection can be keep-alived, long-connection means higher performance (about 2x more), and can get ride of TIME-WAIT disasters, and the server also can be tuned to be automatically timed out and removed. 8 | 4. Support loading handlers through .so library, this feature simulates legacy CGI deployment conveniently. This library try its best loading and updating handler with less impact for others. And much more amazing thing is that you can just build one tzhttpd instance and copy it everywhere, and write your handlers, build them to individual so file, add them to configure files and update configuration dynamically, just like plugins. 9 | 5. Based on Boost library and C++0x standard, so can used in legacy but widely-deploied RHEL-6.x environment, also RHEL-7.x is officially supported. 10 | 6. Support regex-based Http Basic Authorization. 11 | 7. Not buggy, and has stood tests in a way in production environment. 12 | 13 | ### Possible use case 14 | 1. General Web server, KIDDING. tzhttpd does not support full HTTP protocal, so it may not behave well in this situation. But I also add the VHost, Cache Control and some other features, and my [homepage](http://taozj.net) is hosted by this, it works fine currently. 15 | 2. Backend application gateway, YES. tzhttpd is designed and optimized for this purpose. 16 | 3. HTTP protocal adaptor. This way tzhttpd can be use as proxy, it can accept and parse HTTP request, forward the request and transform corresponding respose as standard HTTP response. 17 | 4. Service manage interface. You can integrate tzhttpd into your already project, then you can send customize uri to interfere your service, such as configuration update, runtime statistic output, ... Actually I am changing most of my projects using HTTP interface, before these were implemented by kill Signal. 18 | 19 | ### Performance 20 | ![siege](siege.png?raw=true "siege") 21 | 22 | ### Internal UI 23 | ```bash 24 | # system status 25 | curl 'http://127.0.0.1:18430/internal/status' 26 | 27 | # dynamic update runtime conf 28 | curl 'http://127.0.0.1:18430/internal/updateconf' 29 | 30 | # reload so handler 31 | curl 'http://127.0.0.1:18430/internal/drop?uri=^/cgi-bin/getdemo.cgi$' 32 | curl 'http://127.0.0.1:18430/internal/status' 33 | cp libgetdemo.so ../cgi-bin 34 | curl 'http://127.0.0.1:18430/internal/updateconf' 35 | ``` 36 | 37 | ### Attention: 38 | With rdynamic and whole-archive link options, dynamic cgi-handler (through .so) can also use main program symbols, but those symbols should be built with --fvisibility=default, or your program in so may complain for undefined symbol. Though so deploy is convenient, but should be using very carefully, please use nm -r to check all U symbols of your \*.so, and use readelf -s to check whether these symbol are DEFAULT visibility in the main program. 39 | 40 | ### Reference project 41 | [heracles](https://github.com/taozhijiang/heracles) http_face example. 42 | [tzhttpd_indepent](https://github.com/taozhijiang/tzhttpd_indepent) use tzhttpd without build from scratch. 43 | -------------------------------------------------------------------------------- /ServiceIf.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_SERVICE_IF_H__ 9 | #define __TZHTTPD_SERVICE_IF_H__ 10 | 11 | #include 12 | #include 13 | 14 | #include "HttpHandler.h" 15 | 16 | // real http vhost should implement this interface class 17 | 18 | namespace tzhttpd { 19 | 20 | class HttpReqInstance; 21 | 22 | 23 | class ServiceIf { 24 | 25 | __noncopyable__(ServiceIf) 26 | 27 | public: 28 | ServiceIf() = default; 29 | ~ServiceIf() = default; 30 | 31 | // 根据opCode分发rpc请求的处理 32 | virtual void handle_http_request(std::shared_ptr http_req_instance) = 0; 33 | virtual std::string instance_name() = 0; 34 | 35 | // 36 | virtual int add_get_handler(const std::string& uri, const HttpGetHandler& handler, bool built_in) = 0; 37 | virtual int add_post_handler(const std::string& uri, const HttpPostHandler& handler, bool built_in) = 0; 38 | 39 | virtual bool exist_handler(const std::string& uri_regex, enum HTTP_METHOD method) = 0; 40 | virtual int drop_handler(const std::string& uri_regex, enum HTTP_METHOD method) = 0; 41 | 42 | 43 | // 收集模块的状态信息 44 | virtual int module_status(std::string& module, std::string& key, std::string& value) = 0; 45 | virtual int module_runtime(const libconfig::Config& cfg) = 0; 46 | }; 47 | 48 | } // end namespace tzhttpd 49 | 50 | #endif // __TZHTTPD_SERVICE_IF_H__ 51 | -------------------------------------------------------------------------------- /SlibLoader.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_SLIB_LOADER_H__ 9 | #define __TZHTTPD_SLIB_LOADER_H__ 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include "CgiHelper.h" 16 | 17 | 18 | namespace tzhttpd { 19 | 20 | class SLibLoader { 21 | 22 | __noncopyable__(SLibLoader) 23 | 24 | public: 25 | SLibLoader(const std::string& dl_path) : 26 | dl_path_(dl_path), 27 | dl_handle_(NULL) { 28 | } 29 | 30 | ~SLibLoader() { 31 | close(); 32 | } 33 | 34 | std::string get_dl_path() { 35 | return dl_path_; 36 | } 37 | 38 | bool init() { 39 | 40 | // RTLD_LAZY: Linux is not concerned about unresolved symbols until they are referenced. 41 | // RTLD_NOW: All unresolved symbols resolved when dlopen() is called. 42 | dl_handle_ = dlopen(dl_path_.c_str(), RTLD_LAZY); 43 | if (!dl_handle_) { 44 | roo::log_err("Load library %s failed: %s.", dl_path_.c_str(), dlerror()); 45 | return false; 46 | } 47 | 48 | // Reset errors 49 | dlerror(); 50 | char* err_info = NULL; 51 | 52 | module_init_ = (module_init_t)dlsym(dl_handle_, "module_init"); 53 | if ((err_info = dlerror()) != NULL) { 54 | roo::log_err("Load func module_init failed: %s", err_info); 55 | return false; 56 | } 57 | 58 | // 调用module_init函数 59 | int ret_code = (*module_init_)(); 60 | if (ret_code != 0) { 61 | roo::log_err("call module_init failed: %d", ret_code); 62 | return false; 63 | } 64 | 65 | module_exit_ = (module_exit_t)dlsym(dl_handle_, "module_exit"); 66 | if ((err_info = dlerror()) != NULL) { 67 | roo::log_err("Load func module_exit failed: %s", err_info); 68 | return false; 69 | } 70 | 71 | roo::log_warning("module %s load ok!", dl_path_.c_str()); 72 | return true; 73 | } 74 | 75 | // 函数指针类型 76 | template 77 | bool load_func(const std::string& func_name, FuncType* func) { 78 | 79 | if (!dl_handle_) { 80 | return false; 81 | } 82 | 83 | dlerror(); 84 | char* err_info = NULL; 85 | 86 | FuncType func_t = (FuncType)dlsym(dl_handle_, func_name.c_str()); 87 | if ((err_info = dlerror()) != NULL) { 88 | roo::log_err("Load func %s failed: %s", func_name.c_str(), err_info); 89 | return false; 90 | } 91 | 92 | *func = func_t; 93 | roo::log_warning("load func %s from %s ok!", func_name.c_str(), dl_path_.c_str()); 94 | return true; 95 | } 96 | 97 | void close() { 98 | if (dl_handle_) { 99 | 100 | if (module_exit_) { 101 | (*module_exit_)(); 102 | module_exit_ = NULL; 103 | roo::log_warning("module_exit from %s called!", dl_path_.c_str()); 104 | } 105 | 106 | dlclose(dl_handle_); 107 | dl_handle_ = NULL; 108 | roo::log_warning("dlclose from %s called!", dl_path_.c_str()); 109 | } 110 | } 111 | 112 | 113 | private: 114 | 115 | // so 模块中需要实现的函数接口,用于模块启动和注销时候的操作 116 | module_init_t module_init_; 117 | module_exit_t module_exit_; 118 | 119 | private: 120 | std::string dl_path_; 121 | void* dl_handle_; 122 | }; 123 | 124 | } // end namespace tzhttpd 125 | 126 | #endif // __TZHTTPD_SLIB_LOADER_H__ 127 | -------------------------------------------------------------------------------- /TcpConnAsync.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | 14 | #include 15 | 16 | #include "HttpParser.h" 17 | #include "HttpConf.h" 18 | #include "HttpServer.h" 19 | #include "HttpProto.h" 20 | #include "TcpConnAsync.h" 21 | 22 | #include "Dispatcher.h" 23 | #include "HttpReqInstance.h" 24 | 25 | 26 | namespace tzhttpd { 27 | 28 | namespace http_handler { 29 | extern int default_http_get_handler(const HttpParser& http_parser, 30 | std::string& response, std::string& status, std::vector& add_header); 31 | } // end namespace http_handler 32 | 33 | boost::atomic TcpConnAsync::current_concurrency_(0); 34 | 35 | TcpConnAsync::TcpConnAsync(std::shared_ptr socket, 36 | HttpServer& server) : 37 | ConnIf(socket), 38 | was_cancelled_(false), 39 | ops_cancel_mutex_(), 40 | ops_cancel_timer_(), 41 | session_cancel_timer_(), 42 | http_server_(server), 43 | strand_(std::make_shared(server.io_service())) { 44 | 45 | set_tcp_nodelay(true); 46 | set_tcp_nonblocking(true); 47 | 48 | ++current_concurrency_; 49 | } 50 | 51 | TcpConnAsync::~TcpConnAsync() { 52 | 53 | --current_concurrency_; 54 | // roo::log_info("TcpConnAsync SOCKET RELEASED!!!"); 55 | } 56 | 57 | void TcpConnAsync::start() { 58 | 59 | set_conn_stat(ConnStat::kWorking); 60 | do_read_head(); 61 | } 62 | 63 | void TcpConnAsync::stop() { 64 | set_conn_stat(ConnStat::kPending); 65 | } 66 | 67 | // Wrapping the handler with strand.wrap. This will return a new handler, that will dispatch through the strand. 68 | // Posting or dispatching directly through the strand. 69 | 70 | void TcpConnAsync::do_read_head() { 71 | 72 | if (get_conn_stat() != ConnStat::kWorking) { 73 | roo::log_err("Socket Status Error: %d", get_conn_stat()); 74 | return; 75 | } 76 | 77 | if (was_ops_cancelled()) { 78 | return; 79 | } 80 | 81 | // roo::log_info("strand read read_until ... in thread %#lx", (long)pthread_self()); 82 | 83 | set_session_cancel_timeout(); 84 | async_read_until(*socket_, request_, 85 | http_proto::header_crlfcrlf_str, 86 | strand_->wrap( 87 | std::bind(&TcpConnAsync::read_head_handler, 88 | shared_from_this(), 89 | std::placeholders::_1, 90 | std::placeholders::_2))); 91 | return; 92 | } 93 | 94 | void TcpConnAsync::read_head_handler(const boost::system::error_code& ec, size_t bytes_transferred) { 95 | 96 | revoke_session_cancel_timeout(); 97 | 98 | if (ec) { 99 | handle_socket_ec(ec); 100 | return; 101 | } 102 | 103 | 104 | boost::system::error_code call_ec; 105 | SAFE_ASSERT(bytes_transferred > 0); 106 | 107 | std::string head_str(boost::asio::buffers_begin(request_.data()), 108 | boost::asio::buffers_begin(request_.data()) + request_.size()); 109 | 110 | request_.consume(bytes_transferred); // skip the already head 111 | 112 | 113 | auto http_parser = std::make_shared(); 114 | if (!http_parser) { 115 | roo::log_err("Create HttpParser object failed."); 116 | goto error_return; 117 | } 118 | 119 | if (!http_parser->parse_request_header(head_str.c_str())) { 120 | roo::log_err("Parse request error: %s", head_str.c_str()); 121 | goto error_return; 122 | } 123 | 124 | // 保存远程客户端信息 125 | http_parser->remote_ = socket_->remote_endpoint(call_ec); 126 | if (call_ec) { 127 | roo::log_err("Request remote address failed."); 128 | goto error_return; 129 | } 130 | 131 | // Header must already recv here, do the uri parse work, 132 | // And store the items in params 133 | if (!http_parser->parse_request_uri()) { 134 | std::string uri = http_parser->find_request_header(http_proto::header_options::request_uri); 135 | roo::log_err("Prase request uri failed: %s", uri.c_str()); 136 | goto error_return; 137 | } 138 | 139 | if (http_parser->get_method() == HTTP_METHOD::GET || 140 | http_parser->get_method() == HTTP_METHOD::OPTIONS) { 141 | // HTTP GET handler 142 | SAFE_ASSERT(http_parser->find_request_header(http_proto::header_options::content_length).empty()); 143 | 144 | std::string real_path_info = http_parser->find_request_header(http_proto::header_options::request_path_info); 145 | std::string vhost_name = roo::StrUtil::drop_host_port( 146 | http_parser->find_request_header(http_proto::header_options::host)); 147 | 148 | std::shared_ptr http_req_instance 149 | = std::make_shared(http_parser->get_method(), shared_from_this(), 150 | vhost_name, real_path_info, 151 | http_parser, ""); 152 | Dispatcher::instance().handle_http_request(http_req_instance); 153 | 154 | // 再次开始读取请求,可以shared_from_this()保持住连接 155 | // 156 | // 我们不支持pipeline流水线机制,所以根据HTTP的鸟性,及时是长连接,客户端的下 157 | // 一次请求过来,也是等到服务端发送完请求后才会有数据 158 | // 159 | start(); 160 | return; 161 | } else if (http_parser->get_method() == HTTP_METHOD::POST) { 162 | 163 | size_t len = ::atoi(http_parser->find_request_header(http_proto::header_options::content_length).c_str()); 164 | recv_bound_.length_hint_ = len; // 登记需要读取的长度 165 | size_t additional_size = request_.size(); // net additional body size 166 | 167 | SAFE_ASSERT(additional_size <= len); 168 | 169 | // first async_read_until may read more additional data, if so 170 | // then move additional data possible 171 | if (additional_size) { 172 | 173 | std::string additional(boost::asio::buffers_begin(request_.data()), 174 | boost::asio::buffers_begin(request_.data()) + additional_size); 175 | recv_bound_.buffer_.append_internal(additional); 176 | request_.consume(additional_size); 177 | } 178 | 179 | // normally, we will return these 2 cases 180 | if (additional_size < len) { 181 | // need to read more data here, 182 | 183 | // if cancel, abort following ops 184 | if (was_ops_cancelled()) { 185 | handle_socket_ec(ec); 186 | return; 187 | } 188 | 189 | do_read_body(http_parser); 190 | } else { 191 | // call the process callback directly 192 | read_body_handler(http_parser, ec, 0); // already updated r_size_ 193 | } 194 | 195 | return; 196 | 197 | } else { 198 | roo::log_err("Invalid or unsupport request method: %s", 199 | http_parser->find_request_header(http_proto::header_options::request_method).c_str()); 200 | fill_std_http_for_send(http_parser, http_proto::StatusCode::client_error_bad_request); 201 | goto write_return; 202 | } 203 | 204 | error_return: 205 | fill_std_http_for_send(http_parser, http_proto::StatusCode::server_error_internal_server_error); 206 | request_.consume(request_.size()); 207 | 208 | write_return: 209 | 210 | // If HTTP 1.0 or HTTP 1.1 without Keep-Alived, close the connection directly 211 | // Else, trigger the next generation read again! 212 | 213 | // Bug: 214 | // 因为异步写也会触发定时器的设置和取消,所以这里的读定时器会被误取消导致永久阻塞 215 | // 解决的方式: 216 | // (1)将超时器分开设置 217 | // 218 | // 在 do_write()中启动读操作不合理,见do_write()的注释 219 | // 在这个路径返回的,基本都是异常情况导致的错误返回,此时根据情况看是否发起请求 220 | // 读操作必须在写操作之前,否则可能会导致引用计数消失 221 | 222 | do_write(http_parser); 223 | 224 | if (keep_continue(http_parser)) { 225 | start(); 226 | } 227 | } 228 | 229 | void TcpConnAsync::do_read_body(std::shared_ptr http_parser) { 230 | 231 | if (get_conn_stat() != ConnStat::kWorking) { 232 | roo::log_err("Socket Status Error: %d", get_conn_stat()); 233 | return; 234 | } 235 | 236 | if (was_ops_cancelled()) { 237 | return; 238 | } 239 | 240 | if (recv_bound_.length_hint_ == 0) { 241 | recv_bound_.length_hint_ = ::atoi(http_parser->find_request_header(http_proto::header_options::content_length).c_str()); 242 | } 243 | 244 | size_t to_read = std::min(static_cast(recv_bound_.length_hint_ - recv_bound_.buffer_.get_length()), 245 | static_cast(kFixedIoBufferSize)); 246 | 247 | // roo::log_info("strand read async_read exactly(%lu)... in thread %#lx", 248 | // to_read, (long)pthread_self()); 249 | 250 | set_ops_cancel_timeout(); 251 | async_read(*socket_, boost::asio::buffer(recv_bound_.io_block_, to_read), 252 | boost::asio::transfer_at_least(to_read), 253 | strand_->wrap( 254 | std::bind(&TcpConnAsync::read_body_handler, 255 | shared_from_this(), 256 | http_parser, 257 | std::placeholders::_1, 258 | std::placeholders::_2))); 259 | return; 260 | } 261 | 262 | 263 | void TcpConnAsync::read_body_handler(std::shared_ptr http_parser, 264 | const boost::system::error_code& ec, size_t bytes_transferred) { 265 | 266 | revoke_ops_cancel_timeout(); 267 | 268 | if (ec) { 269 | handle_socket_ec(ec); 270 | return; 271 | } 272 | 273 | // 如果在读取HTTP头部的同时就将数据也读取出来了,这时候实际的 274 | // bytes_transferred == 0 275 | if (bytes_transferred > 0) { 276 | std::string additional(recv_bound_.io_block_, bytes_transferred); 277 | recv_bound_.buffer_.append_internal(additional); 278 | } 279 | 280 | if (recv_bound_.buffer_.get_length() < recv_bound_.length_hint_) { 281 | // need to read more, do again! 282 | do_read_body(http_parser); 283 | return; 284 | } 285 | 286 | std::string real_path_info = http_parser->find_request_header(http_proto::header_options::request_path_info); 287 | std::string vhost_name = roo::StrUtil::drop_host_port( 288 | http_parser->find_request_header(http_proto::header_options::host)); 289 | 290 | std::string post_body; 291 | recv_bound_.buffer_.consume(post_body, recv_bound_.length_hint_); 292 | 293 | std::shared_ptr http_req_instance 294 | = std::make_shared(http_parser->get_method(), shared_from_this(), 295 | vhost_name, real_path_info, 296 | http_parser, post_body); 297 | 298 | 299 | Dispatcher::instance().handle_http_request(http_req_instance); 300 | 301 | // default, OK 302 | // go through write return; 303 | 304 | // If HTTP 1.0 or HTTP 1.1 without Keep-Alived, close the connection directly 305 | // Else, trigger the next generation read again! 306 | 307 | // 算了,强制一个读操作,从而可以引发其错误处理 308 | 309 | // 这里是POST方法,而GET方法在读完头部后就直接再次发起读操作了 310 | // 再次开始读取请求,可以shared_from_this()保持住连接 311 | start(); 312 | } 313 | 314 | bool TcpConnAsync::do_write(std::shared_ptr http_parser) { 315 | 316 | if (get_conn_stat() != ConnStat::kWorking) { 317 | roo::log_err("Socket Status Error: %d", get_conn_stat()); 318 | return false; 319 | } 320 | 321 | if (send_bound_.buffer_.get_length() == 0) { 322 | 323 | // 看是否关闭主动关闭连接 324 | // 因为在读取请求的时候默认就当作长连接发起再次读了,所以如果这里检测到是 325 | // 短连接,就采取主动关闭操作,否则就让之前的长连接假设继续生效 326 | if (!keep_continue(http_parser)) { 327 | 328 | revoke_ops_cancel_timeout(); 329 | ops_cancel(); 330 | sock_shutdown_and_close(ShutdownType::kBoth); 331 | } 332 | 333 | return true; 334 | } 335 | 336 | SAFE_ASSERT(send_bound_.buffer_.get_length() > 0); 337 | 338 | size_t to_write = std::min(static_cast(send_bound_.buffer_.get_length()), 339 | static_cast(kFixedIoBufferSize)); 340 | 341 | // roo::log_info("strand write async_write exactly (%lu)... in thread thread %#lx", 342 | // to_write, (long)pthread_self()); 343 | 344 | send_bound_.buffer_.consume(send_bound_.io_block_, to_write); 345 | 346 | set_ops_cancel_timeout(); 347 | async_write(*socket_, boost::asio::buffer(send_bound_.io_block_, to_write), 348 | boost::asio::transfer_exactly(to_write), 349 | strand_->wrap( 350 | std::bind(&TcpConnAsync::self_write_handler, 351 | shared_from_this(), 352 | http_parser, 353 | std::placeholders::_1, 354 | std::placeholders::_2))); 355 | return true; 356 | } 357 | 358 | 359 | void TcpConnAsync::self_write_handler(std::shared_ptr http_parser, 360 | const boost::system::error_code& ec, size_t bytes_transferred) { 361 | 362 | revoke_ops_cancel_timeout(); 363 | 364 | if (ec) { 365 | handle_socket_ec(ec); 366 | return; 367 | } 368 | 369 | SAFE_ASSERT(bytes_transferred > 0); 370 | 371 | // 372 | // 再次触发写,如果为空就直接返回 373 | // 函数中会检查,如果内容为空,就直接返回不执行写操作 374 | 375 | 376 | // Bug 这里会有竞争条件 !!! 377 | // 当使用http客户端长连接的时候,下面的do_write()如果没有数据会触发 378 | // keep_continue()调用,而该实现是遍历http_parser的head来实现的, 379 | // 但是此时可能在write_handler调用之前或之中就触发了客户端新的head解析,导致 380 | // 该调用访问http_parser会产生问题 381 | 382 | do_write(http_parser); 383 | } 384 | 385 | 386 | void TcpConnAsync::fill_http_for_send(std::shared_ptr http_parser, 387 | const std::string& str, const std::string& status_line, 388 | const std::vector& additional_header) { 389 | 390 | bool keep_next = false; 391 | std::string str_uri = "UNDETECTED_URI"; 392 | std::string str_method = "UNDETECTED_METHOD"; 393 | 394 | if (http_parser) { 395 | keep_next = keep_continue(http_parser); 396 | str_uri = http_parser->get_uri(); 397 | str_method = HTTP_METHOD_STRING(http_parser->get_method()); 398 | } 399 | 400 | std::string content = http_proto::http_response_generate(str, status_line, keep_next, additional_header); 401 | send_bound_.buffer_.append_internal(content); 402 | 403 | roo::log_warning("\n =====> \"%s %s\" %s", 404 | str_method.c_str(), str_uri.c_str(), status_line.c_str()); 405 | 406 | return; 407 | } 408 | 409 | 410 | void TcpConnAsync::fill_std_http_for_send(std::shared_ptr http_parser, 411 | enum http_proto::StatusCode code) { 412 | 413 | bool keep_next = false; 414 | std::string http_ver = "HTTP/1.1"; 415 | std::string str_uri = "UNDETECTED_URI"; 416 | std::string str_method = "UNDETECTED_METHOD"; 417 | 418 | if (http_parser) { 419 | keep_next = keep_continue(http_parser); 420 | http_ver = http_parser->get_version(); 421 | str_uri = http_parser->get_uri(); 422 | str_method = HTTP_METHOD_STRING(http_parser->get_method()); 423 | } 424 | 425 | 426 | std::string status_line = generate_response_status_line(http_ver, code); 427 | std::string content = 428 | http_proto::http_std_response_generate(http_ver, status_line, code, keep_next); 429 | 430 | send_bound_.buffer_.append_internal(content); 431 | 432 | roo::log_warning("\n =====> \"%s %s\" %s", 433 | str_method.c_str(), str_uri.c_str(), status_line.c_str()); 434 | 435 | return; 436 | } 437 | 438 | 439 | // http://www.boost.org/doc/libs/1_44_0/doc/html/boost_asio/reference/error__basic_errors.html 440 | bool TcpConnAsync::handle_socket_ec(const boost::system::error_code& ec) { 441 | 442 | boost::system::error_code ignore_ec; 443 | bool close_socket = false; 444 | 445 | if (ec == boost::asio::error::eof || 446 | ec == boost::asio::error::connection_reset || 447 | ec == boost::asio::error::timed_out || 448 | ec == boost::asio::error::bad_descriptor) { 449 | roo::log_err("error_code: {%d} %s", ec.value(), ec.message().c_str()); 450 | close_socket = true; 451 | } else if (ec == boost::asio::error::operation_aborted) { 452 | // like itimeout trigger 453 | roo::log_err("error_code: {%d} %s", ec.value(), ec.message().c_str()); 454 | } else { 455 | roo::log_err("Undetected error %d, %s ...", ec.value(), ec.message().c_str()); 456 | close_socket = true; 457 | } 458 | 459 | 460 | if (close_socket || was_ops_cancelled()) { 461 | revoke_ops_cancel_timeout(); 462 | ops_cancel(); 463 | sock_shutdown_and_close(ShutdownType::kBoth); 464 | } 465 | 466 | return close_socket; 467 | } 468 | 469 | // 测试方法 while:; do echo -e "GET / HTTP/1.1\nhost: test.domain\n\n"; sleep 3; done | telnet 127.0.0.1 8899 470 | bool TcpConnAsync::keep_continue(const std::shared_ptr& http_parser) { 471 | 472 | if (!http_parser) 473 | return false; 474 | 475 | std::string connection = http_parser->find_request_header(http_proto::header_options::connection); 476 | if (!connection.empty()) { 477 | if (boost::iequals(connection, "Close")) { 478 | return false; 479 | } else if (boost::iequals(connection, "Keep-Alive")) { 480 | return true; 481 | } else { 482 | roo::log_err("unknown connection value: %s", connection.c_str()); 483 | } 484 | } 485 | 486 | if (http_parser->get_version() > "1.0") { 487 | return true; 488 | } 489 | 490 | return false; 491 | } 492 | 493 | void TcpConnAsync::set_session_cancel_timeout() { 494 | 495 | std::lock_guard lock(ops_cancel_mutex_); 496 | 497 | if (http_server_.session_cancel_time_out() == 0) { 498 | SAFE_ASSERT(!session_cancel_timer_); 499 | return; 500 | } 501 | 502 | // cancel the already timer first if any 503 | boost::system::error_code ignore_ec; 504 | if (session_cancel_timer_) { 505 | session_cancel_timer_->cancel(ignore_ec); 506 | } else { 507 | session_cancel_timer_.reset(new steady_timer(http_server_.io_service())); 508 | } 509 | 510 | SAFE_ASSERT(http_server_.session_cancel_time_out()); 511 | session_cancel_timer_->expires_from_now(seconds(http_server_.session_cancel_time_out())); 512 | session_cancel_timer_->async_wait(std::bind(&TcpConnAsync::ops_cancel_timeout_call, shared_from_this(), 513 | std::placeholders::_1)); 514 | roo::log_info("register session_cancel_time_out %d sec", http_server_.session_cancel_time_out()); 515 | } 516 | 517 | void TcpConnAsync::revoke_session_cancel_timeout() { 518 | 519 | std::lock_guard lock(ops_cancel_mutex_); 520 | 521 | boost::system::error_code ignore_ec; 522 | if (session_cancel_timer_) { 523 | session_cancel_timer_->cancel(ignore_ec); 524 | } 525 | } 526 | 527 | void TcpConnAsync::set_ops_cancel_timeout() { 528 | 529 | std::lock_guard lock(ops_cancel_mutex_); 530 | 531 | if (http_server_.ops_cancel_time_out() == 0) { 532 | SAFE_ASSERT(!ops_cancel_timer_); 533 | return; 534 | } 535 | 536 | // cancel the already timer first if any 537 | boost::system::error_code ignore_ec; 538 | if (ops_cancel_timer_) { 539 | ops_cancel_timer_->cancel(ignore_ec); 540 | } else { 541 | ops_cancel_timer_.reset(new steady_timer(http_server_.io_service())); 542 | } 543 | 544 | SAFE_ASSERT(http_server_.ops_cancel_time_out()); 545 | ops_cancel_timer_->expires_from_now(seconds(http_server_.ops_cancel_time_out())); 546 | ops_cancel_timer_->async_wait(std::bind(&TcpConnAsync::ops_cancel_timeout_call, shared_from_this(), 547 | std::placeholders::_1)); 548 | roo::log_info("register ops_cancel_time_out %d sec", http_server_.ops_cancel_time_out()); 549 | } 550 | 551 | void TcpConnAsync::revoke_ops_cancel_timeout() { 552 | 553 | std::lock_guard lock(ops_cancel_mutex_); 554 | 555 | boost::system::error_code ignore_ec; 556 | if (ops_cancel_timer_) { 557 | ops_cancel_timer_->cancel(ignore_ec); 558 | } 559 | } 560 | 561 | void TcpConnAsync::ops_cancel_timeout_call(const boost::system::error_code& ec) { 562 | 563 | if (ec == 0) { 564 | roo::log_warning("ops_cancel_timeout_call called with timeout: %d", http_server_.ops_cancel_time_out()); 565 | ops_cancel(); 566 | sock_shutdown_and_close(ShutdownType::kBoth); 567 | } else if (ec == boost::asio::error::operation_aborted) { 568 | // normal cancel 569 | } else { 570 | roo::log_err("unknown and won't handle error_code: {%d} %s", ec.value(), ec.message().c_str()); 571 | } 572 | } 573 | 574 | 575 | } // end namespace tzhttpd 576 | -------------------------------------------------------------------------------- /TcpConnAsync.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018-2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef __TZHTTPD_TCP_CONN_ASYNC_H__ 9 | #define __TZHTTPD_TCP_CONN_ASYNC_H__ 10 | 11 | #include 12 | #include 13 | 14 | #include "ConnIf.h" 15 | #include "HttpParser.h" 16 | 17 | #include 18 | #include 19 | using boost::asio::steady_timer; 20 | 21 | namespace tzhttpd { 22 | 23 | class TcpConnAsync; 24 | class HttpReqInstance; 25 | 26 | class HttpServer; 27 | class TcpConnAsync : public ConnIf, 28 | public std::enable_shared_from_this { 29 | 30 | __noncopyable__(TcpConnAsync) 31 | friend class HttpReqInstance; 32 | 33 | public: 34 | 35 | // 当前并发连接数目 36 | static boost::atomic current_concurrency_; 37 | 38 | /// Construct a connection with the given socket. 39 | TcpConnAsync(std::shared_ptr socket, HttpServer& server); 40 | virtual ~TcpConnAsync(); 41 | 42 | virtual void start(); 43 | void stop(); 44 | 45 | // http://www.boost.org/doc/libs/1_44_0/doc/html/boost_asio/reference/error__basic_errors.html 46 | bool handle_socket_ec(const boost::system::error_code& ec); 47 | 48 | private: 49 | 50 | virtual bool do_read()override { SAFE_ASSERT(false); 51 | return false;} 52 | virtual void read_handler(const boost::system::error_code& ec, std::size_t bytes_transferred)override { 53 | SAFE_ASSERT(false); 54 | } 55 | 56 | virtual bool do_write()override { SAFE_ASSERT(false); 57 | return false;} 58 | virtual void write_handler(const boost::system::error_code& ec, std::size_t bytes_transferred)override { 59 | SAFE_ASSERT(false); 60 | } 61 | 62 | void do_read_head(); 63 | void read_head_handler(const boost::system::error_code& ec, std::size_t bytes_transferred); 64 | 65 | void do_read_body(std::shared_ptr http_parser); 66 | void read_body_handler(std::shared_ptr http_parser, 67 | const boost::system::error_code& ec, std::size_t bytes_transferred); 68 | 69 | bool do_write(std::shared_ptr http_parser); 70 | // std::bind无法使用重载函数,所以这里另起函数名 71 | void self_write_handler(std::shared_ptr http_parser, 72 | const boost::system::error_code& ec, std::size_t bytes_transferred); 73 | 74 | void set_session_cancel_timeout(); 75 | void revoke_session_cancel_timeout(); 76 | void set_ops_cancel_timeout(); 77 | void revoke_ops_cancel_timeout(); 78 | bool was_ops_cancelled() { 79 | std::lock_guard lock(ops_cancel_mutex_); 80 | return was_cancelled_; 81 | } 82 | 83 | bool ops_cancel() { 84 | std::lock_guard lock(ops_cancel_mutex_); 85 | sock_cancel(); 86 | set_conn_stat(ConnStat::kError); 87 | was_cancelled_ = true; 88 | return was_cancelled_; 89 | } 90 | void ops_cancel_timeout_call(const boost::system::error_code& ec); 91 | 92 | // 是否Connection长连接 93 | bool keep_continue(const std::shared_ptr& http_parser); 94 | 95 | void fill_http_for_send(std::shared_ptr http_parser, 96 | const char* data, size_t len, const std::string& status) { 97 | SAFE_ASSERT(data && len); 98 | std::string msg(data, len); 99 | fill_http_for_send(http_parser, msg, status, { }); 100 | } 101 | 102 | void fill_http_for_send(std::shared_ptr http_parser, 103 | const std::string& str, const std::string& status) { 104 | fill_http_for_send(http_parser, str, status, { }); 105 | } 106 | 107 | void fill_http_for_send(std::shared_ptr http_parser, 108 | const char* data, size_t len, const std::string& status, 109 | const std::vector& additional_header) { 110 | SAFE_ASSERT(data && len); 111 | std::string msg(data, len); 112 | return fill_http_for_send(http_parser, msg, status, additional_header); 113 | } 114 | 115 | void fill_http_for_send(std::shared_ptr http_parser, 116 | const std::string& str, const std::string& status, 117 | const std::vector& additional_header); 118 | 119 | // 标准的HTTP响应头和响应体 120 | void fill_std_http_for_send(std::shared_ptr http_parser, 121 | enum http_proto::StatusCode code); 122 | 123 | private: 124 | 125 | // 用于读取HTTP的头部使用 126 | boost::asio::streambuf request_; // client request_ read 127 | 128 | IOBound recv_bound_; 129 | IOBound send_bound_; 130 | 131 | bool was_cancelled_; 132 | std::mutex ops_cancel_mutex_; 133 | 134 | // IO操作的最大时长 135 | std::unique_ptr ops_cancel_timer_; 136 | 137 | // 会话间隔的最大时长 138 | std::unique_ptr session_cancel_timer_; 139 | 140 | private: 141 | 142 | HttpServer& http_server_; 143 | 144 | // Of course, the handlers may still execute concurrently with other handlers that 145 | // were not dispatched through an boost::asio::strand, or were dispatched through 146 | // a different boost::asio::strand object. 147 | 148 | // Where there is a single chain of asynchronous operations associated with a 149 | // connection (e.g. in a half duplex protocol implementation like HTTP) there 150 | // is no possibility of concurrent execution of the handlers. This is an implicit strand. 151 | 152 | // Strand to ensure the connection's handlers are not called concurrently. ??? 153 | std::shared_ptr strand_; 154 | 155 | }; 156 | 157 | } // end namespace tzhttpd 158 | 159 | #endif //__TZHTTPD_TCP_CONN_ASYNC_H__ 160 | -------------------------------------------------------------------------------- /cgi-handler/getdemo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | aux_source_directory(./ DIR_LIB_SRCS) 2 | add_library (getdemo SHARED ${DIR_LIB_SRCS}) 3 | 4 | include_directories( 5 | ../xtra_rhelz.x/include ) 6 | -------------------------------------------------------------------------------- /cgi-handler/getdemo/getdemo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../../CgiHelper.h" 4 | #include "../../Log.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | 11 | int module_init() { 12 | return 0; 13 | } 14 | 15 | int module_exit() { 16 | return 0; 17 | } 18 | 19 | 20 | int cgi_get_handler(const msg_t* param, msg_t* rsp, msg_t* rsp_header) { 21 | 22 | tzhttpd::tzhttpd_log_debug("get call in cgi log..."); 23 | 24 | std::string msg = "return from getdemo with param:" + std::string(param->data, param->len); 25 | fill_msg(rsp, msg.c_str(), msg.size()); 26 | 27 | std::string strHead = "GetHead1: value1\n GetHead2: value2\n"; 28 | fill_msg(rsp_header, strHead.c_str(), strHead.size()); 29 | 30 | return 0; 31 | } 32 | 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif 37 | -------------------------------------------------------------------------------- /cgi-handler/postdemo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | aux_source_directory(./ DIR_LIB_SRCS) 2 | add_library (postdemo SHARED ${DIR_LIB_SRCS}) 3 | 4 | include_directories( 5 | ../xtra_rhelz.x/include ) 6 | -------------------------------------------------------------------------------- /cgi-handler/postdemo/postdemo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../../CgiHelper.h" 4 | #include "../../Log.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | 11 | int module_init() { 12 | return 0; 13 | } 14 | 15 | int module_exit() { 16 | return 0; 17 | } 18 | 19 | 20 | int cgi_post_handler(const msg_t* param, const msg_t* post, msg_t* rsp, msg_t* rsp_header) { 21 | 22 | tzhttpd::tzhttpd_log_debug("post call in cgi log..."); 23 | 24 | std::string msg = "return from postdemo with param:" + std::string(param->data, param->len); 25 | msg += " , and postdata:" + std::string(post->data, param->len); 26 | fill_msg(rsp, msg.c_str(), msg.size()); 27 | 28 | std::string strHead = "PostHead1: value1\n PostHead2: value2 "; 29 | fill_msg(rsp_header, strHead.c_str(), strHead.size()); 30 | 31 | return 0; 32 | } 33 | 34 | 35 | #ifdef __cplusplus 36 | } 37 | #endif 38 | -------------------------------------------------------------------------------- /examples/HttpUtil.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #ifndef _HTTP_UTIL_H_ 9 | #define _HTTP_UTIL_H_ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "CryptoUtil.h" 20 | 21 | namespace HttpUtil { 22 | 23 | #define LOG_ERR_ printf 24 | 25 | #if 0 26 | static int GenerateUrl(const std::string& strUrl, const std::map& fields, 27 | std::string& strCallUrl) { 28 | 29 | if (strUrl.empty()) 30 | return -1; 31 | 32 | if (fields.empty()) { 33 | strCallUrl = strUrl; 34 | return 0; 35 | } 36 | 37 | std::ostringstream oss; 38 | std::ostringstream ossUrl; 39 | 40 | for (auto iter = fields.begin(); 41 | iter != fields.end(); 42 | ++iter) { 43 | 44 | oss << iter->first << "="; 45 | oss << iter->second << "&"; 46 | 47 | ossUrl << iter->first << "="; 48 | ossUrl << tzhttpd::CryptoUtil::url_encode(iter->second); //url encode 49 | ossUrl << "&"; 50 | } 51 | 52 | // url_encode之前算签名 53 | std::string need_sign = oss.str(); 54 | if (need_sign[need_sign.size() - 1] == '&') { 55 | need_sign.erase(need_sign.end() - 1); 56 | } 57 | 58 | std::string sha1value = tzhttpd::CryptoUtil::sha1(need_sign); 59 | std::string sha1str = tzhttpd::CryptoUtil::hex_string(sha1value.c_str(), sha1value.length()); 60 | 61 | ossUrl << "sign=" << sha1str; 62 | strCallUrl = strUrl + '?' + ossUrl.str(); 63 | 64 | return 0; 65 | } 66 | 67 | static bool CheckSha1(const std::map& fields, const std::string& sign) { 68 | 69 | if (fields.empty() || sign.empty()) { 70 | return false; 71 | } 72 | 73 | std::ostringstream oss; 74 | 75 | for (auto iter = fields.begin(); 76 | iter != fields.end(); 77 | ++iter) { 78 | oss << iter->first << "="; 79 | oss << tzhttpd::CryptoUtil::url_decode(iter->second) << "&"; 80 | } 81 | 82 | // url_encode之前算签名 83 | std::string need_sign = oss.str(); 84 | if (need_sign[need_sign.size() - 1] == '&') { 85 | need_sign.erase(need_sign.end() - 1); 86 | } 87 | 88 | std::string sha1value = tzhttpd::CryptoUtil::sha1(need_sign); 89 | std::string sha1str = tzhttpd::CryptoUtil::to_hex_string(sha1value); 90 | 91 | return sha1value == sign; 92 | } 93 | 94 | #endif 95 | 96 | class HttpClient { 97 | public: 98 | 99 | explicit HttpClient(uint32_t ConnTimeout = 60, uint32_t Timeout = 120) : 100 | kConnTimeout_(ConnTimeout), 101 | kTimeout_(Timeout) { 102 | CurlPrelude(); 103 | } 104 | ~HttpClient() { } 105 | 106 | HttpClient(const HttpClient&) = delete; 107 | HttpClient& operator=(const HttpClient&) = delete; 108 | 109 | int GetByHttp(const std::string& strUrl) { 110 | 111 | if (CurlPrelude()) return -1; 112 | 113 | CURL* curl = curl_ptr_->handle(); 114 | curl_easy_setopt(curl, CURLOPT_ENCODING, "UTF-8"); 115 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1); 116 | curl_easy_setopt(curl, CURLOPT_POST, 0); 117 | curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str()); 118 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 119 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); 120 | curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); 121 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, kConnTimeout_); 122 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, kTimeout_); 123 | 124 | CURLcode res = curl_easy_perform(curl); 125 | return HandleCurlResult(res); 126 | } 127 | 128 | int PostByHttp(const std::string& strUrl, const std::string& strData) { 129 | 130 | if (CurlPrelude()) return -1; 131 | 132 | CURL* curl = curl_ptr_->handle(); 133 | curl_easy_setopt(curl, CURLOPT_ENCODING, "UTF-8"); 134 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1); 135 | curl_easy_setopt(curl, CURLOPT_POST, 1); 136 | curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str()); 137 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, strData.c_str()); 138 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 139 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); 140 | curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); 141 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, kConnTimeout_); 142 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, kTimeout_); 143 | 144 | CURLcode res = curl_easy_perform(curl); 145 | return HandleCurlResult(res); 146 | } 147 | 148 | 149 | // POST WITH headers 150 | int PostByHttp(const std::string& strUrl, const std::string& strData, const std::list& headers) { 151 | 152 | if (CurlPrelude()) return -1; 153 | 154 | CURL* curl = curl_ptr_->handle(); 155 | curl_easy_setopt(curl, CURLOPT_ENCODING, "UTF-8"); 156 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1); 157 | curl_easy_setopt(curl, CURLOPT_POST, 1); 158 | curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str()); 159 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, strData.c_str()); 160 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 161 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); 162 | curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); 163 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, kConnTimeout_); 164 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, kTimeout_); 165 | 166 | struct curl_slist* s_headers = NULL; 167 | std::list::const_iterator it; 168 | for (it = headers.begin(); it != headers.end(); it++) { 169 | s_headers = curl_slist_append(s_headers, it->c_str()); 170 | } 171 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, s_headers); 172 | 173 | CURLcode res = curl_easy_perform(curl); 174 | curl_slist_free_all(s_headers); 175 | 176 | return HandleCurlResult(res); 177 | } 178 | 179 | int PostByHttpsVerifyHost(const std::string& strUrl, const std::string& strData, const std::list& headers) { 180 | 181 | if (CurlPrelude()) return -1; 182 | 183 | CURL* curl = curl_ptr_->handle(); 184 | curl_easy_setopt(curl, CURLOPT_ENCODING, "UTF-8"); 185 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1); 186 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); 187 | curl_easy_setopt(curl, CURLOPT_POST, 1); 188 | curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str()); 189 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, strData.c_str()); 190 | curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderWriteCallback); 191 | curl_easy_setopt(curl, CURLOPT_HEADERDATA, this); 192 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 193 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); 194 | curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); 195 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, kConnTimeout_); 196 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, kTimeout_); 197 | 198 | struct curl_slist* s_headers = NULL; 199 | std::list::const_iterator it; 200 | for (it = headers.begin(); it != headers.end(); it++) { 201 | s_headers = curl_slist_append(s_headers, it->c_str()); 202 | } 203 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, s_headers); 204 | 205 | 206 | CURLcode res = curl_easy_perform(curl); 207 | curl_slist_free_all(s_headers); 208 | 209 | return HandleCurlResult(res); 210 | } 211 | 212 | int PostByHttps(const std::string& strUrl, const std::string& strData, const std::list& headers) { 213 | 214 | if (CurlPrelude()) return -1; 215 | 216 | CURL* curl = curl_ptr_->handle(); 217 | curl_easy_setopt(curl, CURLOPT_ENCODING, "UTF-8"); 218 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); 219 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); 220 | curl_easy_setopt(curl, CURLOPT_POST, 1); 221 | curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str()); 222 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, strData.c_str()); 223 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 224 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); 225 | curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); 226 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, kConnTimeout_); 227 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, kTimeout_); 228 | 229 | struct curl_slist* s_headers = NULL; 230 | std::list::const_iterator it; 231 | for (it = headers.begin(); it != headers.end(); it++) { 232 | s_headers = curl_slist_append(s_headers, it->c_str()); 233 | } 234 | 235 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, s_headers); 236 | 237 | CURLcode res = curl_easy_perform(curl); 238 | curl_slist_free_all(s_headers); 239 | 240 | return HandleCurlResult(res); 241 | } 242 | 243 | 244 | std::string GetData() { 245 | return std::string((char*)&response_data_[0], response_data_.size()); 246 | } 247 | 248 | private: 249 | 250 | // static member func, 所以需要使用指针调用其非静态成员 251 | static size_t WriteCallback(void* ptr, size_t size, size_t nmemb, void* data) { 252 | 253 | HttpClient* pHttp = static_cast(data); 254 | size_t pos = pHttp->response_data_.size(); 255 | size_t len = size * nmemb; 256 | pHttp->response_data_.resize(pos + len); 257 | char* pRespBuff = pHttp->response_data_.data(); 258 | ::memcpy(pRespBuff + pos, ptr, len); 259 | 260 | return len; 261 | } 262 | 263 | static size_t HeaderWriteCallback(void* ptr, size_t size, size_t nmemb, void* data) { 264 | 265 | HttpClient* pHttp = static_cast(data); 266 | size_t pos = pHttp->response_header_.size(); 267 | size_t len = size * nmemb; 268 | pHttp->response_header_.resize(pos + len); 269 | char* pRespBuff = pHttp->response_header_.data(); 270 | ::memcpy(pRespBuff + pos, ptr, len); 271 | 272 | return len; 273 | } 274 | 275 | // Curl Object define 276 | class Curl { 277 | 278 | public: 279 | Curl() { 280 | curl_ = curl_easy_init(); 281 | } 282 | 283 | ~Curl() { 284 | if (curl_) { 285 | curl_easy_cleanup(curl_); 286 | curl_ = NULL; 287 | } 288 | } 289 | 290 | CURL* handle() { 291 | return curl_; 292 | } 293 | 294 | Curl(const Curl&) = delete; 295 | Curl& operator=(const Curl&) = delete; 296 | 297 | private: 298 | CURL* curl_; 299 | }; 300 | 301 | private: 302 | 303 | int CurlPrelude() { 304 | 305 | response_header_.clear(); 306 | response_data_.clear(); 307 | 308 | if (!curl_ptr_ || !curl_ptr_->handle()) { 309 | curl_ptr_.reset(new Curl()); 310 | if (!curl_ptr_ || !curl_ptr_->handle()) { 311 | LOG_ERR_("Create Curl failed."); 312 | return -1; 313 | } 314 | } 315 | 316 | return 0; 317 | } 318 | 319 | int HandleCurlResult(CURLcode res) { 320 | 321 | if (res != CURLE_OK || !curl_ptr_ || !curl_ptr_->handle()) { 322 | LOG_ERR_("CURL error with res %d", res); 323 | 324 | // reset ill curl 325 | curl_ptr_.reset(); 326 | return -1; 327 | } 328 | 329 | long nStatusCode = 0; 330 | curl_easy_getinfo(curl_ptr_->handle(), CURLINFO_RESPONSE_CODE, &nStatusCode); 331 | 332 | if (nStatusCode == 200) 333 | return 0; 334 | 335 | LOG_ERR_("curl nStatusCode:%ld, res=%d, error:%s", nStatusCode, res, curl_easy_strerror(res)); 336 | return -1; 337 | } 338 | 339 | private: 340 | std::vector response_header_; 341 | std::vector response_data_; 342 | 343 | std::unique_ptr curl_ptr_; 344 | 345 | uint32_t kConnTimeout_; 346 | uint32_t kTimeout_; 347 | 348 | }; 349 | 350 | } // end namespace 351 | 352 | 353 | #endif // _TZ_HTTP_UTIL_H_ 354 | -------------------------------------------------------------------------------- /examples/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "build normal httpsrv ..." 4 | g++ -std=c++0x -g -O0 -DNP_DEBUG -Wall main.cpp setup.cpp \ 5 | -I../ -I../../roo \ 6 | -I../ -I../../xtra_rhelz.x/include -I../ -I../../xtra_rhelz.x/include/google \ 7 | -L../build/ -L../../xtra_rhelz.x/libs/ -L../../xtra_rhelz.x/libs/google/ \ 8 | -L../../xtra_rhelz.x/libs/boost/ \ 9 | -ltzhttpd libRoo.a \ 10 | -lboost_system -lboost_thread -lboost_chrono -lboost_regex \ 11 | -lpthread -lrt -rdynamic -ldl -lconfig++ -lssl -lcryptopp -lcrypto \ 12 | -lglog_syslog \ 13 | -o httpsrv 14 | 15 | echo "build fast_client..." 16 | g++ -std=c++0x -g -O0 -DNP_DEBUG -Wall fast_client.cpp \ 17 | -I../ -I../../roo \ 18 | -I../ -I../../xtra_rhelz.x/include -I../../xtra_rhelz.x/include/curl_7.53.1 \ 19 | -L../../xtra_rhelz.x/libs \ 20 | -L../../xtra_rhelz.x/libs/curl_7.53.1 -L../../xtra_rhelz.x/libs/boost/ \ 21 | -lboost_system -lssl -lcryptopp -lcrypto -lcurl -lssh2 -lrt -lz -lpthread \ 22 | -o fast_client 23 | -------------------------------------------------------------------------------- /examples/fast_client.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "HttpUtil.h" 16 | 17 | volatile bool start = false; 18 | volatile bool stop = false; 19 | 20 | time_t start_time = 0; 21 | volatile uint64_t count = 0; 22 | 23 | extern char* program_invocation_short_name; 24 | static void usage() { 25 | std::stringstream ss; 26 | 27 | ss << program_invocation_short_name << " [thread_num] url " << std::endl; 28 | ss << std::endl; 29 | 30 | std::cerr << ss.str(); 31 | } 32 | 33 | int random_int() { 34 | return (random() % 320) + 23; 35 | } 36 | 37 | std::string get_url; 38 | 39 | std::string post_url; 40 | std::string post_dat; 41 | 42 | void* perf_run(void* x_void_ptr) { 43 | 44 | // 客户端长连接 45 | 46 | auto client = std::make_shared(); 47 | int code = 0; 48 | 49 | while (!start) 50 | ::usleep(1); 51 | 52 | while (!stop) { 53 | 54 | if (!get_url.empty()) { 55 | code = client->GetByHttp(get_url); 56 | } else if (!post_url.empty() && !post_dat.empty()) { 57 | code = client->PostByHttp(post_url, post_dat); 58 | } else { 59 | std::cerr << "err.... " << std::endl; 60 | break; 61 | } 62 | 63 | if (code != 0) { 64 | std::cerr << "err for code " << code << std::endl; 65 | break; 66 | } 67 | 68 | // increment success case 69 | count++; 70 | } 71 | 72 | return NULL; 73 | } 74 | 75 | int main(int argc, char* argv[]) { 76 | 77 | 78 | int thread_num = 0; 79 | if (argc < 3 || (thread_num = ::atoi(argv[1])) <= 0) { 80 | usage(); 81 | return 0; 82 | } 83 | 84 | if (argc == 3) { 85 | get_url = std::string(argv[2]); 86 | std::cerr << "we will get for " << get_url << std::endl; 87 | } else if (argc >= 4) { 88 | post_url = std::string(argv[2]); 89 | post_dat = std::string(argv[3]); 90 | std::cerr << "we will post for " << post_url << ", dat " << post_dat << std::endl; 91 | } 92 | 93 | 94 | std::vector tids(thread_num, 0); 95 | for (size_t i = 0; i < tids.size(); ++i) { 96 | pthread_create(&tids[i], NULL, perf_run, NULL); 97 | std::cerr << "starting thread with id: " << tids[i] << std::endl; 98 | } 99 | 100 | ::sleep(3); 101 | std::cerr << "begin to test, press any to stop." << std::endl; 102 | start_time = ::time(NULL); 103 | start = true; 104 | 105 | int ch = getchar(); 106 | (void)ch; 107 | stop = true; 108 | time_t stop_time = ::time(NULL); 109 | 110 | uint64_t count_per_sec = count / (stop_time - start_time); 111 | fprintf(stderr, "total count %ld, time: %ld, perf: %ld tps\n", count, stop_time - start_time, count_per_sec); 112 | 113 | for (size_t i = 0; i < tids.size(); ++i) { 114 | pthread_join(tids[i], NULL); 115 | std::cerr << "joining " << tids[i] << std::endl; 116 | } 117 | 118 | std::cerr << "done" << std::endl; 119 | 120 | return 0; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /examples/libRoo.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taozhijiang/tzhttpd/15be1cf0a61fd63e51dacbb02c825498f963bfa2/examples/libRoo.a -------------------------------------------------------------------------------- /examples/main.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "HttpParser.h" 15 | #include "HttpServer.h" 16 | 17 | void init_signal_handle(); 18 | void usage(); 19 | int create_process_pid(); 20 | 21 | namespace tzhttpd { 22 | 23 | using namespace http_proto; 24 | 25 | int get_test_handler(const HttpParser& http_parser, 26 | std::string& response, std::string& status_line, std::vector& add_header) { 27 | response = "test uri called..."; 28 | add_header.push_back("TestHead1: value1"); 29 | add_header.push_back("TestHead2: value2"); 30 | status_line = generate_response_status_line(http_parser.get_version(), StatusCode::success_ok); 31 | return 0; 32 | } 33 | 34 | namespace http_handler { 35 | extern std::string http_server_version; 36 | } 37 | 38 | } // end namespace tzhttpd 39 | 40 | 41 | extern char* program_invocation_short_name; 42 | 43 | char cfgFile[PATH_MAX] = "httpsrv.conf"; 44 | bool daemonize = false; 45 | 46 | 47 | static int module_status(std::string& module, std::string& name, std::string& val) { 48 | 49 | module = "httpsrv"; 50 | name = "main"; 51 | 52 | val = "conf_file: " + std::string(cfgFile); 53 | 54 | return 0; 55 | } 56 | 57 | int main(int argc, char* argv[]) { 58 | 59 | int opt_g = 0; 60 | while ((opt_g = getopt(argc, argv, "c:dhv")) != -1) { 61 | switch (opt_g) { 62 | case 'c': 63 | memset(cfgFile, 0, sizeof(cfgFile)); 64 | strncpy(cfgFile, optarg, PATH_MAX); 65 | break; 66 | case 'd': 67 | daemonize = true; 68 | break; 69 | case 'v': 70 | std::cout << program_invocation_short_name << ": " 71 | << tzhttpd::http_handler::http_server_version << std::endl; 72 | break; 73 | case 'h': 74 | default: 75 | usage(); 76 | ::exit(EXIT_SUCCESS); 77 | } 78 | } 79 | 80 | 81 | auto setting = std::make_shared(); 82 | if (!setting || !setting->init(cfgFile)) { 83 | fprintf(stderr, "Create and init roo::Setting with cfg %s failed.", cfgFile); 84 | return false; 85 | } 86 | 87 | auto setting_ptr = setting->get_setting(); 88 | if (!setting_ptr) { 89 | fprintf(stderr, "roo::Setting return null pointer, maybe your conf file ill???"); 90 | return false; 91 | } 92 | 93 | int log_level = 0; 94 | setting_ptr->lookupValue("log_level", log_level); 95 | if (log_level <= 0 || log_level > 7) { 96 | fprintf(stderr, "Invalid log_level %d, reset to default 7(DEBUG).", log_level); 97 | log_level = 7; 98 | } 99 | 100 | std::string log_path; 101 | setting_ptr->lookupValue("log_path", log_path); 102 | if (log_path.empty()) 103 | log_path = "./log"; 104 | 105 | roo::log_init(log_level, "", log_path, LOG_LOCAL6); 106 | roo::log_warning("Initialized roo::Log with level %d, path %s.", log_level, log_path.c_str()); 107 | 108 | 109 | std::shared_ptr http_server_ptr; 110 | 111 | // daemonize should before any thread creation... 112 | if (daemonize) { 113 | roo::log_warning("we will daemonize this service..."); 114 | 115 | bool chdir = false; // leave the current working directory in case 116 | // the user has specified relative paths for 117 | // the config file, etc 118 | bool close = true; // close stdin, stdout, stderr 119 | if (::daemon(!chdir, !close) != 0) { 120 | roo::log_err("call to daemon() failed: %s", strerror(errno)); 121 | ::exit(EXIT_FAILURE); 122 | } 123 | } 124 | 125 | // 信号处理 126 | init_signal_handle(); 127 | 128 | http_server_ptr.reset(new tzhttpd::HttpServer(cfgFile, "example_main")); 129 | if (!http_server_ptr) { 130 | roo::log_err("create HttpServer failed!"); 131 | ::exit(EXIT_FAILURE); 132 | } 133 | 134 | // must called before http_server init 135 | http_server_ptr->add_http_vhost("example2.com"); 136 | http_server_ptr->add_http_vhost("www.example2.com"); 137 | http_server_ptr->add_http_vhost("www.example3.com"); 138 | 139 | if (!http_server_ptr->init()) { 140 | roo::log_err("init HttpServer failed!"); 141 | ::exit(EXIT_FAILURE); 142 | } 143 | 144 | http_server_ptr->add_http_get_handler("^/test$", tzhttpd::get_test_handler); 145 | http_server_ptr->register_http_status_callback("httpsrv", module_status); 146 | 147 | http_server_ptr->io_service_threads_.start_threads(); 148 | http_server_ptr->service(); 149 | 150 | http_server_ptr->io_service_threads_.join_threads(); 151 | 152 | return 0; 153 | } 154 | 155 | namespace boost { 156 | 157 | void assertion_failed(char const* expr, char const* function, char const* file, long line) { 158 | fprintf(stderr, "BAD!!! expr `%s` assert failed at %s(%ld): %s", expr, file, line, function); 159 | roo::log_err("BAD!!! expr `%s` assert failed at %s(%ld): %s", expr, file, line, function); 160 | } 161 | 162 | } // end boost 163 | -------------------------------------------------------------------------------- /examples/setup.cpp: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2019 TAO Zhijiang 3 | * 4 | * Licensed under the BSD-3-Clause license, see LICENSE for full information. 5 | * 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include "Global.h" 22 | 23 | // API for main 24 | 25 | void init_signal_handle(); 26 | void usage(); 27 | int create_process_pid(); 28 | 29 | 30 | static void interrupted_callback(int signal) { 31 | roo::log_warning("Signal %d received ...", signal); 32 | switch (signal) { 33 | case SIGHUP: 34 | roo::log_warning("SIGHUP recv, do update_run_conf... "); 35 | tzhttpd::Global::instance().setting_ptr()->update_runtime_setting(); 36 | break; 37 | 38 | case SIGUSR1: 39 | roo::log_warning("SIGUSR recv, do module_status ... "); 40 | { 41 | std::string output; 42 | tzhttpd::Global::instance().status_ptr()->collect_status(output); 43 | std::cout << output << std::endl; 44 | roo::log_warning("%s", output.c_str()); 45 | } 46 | break; 47 | 48 | default: 49 | roo::log_err("Unhandled signal: %d", signal); 50 | break; 51 | } 52 | } 53 | 54 | void init_signal_handle() { 55 | 56 | ::signal(SIGPIPE, SIG_IGN); 57 | ::signal(SIGUSR1, interrupted_callback); 58 | ::signal(SIGHUP, interrupted_callback); 59 | 60 | return; 61 | } 62 | 63 | extern char* program_invocation_short_name; 64 | void usage() { 65 | std::stringstream ss; 66 | 67 | ss << program_invocation_short_name << ":" << std::endl; 68 | ss << "\t -c cfgFile specify config file, default " << program_invocation_short_name << ".conf. " << std::endl; 69 | ss << "\t -d daemonize service." << std::endl; 70 | ss << "\t -v print version info." << std::endl; 71 | ss << std::endl; 72 | 73 | std::cout << ss.str(); 74 | } 75 | 76 | 77 | // /var/run/[program_invocation_short_name].pid --> root permission 78 | int create_process_pid() { 79 | 80 | char pid_msg[24]; 81 | char pid_file[PATH_MAX]; 82 | 83 | snprintf(pid_file, PATH_MAX, "./%s.pid", program_invocation_short_name); 84 | FILE* fp = fopen(pid_file, "w+"); 85 | 86 | if (!fp) { 87 | roo::log_err("Create pid file %s failed!", pid_file); 88 | return -1; 89 | } 90 | 91 | pid_t pid = ::getpid(); 92 | snprintf(pid_msg, sizeof(pid_msg), "%d\n", pid); 93 | fwrite(pid_msg, sizeof(char), strlen(pid_msg), fp); 94 | 95 | fclose(fp); 96 | return 0; 97 | } 98 | -------------------------------------------------------------------------------- /siege.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taozhijiang/tzhttpd/15be1cf0a61fd63e51dacbb02c825498f963bfa2/siege.png -------------------------------------------------------------------------------- /tzhttpd_example.conf: -------------------------------------------------------------------------------- 1 | // LOG_EMERG 0 /* system is unusable */ 2 | // LOG_ALERT 1 /* action must be taken immediately */ 3 | // LOG_CRIT 2 /* critical conditions */ 4 | // LOG_ERR 3 /* error conditions */ 5 | // LOG_WARNING 4 /* warning conditions */ 6 | // LOG_NOTICE 5 /* normal but significant condition */ 7 | // LOG_INFO 6 /* informational */ 8 | // LOG_DEBUG 7 /* debug-level messages */ 9 | log_level = 7; 10 | 11 | http = { 12 | 13 | version = "2.3.2"; // 本服务版本信息 14 | 15 | bind_addr = "0.0.0.0"; 16 | bind_port = 18430; 17 | safe_ip = "127.0.0.1;172.16.10.137;172.16.10.1"; // [D] 客户端访问白名单 18 | backlog_size = 10; 19 | 20 | 21 | io_thread_pool_size = 5; // 工作线程组数目 22 | session_cancel_time_out = 60; // [D] 会话超时的时间 23 | ops_cancel_time_out = 10; // [D] 异步IO操作超时时间,使用会影响性能(大概20%左右) 24 | 25 | // 流控相关 26 | service_enable = true; // [D] 是否允许服务 27 | service_speed = 0; // [D] 每1sec允许服务的数目,0表示不限制 28 | service_concurrency = 0; // [D] 最大并发连接数的限制 29 | 30 | // 不支持动态加载虚拟主机,需要显式进行注册才生效 31 | vhosts = ( 32 | { 33 | server_name = "[default]"; 34 | docu_root = "/var/www/html/"; 35 | docu_index = "index.html;index.htm;index"; 36 | exec_thread_pool_size = 2; // [D] 启动默认线程数目 37 | exec_thread_pool_size_hard = 5; // [D] 容许突发最大线程数 38 | exec_thread_pool_step_queue_size = 100; // [D] 默认resize线程组的数目 39 | 40 | basic_auth = ( 41 | { 42 | uri = "^/internal_manage$"; 43 | auth = ( 44 | { user = "admin"; passwd = "tjjtds"; } 45 | ); 46 | }, 47 | { 48 | uri = "^/cgi-bin/.*"; 49 | auth = ( 50 | { user = "usr1"; passwd = "passwd1"; }, 51 | { user = "usr2"; passwd = "passwd2"; } 52 | ); 53 | } 54 | ); 55 | 56 | // 下面接口可以动态增加,但是不能动态修改和删除 57 | cgi_get_handlers = ( 58 | { uri = "^/cgi-bin/getdemo.cgi$"; dl_path = "../cgi-bin/libgetdemo.so"; } 59 | ); 60 | 61 | cgi_post_handlers = ( 62 | { uri = "^/cgi-bin/postdemo.cgi$"; dl_path = "../cgi-bin/libpostdemo.so"; } 63 | ); 64 | 65 | // support Content-Encoding: gzip, deflate 66 | compress_control = ".xml;.txt;.html;.htm;.js"; 67 | }, 68 | { 69 | server_name = "example2.com"; 70 | docu_root = "/var/www/html_2/"; 71 | docu_index = "index.html;index.htm;index"; 72 | exec_thread_pool_size = 2; 73 | 74 | cache_control = ( 75 | { 76 | suffix = ".xml"; 77 | header = "Cache-Control: public, max-age=172800"; 78 | }, 79 | { 80 | suffix = ".jpg;.jpeg;.png;.bmp"; 81 | header = "Cache-Control: public, max-age=432000"; 82 | } 83 | ); 84 | }, 85 | { 86 | server_name = "www.example2.com"; 87 | redirect = "301~http://example2.com"; 88 | exec_thread_pool_size = 1; 89 | }, 90 | { 91 | server_name = "www.example3.com"; 92 | docu_root = "/var/www/html_3/"; 93 | docu_index = "index.html;index.htm;index"; 94 | exec_thread_pool_size = 3; 95 | } 96 | ); 97 | 98 | }; 99 | 100 | 101 | // demo 102 | // 103 | // curl http://127.0.0.1:18430/index.html 104 | // curl -X POST -d '{xxx}' http://127.0.0.1:18430/cgi-bin/postdemo.cgi -v 105 | // 106 | // siege -c200 --header="Authorization:Basic dXNyMTpwYXNzd2Qx" "http://127.0.0.1:18430/cgi-bin/getdemo.cgi" 107 | // siege -c200 --header="Authorization:Basic dXNyMTpwYXNzd2Qx" "http://127.0.0.1:18430/cgi-bin/postdemo.cgi POST {xxx}" 108 | --------------------------------------------------------------------------------