一、簡介
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 optionsstatic const char options[] = "hvc:ts:dp:";// long optionsstatic 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;}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
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 "file=@LICENSE" 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 libhvsudo make install
4.2.2 從examples裡提取httpd的編譯指令碼
Makefile裡有詳細的examples編譯指令碼,這裡看一個 httpd 編譯相關配置:
include config.mkinclude Makefile.varsMAKEF=$(MAKE) -f Makefile.inALL_SRCDIRS=. base utils event protocol http http/client http/server consul examplesLIBHV_SRCDIRS = . base utils eventLIBHV_HEADERS = hv.h hconfig.h hexport.hLIBHV_HEADERS += $(BASE_HEADERS) $(UTILS_HEADERS) $(EVENT_HEADERS)ifeq ($(WITH_PROTOCOL), yes)LIBHV_HEADERS += $(PROTOCOL_HEADERS)LIBHV_SRCDIRS += protocolendififeq ($(WITH_HTTP), yes)LIBHV_HEADERS += $(HTTP_HEADERS)LIBHV_SRCDIRS += httpifeq ($(WITH_HTTP_SERVER), yes)LIBHV_HEADERS += $(HTTP_SERVER_HEADERS)LIBHV_SRCDIRS += http/serverendififeq ($(WITH_HTTP_CLIENT), yes)LIBHV_HEADERS += $(HTTP_CLIENT_HEADERS)LIBHV_SRCDIRS += http/clientifeq ($(WITH_CONSUL), yes)LIBHV_HEADERS += $(CONSUL_HEADERS)LIBHV_SRCDIRS += consulendifendifendifclean: $(MAKEF) clean SRCDIRS="$(ALL_SRCDIRS)" $(RM) include/hvprepare: $(MKDIR) bininstall: $(MKDIR) $(INSTALL_INCDIR) $(CP) include/hv/* $(INSTALL_INCDIR)httpd: prepare $(RM) examples/httpd/*.o $(MAKEF) TARGET=$@ 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> 指定埠
最新評論