hewei.it 5 years ago
parent
commit
7c0817b7fc

+ 16 - 18
README.md

@@ -42,15 +42,13 @@ see `examples/httpd/httpd.cpp`
 ```c++
 #include "HttpServer.h"
 
-int http_api_echo(HttpRequest* req, HttpResponse* res) {
-    res->body = req->body;
-    return 0;
-}
-
 int main() {
     HttpService service;
     service.base_url = "/v1/api";
-    service.AddApi("/echo", HTTP_POST, http_api_echo);
+    service.POST("/echo", [](HttpRequest* req, HttpResponse* res) {
+        res->body = req->body;
+        return 200;
+    });
 
     http_server_t server;
     server.port = 8080;
@@ -99,19 +97,19 @@ bin/curl -v localhost:8080
 bin/curl -v localhost:8080/downloads/
 
 # http api service
-bin/curl -v localhost:8080/v1/api/hello
-bin/curl -v localhost:8080/v1/api/echo -d "hello,world!"
-bin/curl -v localhost:8080/v1/api/query?page_no=1\&page_size=10
-bin/curl -v localhost:8080/v1/api/kv   -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
-bin/curl -v localhost:8080/v1/api/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
-bin/curl -v localhost:8080/v1/api/form -F "user=admin pswd=123456"
-bin/curl -v localhost:8080/v1/api/upload -F "file=@LICENSE"
-
-bin/curl -v localhost:8080/v1/api/test -H "Content-Type:application/x-www-form-urlencoded" -d 'bool=1&int=123&float=3.14&string=hello'
-bin/curl -v localhost:8080/v1/api/test -H "Content-Type:application/json" -d '{"bool":true,"int":123,"float":3.14,"string":"hello"}'
-bin/curl -v localhost:8080/v1/api/test -F 'bool=1 int=123 float=3.14 string=hello'
+bin/curl -v localhost:8080/ping
+bin/curl -v localhost:8080/echo -d "hello,world!"
+bin/curl -v localhost:8080/query?page_no=1\&page_size=10
+bin/curl -v localhost:8080/kv   -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
+bin/curl -v localhost:8080/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
+bin/curl -v localhost:8080/form -F "user=admin pswd=123456"
+bin/curl -v localhost:8080/upload -F "file=@LICENSE"
+
+bin/curl -v localhost:8080/test -H "Content-Type:application/x-www-form-urlencoded" -d 'bool=1&int=123&float=3.14&string=hello'
+bin/curl -v localhost:8080/test -H "Content-Type:application/json" -d '{"bool":true,"int":123,"float":3.14,"string":"hello"}'
+bin/curl -v localhost:8080/test -F 'bool=1 int=123 float=3.14 string=hello'
 # RESTful API: /group/:group_name/user/:user_id
-bin/curl -v -X DELETE localhost:8080/v1/api/group/test/user/123
+bin/curl -v -X DELETE localhost:8080/group/test/user/123
 
 # webbench (linux only)
 make webbench

+ 43 - 0
base/hlog.c

@@ -102,6 +102,28 @@ void logger_set_level(logger_t* logger, int level) {
     logger->level = level;
 }
 
+void logger_set_level_by_str(logger_t* logger, const char* szLoglevel) {
+    int loglevel = DEFAULT_LOG_LEVEL;
+    if (strcmp(szLoglevel, "VERBOSE") == 0) {
+        loglevel = LOG_LEVEL_VERBOSE;
+    } else if (strcmp(szLoglevel, "DEBUG") == 0) {
+        loglevel = LOG_LEVEL_DEBUG;
+    } else if (strcmp(szLoglevel, "INFO") == 0) {
+        loglevel = LOG_LEVEL_INFO;
+    } else if (strcmp(szLoglevel, "WARN") == 0) {
+        loglevel = LOG_LEVEL_WARN;
+    } else if (strcmp(szLoglevel, "ERROR") == 0) {
+        loglevel = LOG_LEVEL_ERROR;
+    } else if (strcmp(szLoglevel, "FATAL") == 0) {
+        loglevel = LOG_LEVEL_FATAL;
+    } else if (strcmp(szLoglevel, "SILENT") == 0) {
+        loglevel = LOG_LEVEL_SILENT;
+    } else {
+        loglevel = DEFAULT_LOG_LEVEL;
+    }
+    logger->level = loglevel;
+}
+
 void logger_set_remain_days(logger_t* logger, int days) {
     logger->remain_days = days;
 }
@@ -128,6 +150,27 @@ void logger_set_max_filesize(logger_t* logger, unsigned long long filesize) {
     logger->max_filesize = filesize;
 }
 
+void logger_set_max_filesize_by_str(logger_t* logger, const char* str) {
+    int num = atoi(str);
+    if (num <= 0) return;
+    // 16 16M 16MB
+    const char* e = str;
+    while (*e != '\0') ++e;
+    --e;
+    char unit;
+    if (*e >= '0' && *e <= '9') unit = 'M';
+    else if (*e == 'B')         unit = *(e-1);
+    else                        unit = *e;
+    unsigned long long filesize = num;
+    switch (unit) {
+    case 'K': filesize <<= 10; break;
+    case 'M': filesize <<= 20; break;
+    case 'G': filesize <<= 30; break;
+    default:  filesize <<= 20; break;
+    }
+    logger->max_filesize = filesize;
+}
+
 void logger_enable_fsync(logger_t* logger, int on) {
     logger->enable_fsync = on;
 }

+ 7 - 1
base/hlog.h

@@ -47,7 +47,7 @@ typedef enum {
 } log_level_e;
 
 #define DEFAULT_LOG_FILE            "default"
-#define DEFAULT_LOG_LEVEL           LOG_LEVEL_VERBOSE
+#define DEFAULT_LOG_LEVEL           LOG_LEVEL_INFO
 #define DEFAULT_LOG_REMAIN_DAYS     1
 #define DEFAULT_LOG_MAX_BUFSIZE     (1<<14)  // 16k
 #define DEFAULT_LOG_MAX_FILESIZE    (1<<24)  // 16M
@@ -68,6 +68,8 @@ HV_EXPORT void logger_destroy(logger_t* logger);
 
 HV_EXPORT void logger_set_handler(logger_t* logger, logger_handler fn);
 HV_EXPORT void logger_set_level(logger_t* logger, int level);
+// level = [VERBOSE,DEBUG,INFO,WARN,ERROR,FATAL,SILENT]
+HV_EXPORT void logger_set_level_by_str(logger_t* logger, const char* level);
 HV_EXPORT void logger_set_max_bufsize(logger_t* logger, unsigned int bufsize);
 HV_EXPORT void logger_enable_color(logger_t* logger, int on);
 HV_EXPORT int  logger_print(logger_t* logger, int level, const char* fmt, ...);
@@ -75,6 +77,8 @@ HV_EXPORT int  logger_print(logger_t* logger, int level, const char* fmt, ...);
 // below for file logger
 HV_EXPORT void logger_set_file(logger_t* logger, const char* filepath);
 HV_EXPORT void logger_set_max_filesize(logger_t* logger, unsigned long long filesize);
+// 16, 16M, 16MB
+HV_EXPORT void logger_set_max_filesize_by_str(logger_t* logger, const char* filesize);
 HV_EXPORT void logger_set_remain_days(logger_t* logger, int days);
 HV_EXPORT void logger_enable_fsync(logger_t* logger, int on);
 HV_EXPORT void logger_fsync(logger_t* logger);
@@ -87,7 +91,9 @@ HV_EXPORT logger_t* hv_default_logger();
 #define hlog hv_default_logger()
 #define hlog_set_file(filepath)         logger_set_file(hlog, filepath)
 #define hlog_set_level(level)           logger_set_level(hlog, level)
+#define hlog_set_level_by_str(level)    logger_set_level_by_str(hlog, level)
 #define hlog_set_max_filesize(filesize) logger_set_max_filesize(hlog, filesize)
+#define hlog_set_max_filesize_by_str(filesize) logger_set_max_filesize_by_str(hlog, filesize)
 #define hlog_set_remain_days(days)      logger_set_remain_days(hlog, days)
 #define hlog_enable_fsync()             logger_enable_fsync(hlog, 1)
 #define hlog_disable_fsync()            logger_enable_fsync(hlog, 0)

+ 1 - 1
etc/httpd.conf

@@ -11,7 +11,7 @@ worker_processes = 4
 
 # http server
 port = 8080
-base_url = /v1/api
+#base_url = /v1/api
 document_root = html
 home_page = index.html
 #error_page = error.html

+ 4 - 57
examples/hmain_test.cpp

@@ -71,47 +71,14 @@ int parse_confile(const char* confile) {
     }
     hlog_set_file(g_main_ctx.logfile);
     // loglevel
-    const char* szLoglevel = g_conf_ctx.parser->GetValue("loglevel").c_str();
-    int loglevel = LOG_LEVEL_INFO;
-    if (stricmp(szLoglevel, "VERBOSE") == 0) {
-        loglevel = LOG_LEVEL_VERBOSE;
-    } else if (stricmp(szLoglevel, "DEBUG") == 0) {
-        loglevel = LOG_LEVEL_DEBUG;
-    } else if (stricmp(szLoglevel, "INFO") == 0) {
-        loglevel = LOG_LEVEL_INFO;
-    } else if (stricmp(szLoglevel, "WARN") == 0) {
-        loglevel = LOG_LEVEL_WARN;
-    } else if (stricmp(szLoglevel, "ERROR") == 0) {
-        loglevel = LOG_LEVEL_ERROR;
-    } else if (stricmp(szLoglevel, "FATAL") == 0) {
-        loglevel = LOG_LEVEL_FATAL;
-    } else if (stricmp(szLoglevel, "SILENT") == 0) {
-        loglevel = LOG_LEVEL_SILENT;
-    } else {
-        loglevel = LOG_LEVEL_INFO;
+    str = g_conf_ctx.parser->GetValue("loglevel");
+    if (!str.empty()) {
+        hlog_set_level_by_str(str.c_str());
     }
-    g_conf_ctx.loglevel = loglevel;
-    hlog_set_level(loglevel);
     // log_filesize
     str = g_conf_ctx.parser->GetValue("log_filesize");
     if (!str.empty()) {
-        int num = atoi(str.c_str());
-        if (num > 0) {
-            // 16 16M 16MB
-            const char* p = str.c_str() + str.size() - 1;
-            char unit;
-            if (*p >= '0' && *p <= '9') unit = 'M';
-            else if (*p == 'B')         unit = *(p-1);
-            else                        unit = *p;
-            unsigned long long filesize = num;
-            switch (unit) {
-            case 'K': filesize <<= 10; break;
-            case 'M': filesize <<= 20; break;
-            case 'G': filesize <<= 30; break;
-            default:  filesize <<= 20; break;
-            }
-            hlog_set_max_filesize(filesize);
-        }
+        hlog_set_max_filesize_by_str(str.c_str());
     }
     // log_remain_days
     str = g_conf_ctx.parser->GetValue("log_remain_days");
@@ -163,24 +130,6 @@ int parse_confile(const char* confile) {
     return 0;
 }
 
-void master_init(void* userdata) {
-#ifdef OS_UNIX
-    char proctitle[256] = {0};
-    snprintf(proctitle, sizeof(proctitle), "%s: master process", g_main_ctx.program_name);
-    setproctitle(proctitle);
-    signal(SIGNAL_RELOAD, signal_handler);
-#endif
-}
-
-void worker_init(void* userdata) {
-#ifdef OS_UNIX
-    char proctitle[256] = {0};
-    snprintf(proctitle, sizeof(proctitle), "%s: worker process", g_main_ctx.program_name);
-    setproctitle(proctitle);
-    signal(SIGNAL_RELOAD, signal_handler);
-#endif
-}
-
 static void on_reload(void* userdata) {
     hlogi("reload confile [%s]", g_main_ctx.confile);
     parse_confile(g_main_ctx.confile);
@@ -258,8 +207,6 @@ int main(int argc, char** argv) {
             printf("daemon error: %d\n", ret);
             exit(-10);
         }
-        // parent process exit after daemon, so pid changed.
-        g_main_ctx.pid = getpid();
     }
 #endif
 

+ 170 - 0
examples/httpd/handler.h

@@ -0,0 +1,170 @@
+#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* res) {
+        // 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(res, HTTP_STATUS_BAD_REQUEST);
+        // }
+        req->ParseBody();
+        res->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(res, 10011, "Miss token");
+                res->DumpBody();
+                return HTTP_STATUS_UNAUTHORIZED;
+            }
+            else if (strcmp(token.c_str(), "abcdefg") != 0) {
+                response_status(res, 10012, "Token wrong");
+                res->DumpBody();
+                return HTTP_STATUS_UNAUTHORIZED;
+            }
+            return 0;
+        }
+#endif
+        return 0;
+    }
+
+    static int postprocessor(HttpRequest* req, HttpResponse* res) {
+        // printf("%s\n", res->Dump(true, true).c_str());
+        return 0;
+    }
+
+    static int query(HttpRequest* req, HttpResponse* res) {
+        // scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
+        // ?query => HttpRequest::query_params
+        for (auto& param : req->query_params) {
+            res->Set(param.first.c_str(), param.second);
+        }
+        response_status(res, 0, "OK");
+        return 200;
+    }
+
+    static int kv(HttpRequest* req, HttpResponse* res) {
+        if (req->content_type != APPLICATION_URLENCODED) {
+            return response_status(res, HTTP_STATUS_BAD_REQUEST);
+        }
+        res->content_type = APPLICATION_URLENCODED;
+        res->kv = req->kv;
+        return 200;
+    }
+
+    static int json(HttpRequest* req, HttpResponse* res) {
+        if (req->content_type != APPLICATION_JSON) {
+            return response_status(res, HTTP_STATUS_BAD_REQUEST);
+        }
+        res->content_type = APPLICATION_JSON;
+        res->json = req->json;
+        return 200;
+    }
+
+    static int form(HttpRequest* req, HttpResponse* res) {
+        if (req->content_type != MULTIPART_FORM_DATA) {
+            return response_status(res, HTTP_STATUS_BAD_REQUEST);
+        }
+        res->content_type = MULTIPART_FORM_DATA;
+        res->form = req->form;
+        return 200;
+    }
+
+    static int test(HttpRequest* req, HttpResponse* res) {
+        // 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");
+
+        res->content_type = req->content_type;
+        res->Set("bool", b);
+        res->Set("int", n);
+        res->Set("float", f);
+        res->Set("string", str);
+        response_status(res, 0, "OK");
+        return 200;
+    }
+
+    static int grpc(HttpRequest* req, HttpResponse* res) {
+        if (req->content_type != APPLICATION_GRPC) {
+            return response_status(res, HTTP_STATUS_BAD_REQUEST);
+        }
+        // parse protobuf
+        // ParseFromString(req->body);
+        // res->content_type = APPLICATION_GRPC;
+        // serailize protobuf
+        // res->body = SerializeAsString(xxx);
+        response_status(res, 0, "OK");
+        return 200;
+    }
+
+    static int restful(HttpRequest* req, HttpResponse* res) {
+        // 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) {
+            res->Set(param.first.c_str(), param.second);
+        }
+        response_status(res, 0, "OK");
+        return 200;
+    }
+
+    static int login(HttpRequest* req, HttpResponse* res) {
+        string username = req->GetString("username");
+        string password = req->GetString("password");
+        if (username.empty() || password.empty()) {
+            response_status(res, 10001, "Miss username or password");
+            return HTTP_STATUS_BAD_REQUEST;
+        }
+        else if (strcmp(username.c_str(), "admin") != 0) {
+            response_status(res, 10002, "Username not exist");
+            return HTTP_STATUS_BAD_REQUEST;
+        }
+        else if (strcmp(password.c_str(), "123456") != 0) {
+            response_status(res, 10003, "Password wrong");
+            return HTTP_STATUS_BAD_REQUEST;
+        }
+        else {
+            res->Set("token", "abcdefg");
+            response_status(res, 0, "Login succeed.");
+            return HTTP_STATUS_OK;
+        }
+    }
+
+    static int upload(HttpRequest* req, HttpResponse* res) {
+        if (req->content_type != MULTIPART_FORM_DATA) {
+            return response_status(res, 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(res, 0, "OK");
+        return 200;
+    }
+
+private:
+    static int response_status(HttpResponse* res, int code = 200, const char* message = NULL) {
+        res->Set("code", code);
+        if (message == NULL) message = http_status_str((enum http_status)code);
+        res->Set("message", message);
+        res->DumpBody();
+        return code;
+    }
+};
+
+#endif // HV_HTTPD_HANDLER_H

+ 0 - 190
examples/httpd/http_api_test.h

@@ -1,190 +0,0 @@
-#ifndef HTTP_API_TEST_H_
-#define HTTP_API_TEST_H_
-
-#include "HttpServer.h"
-
-// XXX(path, method, handler)
-#define HTTP_API_MAP(XXX) \
-    XXX("/login",   POST,   http_api_login)     \
-    XXX("/hello",   GET,    http_api_hello)     \
-    XXX("/query",   GET,    http_api_query)     \
-    XXX("/echo",    POST,   http_api_echo)      \
-    XXX("/kv",      POST,   http_api_kv)        \
-    XXX("/json",    POST,   http_api_json)      \
-    XXX("/form",    POST,   http_api_form)      \
-    XXX("/upload",  POST,   http_api_upload)    \
-    XXX("/grpc",    POST,   http_api_grpc)      \
-    \
-    XXX("/test",    POST,   http_api_test)      \
-    XXX("/group/:group_name/user/:user_id", DELETE, http_api_restful)   \
-
-inline void response_status(HttpResponse* res, int code, const char* message) {
-    res->Set("code", code);
-    res->Set("message", message);
-}
-
-inline int http_api_preprocessor(HttpRequest* req, HttpResponse* res) {
-    //printf("%s:%d\n", req->client_addr.ip.c_str(), req->client_addr.port);
-    //printf("%s\n", req->Dump(true, true).c_str());
-    req->ParseBody();
-    res->content_type = APPLICATION_JSON;
-#if 0
-    // authentication sample code
-    if (strcmp(req->path.c_str(), DEFAULT_BASE_URL "/login") != 0) {
-        string token = req->GetHeader("token");
-        if (token.empty()) {
-            response_status(res, 10011, "Miss token");
-            res->DumpBody();
-            return HTTP_STATUS_UNAUTHORIZED;
-        }
-        else if (strcmp(token.c_str(), "abcdefg") != 0) {
-            response_status(res, 10012, "Token wrong");
-            res->DumpBody();
-            return HTTP_STATUS_UNAUTHORIZED;
-        }
-        return 0;
-    }
-#endif
-    return 0;
-}
-
-inline int http_api_postprocessor(HttpRequest* req, HttpResponse* res) {
-    res->DumpBody();
-    //printf("%s\n", res->Dump(true, true).c_str());
-    return 0;
-}
-
-inline int http_api_login(HttpRequest* req, HttpResponse* res) {
-    int ret = 0;
-    string username = req->GetString("username");
-    string password = req->GetString("password");
-    if (username.empty() || password.empty()) {
-        response_status(res, 10001, "Miss username or password");
-        ret = HTTP_STATUS_BAD_REQUEST;
-    }
-    else if (strcmp(username.c_str(), "admin") != 0) {
-        response_status(res, 10002, "Username not exist");
-        ret = HTTP_STATUS_BAD_REQUEST;
-    }
-    else if (strcmp(password.c_str(), "123456") != 0) {
-        response_status(res, 10003, "Password wrong");
-        ret = HTTP_STATUS_BAD_REQUEST;
-    }
-    else {
-        res->Set("token", "abcdefg");
-        response_status(res, 0, "Login succeed.");
-        ret = HTTP_STATUS_OK;
-    }
-    res->DumpBody();
-    return ret;
-}
-
-inline int http_api_hello(HttpRequest* req, HttpResponse* res) {
-    res->content_type = TEXT_PLAIN;
-    res->body = "hello";
-    return 0;
-}
-
-inline int http_api_query(HttpRequest* req, HttpResponse* res) {
-    // scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
-    // ?query => HttpRequest::query_params
-    for (auto& param : req->query_params) {
-        res->Set(param.first.c_str(), param.second);
-    }
-    response_status(res, 0, "Query completed.");
-    return 0;
-}
-
-inline int http_api_echo(HttpRequest* req, HttpResponse* res) {
-    res->content_type = req->content_type;
-    res->body = req->body;
-    return 0;
-}
-
-inline int http_api_kv(HttpRequest*req, HttpResponse* res) {
-    if (req->content_type != APPLICATION_URLENCODED) {
-        return HTTP_STATUS_BAD_REQUEST;
-    }
-    res->content_type = APPLICATION_URLENCODED;
-    res->kv = req->kv;
-    return 0;
-}
-
-inline int http_api_json(HttpRequest* req, HttpResponse* res) {
-    if (req->content_type != APPLICATION_JSON) {
-        return HTTP_STATUS_BAD_REQUEST;
-    }
-    res->content_type = APPLICATION_JSON;
-    res->json = req->json;
-    return 0;
-}
-
-inline int http_api_form(HttpRequest* req, HttpResponse* res) {
-    if (req->content_type != MULTIPART_FORM_DATA) {
-        return HTTP_STATUS_BAD_REQUEST;
-    }
-    res->content_type = MULTIPART_FORM_DATA;
-    res->form = req->form;
-    return 0;
-}
-
-inline int http_api_upload(HttpRequest* req, HttpResponse* res) {
-    if (req->content_type != MULTIPART_FORM_DATA) {
-        return 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) {
-        hlogi("Save as %s", filepath.c_str());
-        fwrite(file.content.data(), 1, file.content.size(), fp);
-        fclose(fp);
-    }
-    response_status(res, 0, "OK");
-    return 0;
-}
-
-inline int http_api_grpc(HttpRequest* req, HttpResponse* res) {
-    if (req->content_type != APPLICATION_GRPC) {
-        return HTTP_STATUS_BAD_REQUEST;
-    }
-    // parse protobuf: ParseFromString
-    // req->body;
-    // res->content_type = APPLICATION_GRPC;
-    // serailize protobuf: SerializeAsString
-    // res->body;
-    return 0;
-}
-
-inline int http_api_test(HttpRequest* req, HttpResponse* res) {
-    string str = req->GetString("string");
-    //int64_t n = req->Get<int64_t>("int");
-    //double f = req->Get<double>("float");
-    //bool b = req->Get<bool>("bool");
-    int64_t n = req->GetInt("int");
-    double f = req->GetFloat("float");
-    bool b = req->GetBool("bool");
-
-    res->content_type = req->content_type;
-    res->Set("string", str);
-    res->Set("int", n);
-    res->Set("float", f);
-    res->Set("bool", b);
-    response_status(res, 0, "OK");
-    return 0;
-}
-
-inline int http_api_restful(HttpRequest* req, HttpResponse* res) {
-    // 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) {
-        res->Set(param.first.c_str(), param.second);
-    }
-    response_status(res, 0, "Operation completed.");
-    return 0;
-}
-
-#endif // HTTP_API_TEST_H_

+ 13 - 47
examples/httpd/httpd.cpp

@@ -3,9 +3,10 @@
 #include "iniparser.h"
 
 #include "HttpServer.h"
-#include "http_api_test.h"
 #include "ssl_ctx.h"
 
+#include "router.h"
+
 http_server_t   g_http_server;
 HttpService     g_http_service;
 
@@ -61,53 +62,27 @@ int parse_confile(const char* confile) {
     }
     hlog_set_file(g_main_ctx.logfile);
     // loglevel
-    const char* szLoglevel = ini.GetValue("loglevel").c_str();
-    int loglevel = LOG_LEVEL_DEBUG;
-    if (stricmp(szLoglevel, "VERBOSE") == 0) {
-        loglevel = LOG_LEVEL_VERBOSE;
-    } else if (stricmp(szLoglevel, "DEBUG") == 0) {
-        loglevel = LOG_LEVEL_DEBUG;
-    } else if (stricmp(szLoglevel, "INFO") == 0) {
-        loglevel = LOG_LEVEL_INFO;
-    } else if (stricmp(szLoglevel, "WARN") == 0) {
-        loglevel = LOG_LEVEL_WARN;
-    } else if (stricmp(szLoglevel, "ERROR") == 0) {
-        loglevel = LOG_LEVEL_ERROR;
-    } else if (stricmp(szLoglevel, "FATAL") == 0) {
-        loglevel = LOG_LEVEL_FATAL;
-    } else if (stricmp(szLoglevel, "SILENT") == 0) {
-        loglevel = LOG_LEVEL_SILENT;
-    } else {
-        loglevel = LOG_LEVEL_VERBOSE;
+    str = ini.GetValue("loglevel");
+    if (!str.empty()) {
+        hlog_set_level_by_str(str.c_str());
     }
-    hlog_set_level(loglevel);
     // log_filesize
     str = ini.GetValue("log_filesize");
     if (!str.empty()) {
-        int num = atoi(str.c_str());
-        if (num > 0) {
-            // 16 16M 16MB
-            const char* p = str.c_str() + str.size() - 1;
-            char unit;
-            if (*p >= '0' && *p <= '9') unit = 'M';
-            else if (*p == 'B')         unit = *(p-1);
-            else                        unit = *p;
-            unsigned long long filesize = num;
-            switch (unit) {
-            case 'K': filesize <<= 10; break;
-            case 'M': filesize <<= 20; break;
-            case 'G': filesize <<= 30; break;
-            default:  filesize <<= 20; break;
-            }
-            hlog_set_max_filesize(filesize);
-        }
+        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;
@@ -263,23 +238,14 @@ int main(int argc, char** argv) {
             printf("daemon error: %d\n", ret);
             exit(-10);
         }
-        // parent process exit after daemon, so pid changed.
-        g_main_ctx.pid = getpid();
     }
 #endif
 
     // pidfile
     create_pidfile();
 
-    // HttpService
-    g_http_service.preprocessor = http_api_preprocessor;
-    g_http_service.postprocessor = http_api_postprocessor;
-#define XXX(path, method, handler) \
-    g_http_service.AddApi(path, HTTP_##method, handler);
-    HTTP_API_MAP(XXX)
-#undef XXX
-
     // http_server
+    Router::Register(g_http_service);
     g_http_server.service = &g_http_service;
     ret = http_server_run(&g_http_server);
     return ret;

+ 61 - 0
examples/httpd/router.h

@@ -0,0 +1,61 @@
+#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* res) {
+            res->body = "PONG";
+            return 200;
+        });
+
+        // curl -v http://ip:port/echo -d "hello,world!"
+        http.POST("/echo", [](HttpRequest* req, HttpResponse* res) {
+            res->content_type = req->content_type;
+            res->body = req->body;
+            return 200;
+        });
+
+        // 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);
+    }
+};
+
+#endif // HV_HTTPD_ROUTER_H

+ 13 - 10
getting_started.sh

@@ -24,35 +24,38 @@ bin/curl -v localhost:8080/downloads/
 
 # http api service
 read -n1
-bin/curl -v localhost:8080/v1/api/echo -d "hello,world!"
+bin/curl -v localhost:8080/ping
 
 read -n1
-bin/curl -v localhost:8080/v1/api/query?page_no=1\&page_size=10
+bin/curl -v localhost:8080/echo -d "hello,world!"
 
 read -n1
-bin/curl -v localhost:8080/v1/api/kv   -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
+bin/curl -v localhost:8080/query?page_no=1\&page_size=10
 
 read -n1
-bin/curl -v localhost:8080/v1/api/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
+bin/curl -v localhost:8080/kv   -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
 
 read -n1
-bin/curl -v localhost:8080/v1/api/form -F "user=admin pswd=123456"
+bin/curl -v localhost:8080/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
 
 read -n1
-bin/curl -v localhost:8080/v1/api/upload -F "file=@LICENSE"
+bin/curl -v localhost:8080/form -F "user=admin pswd=123456"
 
 read -n1
-bin/curl -v localhost:8080/v1/api/test -H "Content-Type:application/x-www-form-urlencoded" -d 'bool=1&int=123&float=3.14&string=hello'
+bin/curl -v localhost:8080/upload -F "file=@LICENSE"
 
 read -n1
-bin/curl -v localhost:8080/v1/api/test -H "Content-Type:application/json" -d '{"bool":true,"int":123,"float":3.14,"string":"hello"}'
+bin/curl -v localhost:8080/test -H "Content-Type:application/x-www-form-urlencoded" -d 'bool=1&int=123&float=3.14&string=hello'
 
 read -n1
-bin/curl -v localhost:8080/v1/api/test -F 'bool=1 int=123 float=3.14 string=hello'
+bin/curl -v localhost:8080/test -H "Content-Type:application/json" -d '{"bool":true,"int":123,"float":3.14,"string":"hello"}'
+
+read -n1
+bin/curl -v localhost:8080/test -F 'bool=1 int=123 float=3.14 string=hello'
 
 # RESTful API: /group/:group_name/user/:user_id
 read -n1
-bin/curl -v -X DELETE localhost:8080/v1/api/group/test/user/123
+bin/curl -v -X DELETE localhost:8080/group/test/user/123
 
 # see logs
 read -n1

+ 1 - 1
html/uploads/upload.sh

@@ -1 +1 @@
-bin/curl -v localhost:8080/v1/api/upload -F "file=@LICENSE"
+bin/curl -v localhost:8080/upload -F "file=@LICENSE"

+ 4 - 1
http/http_content.cpp

@@ -158,7 +158,10 @@ struct multipart_parser_userdata {
 
     void handle_data() {
         if (name.size() != 0) {
-            (*mp)[name] = FormData(part_data.c_str(), filename.c_str());
+            FormData formdata;
+            formdata.content = part_data;
+            formdata.filename = filename;
+            (*mp)[name] = formdata;
         }
         name.clear();
         filename.clear();

+ 42 - 3
http/server/HttpService.h

@@ -38,19 +38,20 @@ struct HV_EXPORT HttpService {
     // preprocessor -> api -> web -> postprocessor
     http_api_handler    preprocessor;
     http_api_handler    postprocessor;
-    // api service
+    // api service (that is http.APIServer)
     std::string         base_url;
     http_api_handlers   api_handlers;
-    // web service
+    // web service (that is http.FileServer)
     std::string document_root;
     std::string home_page;
     std::string error_page;
+    // indexof service (that is http.DirectoryServer)
     std::string index_of;
 
     HttpService() {
         preprocessor = NULL;
         postprocessor = NULL;
-        base_url = DEFAULT_BASE_URL;
+        // base_url = DEFAULT_BASE_URL;
         document_root = DEFAULT_DOCUMENT_ROOT;
         home_page = DEFAULT_HOME_PAGE;
     }
@@ -60,6 +61,44 @@ struct HV_EXPORT HttpService {
     int GetApi(const char* url, http_method method, http_api_handler* handler);
     // RESTful API /:field/ => req->query_params["field"]
     int GetApi(HttpRequest* req, http_api_handler* handler);
+
+    // github.com/gin-gonic/gin
+    void Handle(const char* httpMethod, const char* relativePath, http_api_handler handlerFunc) {
+        AddApi(relativePath, http_method_enum(httpMethod), handlerFunc);
+    }
+
+    void HEAD(const char* relativePath, http_api_handler handlerFunc) {
+        Handle("HEAD", relativePath, handlerFunc);
+    }
+
+    void GET(const char* relativePath, http_api_handler handlerFunc) {
+        Handle("GET", relativePath, handlerFunc);
+    }
+
+    void POST(const char* relativePath, http_api_handler handlerFunc) {
+        Handle("POST", relativePath, handlerFunc);
+    }
+
+    void PUT(const char* relativePath, http_api_handler handlerFunc) {
+        Handle("PUT", relativePath, handlerFunc);
+    }
+
+    void DELETE(const char* relativePath, http_api_handler handlerFunc) {
+        Handle("DELETE", relativePath, handlerFunc);
+    }
+
+    void PATCH(const char* relativePath, http_api_handler handlerFunc) {
+        Handle("PATCH", relativePath, handlerFunc);
+    }
+
+    void Any(const char* relativePath, http_api_handler handlerFunc) {
+        Handle("HEAD", relativePath, handlerFunc);
+        Handle("GET", relativePath, handlerFunc);
+        Handle("POST", relativePath, handlerFunc);
+        Handle("PUT", relativePath, handlerFunc);
+        Handle("DELETE", relativePath, handlerFunc);
+        Handle("PATCH", relativePath, handlerFunc);
+    }
 };
 
 #endif // HTTP_SERVICE_H_

+ 16 - 18
readme_cn.md

@@ -41,15 +41,13 @@ see `examples/httpd/httpd.cpp`
 ```c++
 #include "HttpServer.h"
 
-int http_api_echo(HttpRequest* req, HttpResponse* res) {
-    res->body = req->body;
-    return 0;
-}
-
 int main() {
     HttpService service;
     service.base_url = "/v1/api";
-    service.AddApi("/echo", HTTP_POST, http_api_echo);
+    service.POST("/echo", [](HttpRequest* req, HttpResponse* res) {
+        res->body = req->body;
+        return 200;
+    });
 
     http_server_t server;
     server.port = 8080;
@@ -98,19 +96,19 @@ bin/curl -v localhost:8080
 bin/curl -v localhost:8080/downloads/
 
 # http api service
-bin/curl -v localhost:8080/v1/api/hello
-bin/curl -v localhost:8080/v1/api/echo -d "hello,world!"
-bin/curl -v localhost:8080/v1/api/query?page_no=1\&page_size=10
-bin/curl -v localhost:8080/v1/api/kv   -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
-bin/curl -v localhost:8080/v1/api/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
-bin/curl -v localhost:8080/v1/api/form -F "user=admin pswd=123456"
-bin/curl -v localhost:8080/v1/api/upload -F "file=@LICENSE"
-
-bin/curl -v localhost:8080/v1/api/test -H "Content-Type:application/x-www-form-urlencoded" -d 'bool=1&int=123&float=3.14&string=hello'
-bin/curl -v localhost:8080/v1/api/test -H "Content-Type:application/json" -d '{"bool":true,"int":123,"float":3.14,"string":"hello"}'
-bin/curl -v localhost:8080/v1/api/test -F 'bool=1 int=123 float=3.14 string=hello'
+bin/curl -v localhost:8080/ping
+bin/curl -v localhost:8080/echo -d "hello,world!"
+bin/curl -v localhost:8080/query?page_no=1\&page_size=10
+bin/curl -v localhost:8080/kv   -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
+bin/curl -v localhost:8080/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
+bin/curl -v localhost:8080/form -F "user=admin pswd=123456"
+bin/curl -v localhost:8080/upload -F "file=@LICENSE"
+
+bin/curl -v localhost:8080/test -H "Content-Type:application/x-www-form-urlencoded" -d 'bool=1&int=123&float=3.14&string=hello'
+bin/curl -v localhost:8080/test -H "Content-Type:application/json" -d '{"bool":true,"int":123,"float":3.14,"string":"hello"}'
+bin/curl -v localhost:8080/test -F 'bool=1 int=123 float=3.14 string=hello'
 # RESTful API: /group/:group_name/user/:user_id
-bin/curl -v -X DELETE localhost:8080/v1/api/group/test/user/123
+bin/curl -v -X DELETE localhost:8080/group/test/user/123
 
 # webbench (linux only)
 make webbench

+ 1 - 0
utils/hmain.cpp

@@ -305,6 +305,7 @@ int create_pidfile() {
         return -1;
     }
 
+    g_main_ctx.pid = hv_getpid();
     char pid[16] = {0};
     snprintf(pid, sizeof(pid), "%d\n", g_main_ctx.pid);
     fwrite(pid, 1, strlen(pid), fp);