1. 程式人生 > 其它 >碼雲GVP專案 libhv C++跨平臺網路庫學習1上手測試

碼雲GVP專案 libhv C++跨平臺網路庫學習1上手測試

技術標籤: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(&param, 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(&param) == 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> 指定埠