碼雲GVP專案 libhv C++跨平臺網路庫學習1上手測試
阿新 • • 發佈:2021-01-04
技術標籤:C/C++/VC/MFC
碼雲GVP專案 libhv C++跨平臺網路庫學習
一、簡介
libhv是一個類似於libevent、libev、libuv的基於C++的跨平臺網路庫,提供了更簡單的介面和更豐富的協議。
二、上手
1. 克隆專案
git clone https://gitee.com/ithewei/libhv
2. 開始
cd libhv
./getting_started.sh
該命令會編譯後自動執行一些測試命令。
編譯後提示執行
bin/httpd -c etc/httpd.conf -s restart -d
命令。
執行後,可以找開網頁:
http://localhost:8080/downloads/
三、 實現一個基本的http服務端
/* * sample http server * more detail see examples/httpd * */ #include "HttpServer.h" int main() { HV_MEMCHECK; HttpService service; service.GET("/ping", [](HttpRequest* req, HttpResponse* resp) { resp->body = "pong"; return 200; }); service.POST("/echo", [](HttpRequest* req, HttpResponse* resp) { resp->content_type = req->content_type; resp->body = req->body; return 200; }); http_server_t server; server.port = 8080; // uncomment to test multi-processes // server.worker_processes = 4; // uncomment to test multi-threads // server.worker_threads = 4; server.service = &service; #if 1 http_server_run(&server); #else // test http_server_stop http_server_run(&server, 0); sleep(10); http_server_stop(&server); #endif return 0; }
四、實現一個web服務框架
1. 程式碼說明
示例程式碼在exampels/httpd裡。
4.1.1 handler.h 控制器程式
#ifndef HV_HTTPD_HANDLER_H #define HV_HTTPD_HANDLER_H #include "HttpMessage.h" class Handler { public: // 資料流向 // preprocessor => handler => postprocessor static int preprocessor(HttpRequest* req, HttpResponse* resp) { // printf("%s:%d\n", req->client_addr.ip.c_str(), req->client_addr.port); // printf("%s\n", req->Dump(true, true).c_str()); // if (req->content_type != APPLICATION_JSON) { // return response_status(resp, HTTP_STATUS_BAD_REQUEST); // } req->ParseBody(); resp->content_type = APPLICATION_JSON; #if 0 // authentication sample code if (strcmp(req->path.c_str(), "/login") != 0) { string token = req->GetHeader("token"); if (token.empty()) { response_status(resp, 10011, "Miss token"); return HTTP_STATUS_UNAUTHORIZED; } else if (strcmp(token.c_str(), "abcdefg") != 0) { response_status(resp, 10012, "Token wrong"); return HTTP_STATUS_UNAUTHORIZED; } return 0; } #endif return 0; } static int postprocessor(HttpRequest* req, HttpResponse* resp) { // printf("%s\n", resp->Dump(true, true).c_str()); return 0; } static int sleep(HttpRequest* req, HttpResponse* resp) { time_t start_time = time(NULL); std::string strTime = req->GetParam("t"); if (!strTime.empty()) { int sec = atoi(strTime.c_str()); if (sec > 0) { hv_delay(sec*1000); } } time_t end_time = time(NULL); resp->Set("start_time", start_time); resp->Set("end_time", end_time); response_status(resp, 0, "OK"); return 200; } static int query(HttpRequest* req, HttpResponse* resp) { // scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment] // ?query => HttpRequest::query_params for (auto& param : req->query_params) { resp->Set(param.first.c_str(), param.second); } response_status(resp, 0, "OK"); return 200; } static int kv(HttpRequest* req, HttpResponse* resp) { if (req->content_type != APPLICATION_URLENCODED) { return response_status(resp, HTTP_STATUS_BAD_REQUEST); } resp->content_type = APPLICATION_URLENCODED; resp->kv = req->kv; resp->kv["int"] = hv::to_string(123); resp->kv["float"] = hv::to_string(3.14); resp->kv["string"] = "hello"; return 200; } static int json(HttpRequest* req, HttpResponse* resp) { if (req->content_type != APPLICATION_JSON) { return response_status(resp, HTTP_STATUS_BAD_REQUEST); } resp->content_type = APPLICATION_JSON; resp->json = req->json; resp->json["int"] = 123; resp->json["float"] = 3.14; resp->json["string"] = "hello"; return 200; } static int form(HttpRequest* req, HttpResponse* resp) { if (req->content_type != MULTIPART_FORM_DATA) { return response_status(resp, HTTP_STATUS_BAD_REQUEST); } resp->content_type = MULTIPART_FORM_DATA; resp->form = req->form; resp->form["int"] = 123; resp->form["float"] = 3.14; resp->form["float"] = "hello"; // resp->form["file"] = FormData("test.jpg"); return 200; } static int test(HttpRequest* req, HttpResponse* resp) { // bool b = req->Get<bool>("bool"); // int64_t n = req->Get<int64_t>("int"); // double f = req->Get<double>("float"); bool b = req->GetBool("bool"); int64_t n = req->GetInt("int"); double f = req->GetFloat("float"); string str = req->GetString("string"); resp->content_type = req->content_type; resp->Set("bool", b); resp->Set("int", n); resp->Set("float", f); resp->Set("string", str); response_status(resp, 0, "OK"); return 200; } static int grpc(HttpRequest* req, HttpResponse* resp) { if (req->content_type != APPLICATION_GRPC) { return response_status(resp, HTTP_STATUS_BAD_REQUEST); } // parse protobuf // ParseFromString(req->body); // resp->content_type = APPLICATION_GRPC; // serailize protobuf // resp->body = SerializeAsString(xxx); response_status(resp, 0, "OK"); return 200; } static int restful(HttpRequest* req, HttpResponse* resp) { // RESTful /:field/ => HttpRequest::query_params // path=/group/:group_name/user/:user_id // string group_name = req->GetParam("group_name"); // string user_id = req->GetParam("user_id"); for (auto& param : req->query_params) { resp->Set(param.first.c_str(), param.second); } response_status(resp, 0, "OK"); return 200; } static int login(HttpRequest* req, HttpResponse* resp) { string username = req->GetString("username"); string password = req->GetString("password"); if (username.empty() || password.empty()) { response_status(resp, 10001, "Miss username or password"); return HTTP_STATUS_BAD_REQUEST; } else if (strcmp(username.c_str(), "admin") != 0) { response_status(resp, 10002, "Username not exist"); return HTTP_STATUS_BAD_REQUEST; } else if (strcmp(password.c_str(), "123456") != 0) { response_status(resp, 10003, "Password wrong"); return HTTP_STATUS_BAD_REQUEST; } else { resp->Set("token", "abcdefg"); response_status(resp, 0, "OK"); return HTTP_STATUS_OK; } } static int upload(HttpRequest* req, HttpResponse* resp) { if (req->content_type != MULTIPART_FORM_DATA) { return response_status(resp, HTTP_STATUS_BAD_REQUEST); } FormData file = req->form["file"]; string filepath("html/uploads/"); filepath += file.filename; FILE* fp = fopen(filepath.c_str(), "w"); if (fp) { fwrite(file.content.data(), 1, file.content.size(), fp); fclose(fp); } response_status(resp, 0, "OK"); return 200; } private: static int response_status(HttpResponse* resp, int code = 200, const char* message = NULL) { resp->Set("code", code); if (message == NULL) message = http_status_str((enum http_status)code); resp->Set("message", message); resp->DumpBody(); return code; } }; #endif // HV_HTTPD_HANDLER_H
4.1.2 httpd.cpp 主服務
#include "hv.h"
#include "hmain.h"
#include "iniparser.h"
#include "HttpServer.h"
#include "router.h"
http_server_t g_http_server;
HttpService g_http_service;
static void print_version();
static void print_help();
static int parse_confile(const char* confile);
// short options
static const char options[] = "hvc:ts:dp:";
// long options
static const option_t long_options[] = {
{'h', "help", NO_ARGUMENT},
{'v', "version", NO_ARGUMENT},
{'c', "confile", REQUIRED_ARGUMENT},
{'t', "test", NO_ARGUMENT},
{'s', "signal", REQUIRED_ARGUMENT},
{'d', "daemon", NO_ARGUMENT},
{'p', "port", REQUIRED_ARGUMENT}
};
static const char detail_options[] = R"(
-h|--help Print this information
-v|--version Print version
-c|--confile <confile> Set configure file, default etc/{program}.conf
-t|--test Test configure file and exit
-s|--signal <signal> Send <signal> to process,
<signal>=[start,stop,restart,status,reload]
-d|--daemon Daemonize
-p|--port <port> Set listen port
)";
void print_version() {
printf("%s version %s\n", g_main_ctx.program_name, hv_compile_version());
}
void print_help() {
printf("Usage: %s [%s]\n", g_main_ctx.program_name, options);
printf("Options:\n%s\n", detail_options);
}
int parse_confile(const char* confile) {
IniParser ini;
int ret = ini.LoadFromFile(confile);
if (ret != 0) {
printf("Load confile [%s] failed: %d\n", confile, ret);
exit(-40);
}
// logfile
string str = ini.GetValue("logfile");
if (!str.empty()) {
strncpy(g_main_ctx.logfile, str.c_str(), sizeof(g_main_ctx.logfile));
}
hlog_set_file(g_main_ctx.logfile);
// loglevel
str = ini.GetValue("loglevel");
if (!str.empty()) {
hlog_set_level_by_str(str.c_str());
}
// log_filesize
str = ini.GetValue("log_filesize");
if (!str.empty()) {
hlog_set_max_filesize_by_str(str.c_str());
}
// log_remain_days
str = ini.GetValue("log_remain_days");
if (!str.empty()) {
hlog_set_remain_days(atoi(str.c_str()));
}
// log_fsync
str = ini.GetValue("log_fsync");
if (!str.empty()) {
logger_enable_fsync(hlog, getboolean(str.c_str()));
}
hlogi("%s version: %s", g_main_ctx.program_name, hv_compile_version());
hlog_fsync();
// worker_processes
int worker_processes = 0;
str = ini.GetValue("worker_processes");
if (str.size() != 0) {
if (strcmp(str.c_str(), "auto") == 0) {
worker_processes = get_ncpu();
hlogd("worker_processes=ncpu=%d", worker_processes);
}
else {
worker_processes = atoi(str.c_str());
}
}
g_http_server.worker_processes = LIMIT(0, worker_processes, MAXNUM_WORKER_PROCESSES);
// worker_threads
int worker_threads = ini.Get<int>("worker_threads");
g_http_server.worker_threads = LIMIT(0, worker_threads, 16);
// port
int port = 0;
const char* szPort = get_arg("p");
if (szPort) {
port = atoi(szPort);
}
if (port == 0) {
port = ini.Get<int>("port");
}
if (port == 0) {
printf("Please config listen port!\n");
exit(-10);
}
g_http_server.port = port;
// http server
// base_url
str = ini.GetValue("base_url");
if (str.size() != 0) {
g_http_service.base_url = str;
}
// document_root
str = ini.GetValue("document_root");
if (str.size() != 0) {
g_http_service.document_root = str;
}
// home_page
str = ini.GetValue("home_page");
if (str.size() != 0) {
g_http_service.home_page = str;
}
// error_page
str = ini.GetValue("error_page");
if (str.size() != 0) {
g_http_service.error_page = str;
}
// index_of
str = ini.GetValue("index_of");
if (str.size() != 0) {
g_http_service.index_of = str;
}
// ssl
str = ini.GetValue("ssl");
if (getboolean(str.c_str())) {
g_http_server.ssl = 1;
std::string crt_file = ini.GetValue("ssl_certificate");
std::string key_file = ini.GetValue("ssl_privatekey");
std::string ca_file = ini.GetValue("ssl_ca_certificate");
hssl_ctx_init_param_t param;
memset(¶m, 0, sizeof(param));
param.crt_file = crt_file.c_str();
param.key_file = key_file.c_str();
param.ca_file = ca_file.c_str();
if (hssl_ctx_init(¶m) == NULL) {
hloge("SSL certificate verify failed!");
exit(0);
}
else {
hlogi("SSL certificate verify ok!");
}
}
hlogi("parse_confile('%s') OK", confile);
return 0;
}
static void on_reload(void* userdata) {
hlogi("reload confile [%s]", g_main_ctx.confile);
parse_confile(g_main_ctx.confile);
}
int main(int argc, char** argv) {
// g_main_ctx
main_ctx_init(argc, argv);
//int ret = parse_opt(argc, argv, options);
int ret = parse_opt_long(argc, argv, long_options, ARRAY_SIZE(long_options));
if (ret != 0) {
print_help();
exit(ret);
}
/*
printf("---------------arg------------------------------\n");
printf("%s\n", g_main_ctx.cmdline);
for (auto& pair : g_main_ctx.arg_kv) {
printf("%s=%s\n", pair.first.c_str(), pair.second.c_str());
}
for (auto& item : g_main_ctx.arg_list) {
printf("%s\n", item.c_str());
}
printf("================================================\n");
*/
/*
printf("---------------env------------------------------\n");
for (auto& pair : g_main_ctx.env_kv) {
printf("%s=%s\n", pair.first.c_str(), pair.second.c_str());
}
printf("================================================\n");
*/
// help
if (get_arg("h")) {
print_help();
exit(0);
}
// version
if (get_arg("v")) {
print_version();
exit(0);
}
// parse_confile
const char* confile = get_arg("c");
if (confile) {
strncpy(g_main_ctx.confile, confile, sizeof(g_main_ctx.confile));
}
parse_confile(g_main_ctx.confile);
// test
if (get_arg("t")) {
printf("Test confile [%s] OK!\n", g_main_ctx.confile);
exit(0);
}
// signal
signal_init(on_reload);
const char* signal = get_arg("s");
if (signal) {
signal_handle(signal);
}
#ifdef OS_UNIX
// daemon
if (get_arg("d")) {
// nochdir, noclose
int ret = daemon(1, 1);
if (ret != 0) {
printf("daemon error: %d\n", ret);
exit(-10);
}
}
#endif
// pidfile
create_pidfile();
// http_server
Router::Register(g_http_service);
g_http_server.service = &g_http_service;
ret = http_server_run(&g_http_server);
return ret;
}
4.1.3 router.h 網址路由
#ifndef HV_HTTPD_ROUTER_H
#define HV_HTTPD_ROUTER_H
#include "HttpService.h"
#include "handler.h"
class Router {
public:
static void Register(HttpService& http) {
// preprocessor => Handler => postprocessor
http.preprocessor = Handler::preprocessor;
http.postprocessor = Handler::postprocessor;
// curl -v http://ip:port/ping
http.GET("/ping", [](HttpRequest* req, HttpResponse* resp) {
resp->body = "pong";
return 200;
});
// curl -v http://ip:port/echo -d "hello,world!"
http.POST("/echo", [](HttpRequest* req, HttpResponse* resp) {
resp->content_type = req->content_type;
resp->body = req->body;
return 200;
});
// curl -v http://ip:port/sleep?t=3
http.GET("/sleep", Handler::sleep);
// curl -v http://ip:port/query?page_no=1\&page_size=10
http.GET("/query", Handler::query);
// Content-Type: application/x-www-form-urlencoded
// curl -v http://ip:port/kv -H "content-type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
http.POST("/kv", Handler::kv);
// Content-Type: application/json
// curl -v http://ip:port/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
http.POST("/json", Handler::json);
// Content-Type: multipart/form-data
// bin/curl -v localhost:8080/form -F "user=admin pswd=123456"
http.POST("/form", Handler::form);
// curl -v http://ip:port/test -H "Content-Type:application/x-www-form-urlencoded" -d 'bool=1&int=123&float=3.14&string=hello'
// curl -v http://ip:port/test -H "Content-Type:application/json" -d '{"bool":true,"int":123,"float":3.14,"string":"hello"}'
// bin/curl -v http://ip:port/test -F 'bool=1 int=123 float=3.14 string=hello'
http.POST("/test", Handler::test);
// Content-Type: application/grpc
// bin/curl -v --http2 http://ip:port/grpc -H "content-type:application/grpc" -d 'protobuf'
http.POST("/grpc", Handler::grpc);
// RESTful API: /group/:group_name/user/:user_id
// curl -v -X DELETE http://ip:port/group/test/user/123
http.Delete("/group/:group_name/user/:user_id", Handler::restful);
// bin/curl -v localhost:8080/upload -F "[email protected]"
http.POST("/upload", Handler::upload);
// curl -v http://ip:port/login -H "Content-Type:application/json" -d '{"username":"admin","password":"123456"}'
http.POST("/login", Handler::login);
}
};
#endif // HV_HTTPD_ROUTER_H
2. 編譯命令
4.2.1 庫編譯
make libhv
sudo make install
4.2.2 從examples裡提取httpd的編譯指令碼
Makefile裡有詳細的examples編譯指令碼,這裡看一個 httpd 編譯相關配置:
include config.mk
include Makefile.vars
MAKEF=$(MAKE) -f Makefile.in
ALL_SRCDIRS=. base utils event protocol http http/client http/server consul examples
LIBHV_SRCDIRS = . base utils event
LIBHV_HEADERS = hv.h hconfig.h hexport.h
LIBHV_HEADERS += $(BASE_HEADERS) $(UTILS_HEADERS) $(EVENT_HEADERS)
ifeq ($(WITH_PROTOCOL), yes)
LIBHV_HEADERS += $(PROTOCOL_HEADERS)
LIBHV_SRCDIRS += protocol
endif
ifeq ($(WITH_HTTP), yes)
LIBHV_HEADERS += $(HTTP_HEADERS)
LIBHV_SRCDIRS += http
ifeq ($(WITH_HTTP_SERVER), yes)
LIBHV_HEADERS += $(HTTP_SERVER_HEADERS)
LIBHV_SRCDIRS += http/server
endif
ifeq ($(WITH_HTTP_CLIENT), yes)
LIBHV_HEADERS += $(HTTP_CLIENT_HEADERS)
LIBHV_SRCDIRS += http/client
ifeq ($(WITH_CONSUL), yes)
LIBHV_HEADERS += $(CONSUL_HEADERS)
LIBHV_SRCDIRS += consul
endif
endif
endif
clean:
$(MAKEF) clean SRCDIRS="$(ALL_SRCDIRS)"
$(RM) include/hv
prepare:
$(MKDIR) bin
install:
$(MKDIR) $(INSTALL_INCDIR)
$(CP) include/hv/* $(INSTALL_INCDIR)
httpd: prepare
$(RM) examples/httpd/*.o
$(MAKEF) [email protected] SRCDIRS=". base utils event http http/server examples/httpd"
.PHONY: clean prepare libhv install examples httpd
4.2.3 編譯命令
make httpd
4.2.4 執行程式
bin/httpd -d
# 或
bin/httpd -c etc/httpd.conf -s restart -d
引數說明:
-h|--help
列印幫助
-v|--version
版本資訊
-c|--confile <confile>
指定配置檔案
-t|--test
測試配置檔案
-s|--signal <signal>
訊號 =[start,stop,restart,status,reload]
-d|--daemon
後臺常駐程式
-p|--port <port>
指定埠