hewei 6 лет назад
Родитель
Сommit
b2f4586147

+ 16 - 15
Makefile

@@ -18,12 +18,12 @@ libhv: prepare
 	$(RM) include
 	$(MAKEF) TARGET=$@ TARGET_TYPE=SHARED SRCDIRS=". base utils event http http/client http/server protocol"
 	$(MAKEF) TARGET=$@ TARGET_TYPE=STATIC SRCDIRS=". base utils event http http/client http/server protocol"
-	$(MKDIR) include
-	$(CP) $(INSTALL_HEADERS) include
+	$(MKDIR) include/hv
+	$(CP) $(INSTALL_HEADERS) include/hv
 
 install:
 	$(MKDIR) -p $(INSTALL_INCDIR)
-	$(CP) include/* $(INSTALL_INCDIR)
+	$(CP) include/hv/* $(INSTALL_INCDIR)
 	$(CP) lib/libhv.a lib/libhv.so $(INSTALL_LIBDIR)
 
 test: prepare
@@ -83,17 +83,18 @@ consul_cli: prepare
 	$(MAKEF) TARGET=$@ SRCDIRS=". base utils http http/client consul $(TMPDIR)" DEFINES="$(DEFINES) PRINT_DEBUG"
 
 unittest: prepare
-	$(CC)  -g -Wall -std=c99   -I. -Ibase            -o bin/hmutex     unittest/hmutex_test.c        -pthread
-	$(CC)  -g -Wall -std=c99   -I. -Ibase            -o bin/connect    unittest/connect_test.c       base/hsocket.c
-	$(CC)  -g -Wall -std=c99   -I. -Ibase            -o bin/socketpair unittest/socketpair_test.c    base/hsocket.c
-	$(CXX) -g -Wall -std=c++11 -I. -Ibase            -o bin/defer      unittest/defer_test.cpp
-	$(CXX) -g -Wall -std=c++11 -I. -Ibase            -o bin/threadpool unittest/threadpool_test.cpp  -pthread
-	$(CXX) -g -Wall -std=c++11 -I. -Ibase            -o bin/objectpool unittest/objectpool_test.cpp  -pthread
-	$(CXX) -g -Wall -std=c++11 -I. -Ibase            -o bin/ls         unittest/listdir_test.cpp     base/hdir.cpp base/hbase.c
-	$(CXX) -g -Wall -std=c++11 -I. -Ibase -Iutils    -o bin/ifconfig   unittest/ifconfig_test.cpp    utils/ifconfig.cpp
-	$(CC)  -g -Wall -std=c99   -I. -Ibase -Iprotocol -o bin/nslookup   unittest/nslookup_test.c      protocol/dns.c
-	$(CC)  -g -Wall -std=c99   -I. -Ibase -Iprotocol -o bin/ping       unittest/ping_test.c          protocol/icmp.c base/hsocket.c base/htime.c -DPRINT_DEBUG
-	$(CC)  -g -Wall -std=c99   -I. -Ibase -Iprotocol -o bin/ftp        unittest/ftp_test.c           protocol/ftp.c  base/hsocket.c
+	$(CC)  -g -Wall -std=c99   -I. -Ibase            -o bin/hmutex_test       unittest/hmutex_test.c        -pthread
+	$(CC)  -g -Wall -std=c99   -I. -Ibase            -o bin/connect_test      unittest/connect_test.c       base/hsocket.c
+	$(CC)  -g -Wall -std=c99   -I. -Ibase            -o bin/socketpair_test   unittest/socketpair_test.c    base/hsocket.c
+	$(CXX) -g -Wall -std=c++11 -I. -Ibase            -o bin/defer_test        unittest/defer_test.cpp
+	$(CXX) -g -Wall -std=c++11 -I. -Ibase            -o bin/hstring_test      unittest/hstring_test.cpp     base/hstring.cpp base/hbase.c
+	$(CXX) -g -Wall -std=c++11 -I. -Ibase            -o bin/threadpool_test   unittest/threadpool_test.cpp  -pthread
+	$(CXX) -g -Wall -std=c++11 -I. -Ibase            -o bin/objectpool_test   unittest/objectpool_test.cpp  -pthread
+	$(CXX) -g -Wall -std=c++11 -I. -Ibase            -o bin/ls                unittest/listdir_test.cpp     base/hdir.cpp base/hbase.c
+	$(CXX) -g -Wall -std=c++11 -I. -Ibase -Iutils    -o bin/ifconfig          unittest/ifconfig_test.cpp    utils/ifconfig.cpp
+	$(CC)  -g -Wall -std=c99   -I. -Ibase -Iprotocol -o bin/nslookup          unittest/nslookup_test.c      protocol/dns.c
+	$(CC)  -g -Wall -std=c99   -I. -Ibase -Iprotocol -o bin/ping              unittest/ping_test.c          protocol/icmp.c base/hsocket.c base/htime.c -DPRINT_DEBUG
+	$(CC)  -g -Wall -std=c99   -I. -Ibase -Iprotocol -o bin/ftp               unittest/ftp_test.c           protocol/ftp.c  base/hsocket.c
 	$(CC)  -g -Wall -std=c99   -I. -Ibase -Iutils -Iprotocol -o bin/sendmail  unittest/sendmail_test.c  protocol/smtp.c base/hsocket.c utils/base64.c
 
 # UNIX only
@@ -104,7 +105,7 @@ echo-servers:
 	$(CC)  -g -Wall -std=c99   -o bin/libevent_echo echo-servers/libevent_echo.c -levent
 	$(CC)  -g -Wall -std=c99   -o bin/libev_echo    echo-servers/libev_echo.c    -lev
 	$(CC)  -g -Wall -std=c99   -o bin/libuv_echo    echo-servers/libuv_echo.c    -luv
-	$(CC)  -g -Wall -std=c99   -o bin/libhv_echo    echo-servers/libhv_echo.c    -Iinclude -Llib -lhv
+	$(CC)  -g -Wall -std=c99   -o bin/libhv_echo    echo-servers/libhv_echo.c    -Iinclude/hv -Llib -lhv
 	$(CXX) -g -Wall -std=c++11 -o bin/asio_echo     echo-servers/asio_echo.cpp   -lboost_system
 	$(CXX) -g -Wall -std=c++11 -o bin/poco_echo     echo-servers/poco_echo.cpp   -lPocoNet -lPocoUtil -lPocoFoundation
 	$(CXX) -g -Wall -std=c++11 -o bin/muduo_echo    echo-servers/muduo_echo.cpp  -lmuduo_net -lmuduo_base -lpthread

+ 9 - 1
README.md

@@ -13,6 +13,7 @@ but simpler apis and richer protocols.
 - enable IPv6
 - with OpenSSL
 - http client/server (include https http1/x http2 grpc)
+- http web service, indexof service, api service (support RESTful API)
 - protocols
     - dns
     - ftp
@@ -70,7 +71,14 @@ bin/curl -v localhost:8080
 bin/curl -v localhost:8080/downloads/
 
 # http api service
-bin/curl -v -X POST localhost:8080/v1/api/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
+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/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
+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/mp   -F "file=@LICENSE"
+# RESTful API: /group/:group_name/user/:user_id
+bin/curl -v -X DELETE localhost:8080/v1/api/group/test/user/123
 
 # webbench (linux only)
 make webbench

+ 31 - 0
base/hbase.c

@@ -114,6 +114,37 @@ char* safe_strncat(char* dest, const char* src, size_t n) {
     return ret;
 }
 
+bool strstartswith(const char* str, const char* start) {
+    assert(str != NULL && start != NULL);
+    while (*str && *start && *str == *start) {
+        ++str;
+        ++start;
+    }
+    return *start == '\0';
+}
+
+bool strendswith(const char* str, const char* end) {
+    assert(str != NULL && end != NULL);
+    int len1 = 0;
+    int len2 = 0;
+    while (*str++) {++len1;}
+    while (*end++) {++len2;}
+    if (len1 < len2) return false;
+    while (len2-- > 0) {
+        --str;
+        --end;
+        if (*str != *end) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool strcontains(const char* str, const char* sub) {
+    assert(str != NULL && sub != NULL);
+    return strstr(str, sub) != NULL;
+}
+
 bool getboolean(const char* str) {
     if (str == NULL) return false;
     int len = strlen(str);

+ 4 - 0
base/hbase.h

@@ -52,6 +52,10 @@ char* strupper(char* str);
 char* strlower(char* str);
 char* strreverse(char* str);
 
+bool strstartswith(const char* str, const char* start);
+bool strendswith(const char* str, const char* end);
+bool strcontains(const char* str, const char* sub);
+
 // strncpy n = sizeof(dest_buf)-1
 // safe_strncpy n = sizeof(dest_buf)
 char* safe_strncpy(char* dest, const char* src, size_t n);

+ 49 - 3
base/hstring.cpp

@@ -5,9 +5,6 @@
 #include <string.h>
 #include <stdarg.h>
 
-#include <iostream>
-#include <sstream>
-
 static inline int vscprintf(const char* fmt, va_list ap) {
     return vsnprintf(NULL, 0, fmt, ap);
 }
@@ -31,6 +28,7 @@ string asprintf(const char* fmt, ...) {
 }
 
 StringList split(const string& str, char delim) {
+    /*
     std::stringstream ss;
     ss << str;
     string item;
@@ -39,6 +37,54 @@ StringList split(const string& str, char delim) {
         res.push_back(item);
     }
     return res;
+    */
+    const char* p = str.c_str();
+    const char* value = p;
+    StringList res;
+    while (*p != '\0') {
+        if (*p == delim) {
+            res.push_back(std::string(value, p-value));
+            value = p+1;
+        }
+        ++p;
+    }
+    res.push_back(value);
+    return res;
+}
+
+KeyValue splitKV(const string& str, char kv_kv, char k_v) {
+    enum {
+        s_key,
+        s_value,
+    } state = s_key;
+    const char* p = str.c_str();
+    const char* key = p;
+    const char* value = NULL;
+    int key_len = 0;
+    int value_len = 0;
+    KeyValue kvs;
+    while (*p != '\0') {
+        if (*p == kv_kv) {
+            if (key_len && value_len) {
+                kvs[std::string(key, key_len)] = std::string(value, value_len);
+                key_len = value_len = 0;
+            }
+            state = s_key;
+            key = p+1;
+        }
+        else if (*p == k_v) {
+            state = s_value;
+            value = p+1;
+        }
+        else {
+            state == s_key ? ++key_len : ++value_len;
+        }
+        ++p;
+    }
+    if (key_len && value_len) {
+        kvs[std::string(key, key_len)] = std::string(value, value_len);
+    }
+    return kvs;
 }
 
 string trim(const string& str, const char* chars) {

+ 48 - 1
base/hstring.h

@@ -3,12 +3,56 @@
 
 #include <string>
 #include <vector>
+#include <map>
 
 #include "hbase.h"
 
 using std::string;
 typedef std::vector<string> StringList;
 
+// MultiMap
+namespace std {
+/*
+int main() {
+    std::MultiMap<std::string, std::string> kvs;
+    kvs["name"] = "hw";
+    kvs["filename"] = "1.jpg";
+    kvs["filename"] = "2.jpg";
+    //kvs.insert(std::pair<std::string,std::string>("name", "hw"));
+    //kvs.insert(std::pair<std::string,std::string>("filename", "1.jpg"));
+    //kvs.insert(std::pair<std::string,std::string>("filename", "2.jpg"));
+    for (auto& pair : kvs) {
+        printf("%s:%s\n", pair.first.c_str(), pair.second.c_str());
+    }
+    auto iter = kvs.find("filename");
+    if (iter != kvs.end()) {
+        for (int i = 0; i < kvs.count("filename"); ++i, ++iter) {
+            printf("%s:%s\n", iter->first.c_str(), iter->second.c_str());
+        }
+    }
+    return 0;
+}
+ */
+template<typename Key,typename Value>
+class MultiMap : public multimap<Key, Value> {
+public:
+    Value& operator[](Key key) {
+        auto iter = this->insert(std::pair<Key,Value>(key,Value()));
+        return (*iter).second;
+    }
+};
+}
+
+// MAP
+#ifdef USE_MULTIMAP
+#define MAP     std::MultiMap
+#else
+#define MAP     std::map
+#endif
+
+// KeyValue
+typedef MAP<std::string, std::string> KeyValue;
+
 // std::map<std::string, std::string, StringCaseLess>
 class StringCaseLess : public std::binary_function<std::string, std::string, bool> {
 public:
@@ -21,7 +65,10 @@ public:
 #define PAIR_CHARS      "{}[]()<>\"\"\'\'``"
 
 string asprintf(const char* fmt, ...);
-StringList split(const string& str, char delim);
+// x,y,z
+StringList split(const string& str, char delim = ',');
+// user=amdin&pswd=123456
+KeyValue   splitKV(const string& str, char kv_kv = '&', char k_v = '=');
 string trim(const string& str, const char* chars = SPACE_CHARS);
 string trimL(const string& str, const char* chars = SPACE_CHARS);
 string trimR(const string& str, const char* chars = SPACE_CHARS);

+ 66 - 13
examples/curl.cpp

@@ -16,9 +16,10 @@ static const char* url = NULL;
 static const char* method = NULL;
 static const char* headers = NULL;
 static const char* data = NULL;
+static const char* form = NULL;
 static int  send_count   = 1;
 
-static const char* options = "hVvX:H:d:n:";
+static const char* options = "hVvX:H:d:F:n:";
 static const struct option long_options[] = {
     {"help",    no_argument,        NULL,   'h'},
     {"verion",  no_argument,        NULL,   'V'},
@@ -26,6 +27,7 @@ static const struct option long_options[] = {
     {"method",  required_argument,  NULL,   'X'},
     {"header",  required_argument,  NULL,   'H'},
     {"data",    required_argument,  NULL,   'd'},
+    {"form",    required_argument,  NULL,   'F'},
     {"http2",   no_argument,        &http_version, 2},
     {"grpc",    no_argument,        &grpc,  1},
     {"count",   required_argument,  NULL,   'n'},
@@ -36,17 +38,20 @@ static const char* help = R"(Options:
     -V|--version        Print version.
     -v|--verbose        Show verbose infomation.
     -X|--method         Set http method.
-    -H|--header         Add http headers, format -H "Content-Type:application/json Accept:*/*"
+    -H|--header         Add http headers, -H "Content-Type:application/json Accept:*/*"
     -d|--data           Set http body.
+    -F|--form           Set http form, -F "name1=content;name2=@filename"
     -n|--count          Send request count, used for test keep-alive
        --http2          Use http2
        --grpc           Use grpc over http2
 Examples:
-    curl -v localhost:8086
-    curl -v localhost:8086/v1/api/query?page_no=1&page_size=10
-    curl -v -X POST localhost:8086/v1/api/json  -H "Content-Type:application/json"                  -d '{"user":"admin","pswd":"123456"}'
-    curl -v -X POST localhost:8086/v1/api/kv    -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
-    curl -v -X POST localhost:8086/v1/api/echo  -H "Content-Type:text/plain"                        -d 'hello,world!'
+    curl -v localhost:8080
+    curl -v localhost:8080/v1/api/hello
+    curl -v localhost:8080/v1/api/query?page_no=1&page_size=10
+    curl -v localhost:8080/v1/api/echo  -d 'hello,world!'
+    curl -v localhost:8080/v1/api/json  -H "Content-Type:application/json"                  -d '{"user":"admin","pswd":"123456"}'
+    curl -v localhost:8080/v1/api/kv    -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
+    curl -v localhost:8080/v1/api/mp    -F 'file=@filename'
 )";
 
 void print_usage() {
@@ -72,6 +77,7 @@ int parse_cmdline(int argc, char* argv[]) {
         case 'X': method = optarg; break;
         case 'H': headers = optarg; break;
         case 'd': data = optarg; break;
+        case 'F': form = optarg; break;
         case 'n': send_count = atoi(optarg); break;
         default: break;
         }
@@ -109,16 +115,17 @@ int main(int argc, char* argv[]) {
     if (method) {
         req.method = http_method_enum(method);
     }
+    enum {
+        s_key,
+        s_value,
+    } state = s_key;
     if (headers) {
-        enum {
-            s_key,
-            s_value,
-        } state = s_key;
         const char* p = headers;
         const char* key = p;
         const char* value = NULL;
         int key_len = 0;
         int value_len = 0;
+        state = s_key;
         while (*p != '\0') {
             if (*p == ' ') {
                 if (key_len && value_len) {
@@ -142,11 +149,57 @@ int main(int argc, char* argv[]) {
             key_len = value_len = 0;
         }
     }
-    if (data) {
+    if (data || form) {
         if (method == NULL) {
             req.method = HTTP_POST;
         }
-        req.body = data;
+        if (data) {
+            req.body = data;
+        }
+        else if (form) {
+            const char* p = form;
+            const char* key = p;
+            const char* value = NULL;
+            int key_len = 0;
+            int value_len = 0;
+            state = s_key;
+            while (*p != '\0') {
+                if (*p == ';') {
+                    if (key_len && value_len) {
+                        FormData data;
+                        if (*value == '@') {
+                            data.filename = std::string(value+1, value_len-1);
+                        }
+                        else {
+                            data.content = std::string(value, value_len);
+                        }
+                        req.mp[std::string(key,key_len)] = data;
+                        key_len = value_len = 0;
+                    }
+                    state = s_key;
+                    key = p+1;
+                }
+                else if (*p == '=') {
+                    state = s_value;
+                    value = p+1;
+                }
+                else {
+                    state == s_key ? ++key_len : ++value_len;
+                }
+                ++p;
+            }
+            if (key_len && value_len) {
+                printf("key=%.*s value=%.*s\n", key_len, key, value_len, value);
+                FormData data;
+                if (*value == '@') {
+                    data.filename = std::string(value+1, value_len-1);
+                }
+                else {
+                    data.content = std::string(value, value_len);
+                }
+                req.mp[std::string(key,key_len)] = data;
+            }
+        }
     }
     HttpResponse res;
     http_client_t* hc = http_client_new();

+ 31 - 19
examples/http_api_test.h

@@ -5,12 +5,14 @@
 
 // XXX(path, method, handler)
 #define HTTP_API_MAP(XXX) \
+    XXX("/hello",   GET,    http_api_hello)     \
+    XXX("/query",   GET,    http_api_query)     \
+    XXX("/echo",    POST,   http_api_echo)      \
     XXX("/json",    POST,   http_api_json)      \
     XXX("/mp",      POST,   http_api_mp)        \
     XXX("/kv",      POST,   http_api_kv)        \
     XXX("/grpc",    POST,   http_api_grpc)      \
-    XXX("/query",   GET,    http_api_query)     \
-    XXX("/echo",    POST,   http_api_echo)      \
+    XXX("/group/:group_name/user/:user_id", DELETE, http_api_restful)   \
 
 
 inline int http_api_preprocessor(HttpRequest* req, HttpResponse* res) {
@@ -25,6 +27,22 @@ inline int http_api_postprocessor(HttpRequest* req, HttpResponse* res) {
     return 0;
 }
 
+inline int http_api_hello(HttpRequest* req, HttpResponse* res) {
+    res->body = "hello";
+    return 0;
+}
+
+inline int http_api_query(HttpRequest* req, HttpResponse* res) {
+    res->kv = req->query_params;
+    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_json(HttpRequest* req, HttpResponse* res) {
     if (req->content_type != APPLICATION_JSON) {
         res->status_code = HTTP_STATUS_BAD_REQUEST;
@@ -43,37 +61,31 @@ inline int http_api_mp(HttpRequest* req, HttpResponse* res) {
     return 0;
 }
 
-inline int http_api_grpc(HttpRequest* req, HttpResponse* res) {
-    if (req->content_type != APPLICATION_GRPC) {
+inline int http_api_kv(HttpRequest*req, HttpResponse* res) {
+    if (req->content_type != APPLICATION_URLENCODED) {
         res->status_code = HTTP_STATUS_BAD_REQUEST;
         return 0;
     }
-    // parse protobuf: ParseFromString
-    // req->body;
-    // serailize protobuf: SerializeAsString
-    // res->body;
+    res->kv = req->kv;
     return 0;
 }
 
-inline int http_api_kv(HttpRequest*req, HttpResponse* res) {
-    if (req->content_type != APPLICATION_URLENCODED) {
+inline int http_api_grpc(HttpRequest* req, HttpResponse* res) {
+    if (req->content_type != APPLICATION_GRPC) {
         res->status_code = HTTP_STATUS_BAD_REQUEST;
         return 0;
     }
-    res->kv = req->kv;
+    // parse protobuf: ParseFromString
+    // req->body;
+    // serailize protobuf: SerializeAsString
+    // res->body;
     return 0;
 }
 
-inline int http_api_query(HttpRequest* req, HttpResponse* res) {
+inline int http_api_restful(HttpRequest*req, HttpResponse* res) {
+    // RESTful /:field/ => req->query_params
     res->kv = req->query_params;
     return 0;
 }
 
-inline int http_api_echo(HttpRequest* req, HttpResponse* res) {
-    res->content_type = req->content_type;
-    res->body = req->body;
-    return 0;
-}
-
 #endif // HTTP_API_TEST_H_
-

+ 8 - 1
html/downloads/scripts/getting_started.sh

@@ -12,4 +12,11 @@ bin/curl -v localhost:8080
 bin/curl -v localhost:8080/downloads/
 
 # http api service
-bin/curl -v -X POST localhost:8080/v1/api/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
+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/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
+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/mp   -F "file=@LICENSE"
+# RESTful API: /group/:group_name/user/:user_id
+bin/curl -v -X DELETE localhost:8080/v1/api/group/test/user/123

+ 12 - 5
http/HttpMessage.cpp

@@ -52,14 +52,21 @@ append:
 
 void HttpMessage::FillContentLength() {
     auto iter = headers.find("Content-Length");
-    if (iter == headers.end()) {
+    if (iter != headers.end()) {
+        content_length = atoi(iter->second.c_str());
+    }
+
+    if (iter == headers.end() || content_length == 0) {
         if (content_length == 0) {
+            content_length == body.size();
+        }
+        if (content_length == 0) {
+            DumpBody();
             content_length = body.size();
         }
-        headers["Content-Length"] = asprintf("%d", content_length);
-    }
-    else {
-        content_length = atoi(iter->second.c_str());
+        char sz[64];
+        snprintf(sz, sizeof(sz), "%d", content_length);
+        headers["Content-Length"] = sz;
     }
 }
 

+ 1 - 45
http/http_content.h

@@ -1,51 +1,7 @@
 #ifndef HTTP_CONTENT_H_
 #define HTTP_CONTENT_H_
 
-#include <string>
-#include <map>
-
-// MultiMap
-namespace std {
-/*
-int main() {
-    std::MultiMap<std::string, std::string> kvs;
-    kvs["name"] = "hw";
-    kvs["filename"] = "1.jpg";
-    kvs["filename"] = "2.jpg";
-    //kvs.insert(std::pair<std::string,std::string>("name", "hw"));
-    //kvs.insert(std::pair<std::string,std::string>("filename", "1.jpg"));
-    //kvs.insert(std::pair<std::string,std::string>("filename", "2.jpg"));
-    for (auto& pair : kvs) {
-        printf("%s:%s\n", pair.first.c_str(), pair.second.c_str());
-    }
-    auto iter = kvs.find("filename");
-    if (iter != kvs.end()) {
-        for (int i = 0; i < kvs.count("filename"); ++i, ++iter) {
-            printf("%s:%s\n", iter->first.c_str(), iter->second.c_str());
-        }
-    }
-    return 0;
-}
- */
-template<typename Key,typename Value>
-class MultiMap : public multimap<Key, Value> {
-public:
-    Value& operator[](Key key) {
-        auto iter = this->insert(std::pair<Key,Value>(key,Value()));
-        return (*iter).second;
-    }
-};
-}
-
-// MAP
-#ifdef USE_MULTIMAP
-#define MAP     std::MultiMap
-#else
-#define MAP     std::map
-#endif
-
-// KeyValue
-typedef MAP<std::string, std::string> KeyValue;
+#include "hstring.h"
 
 // QueryParams
 typedef KeyValue    QueryParams;

+ 8 - 5
http/httpdef.c

@@ -1,10 +1,13 @@
 #include "httpdef.h"
 
 #include <string.h>
-// strncmp(s1, s2, strlen(s2))
-static int mystrcmp(const char* s1, const char* s2) {
-    while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2) {++s1;++s2;}
-    return *s2 == 0 ? 0 : (*s1-*s2);
+//#include "hbase.h"
+static int strstartswith(const char* str, const char* start) {
+    while (*str && *start && *str == *start) {
+        ++str;
+        ++start;
+    }
+    return *start == '\0';
 }
 
 const char* http_status_str(enum http_status status) {
@@ -59,7 +62,7 @@ enum http_content_type http_content_type_enum(const char* str) {
         return CONTENT_TYPE_NONE;
     }
 #define XX(name, string, suffix) \
-    if (mystrcmp(str, #string) == 0) { \
+    if (strstartswith(str, #string)) { \
         return name; \
     }
     HTTP_CONTENT_TYPE_MAP(XX)

+ 20 - 12
http/server/HttpHandler.cpp

@@ -1,5 +1,6 @@
 #include "HttpHandler.h"
 
+#include "hbase.h"
 #include "hstring.h"
 #include "http_page.h"
 
@@ -12,8 +13,13 @@ int HttpHandler::HandleRequest() {
             return HANDLE_DONE;
         }
     }
+
     http_api_handler api = NULL;
-    int ret = service->GetApi(req.path.c_str(), req.method, &api);
+    int ret = 0;
+    if (service->api_handlers.size() != 0) {
+        ret = service->GetApi(&req, &api);
+    }
+
     if (api) {
         // api service
         if (api(&req, &res) == HANDLE_DONE) {
@@ -24,25 +30,27 @@ int HttpHandler::HandleRequest() {
         // Method Not Allowed
         res.status_code = HTTP_STATUS_METHOD_NOT_ALLOWED;
     }
-    else if (req.method == HTTP_GET) {
+    else if (service->document_root.size() != 0 && req.method == HTTP_GET) {
         // web service
-        // check path
-        if (*req.path.c_str() != '/' || strstr(req.path.c_str(), "/../")) {
+        // path safe check
+        const char* req_path = req.path.c_str();
+        if (*req_path != '/' || strstr(req_path, "/../")) {
             res.status_code = HTTP_STATUS_BAD_REQUEST;
             goto make_http_status_page;
         }
         std::string filepath = service->document_root;
-        filepath += req.path.c_str();
-        if (strcmp(req.path.c_str(), "/") == 0) {
+        filepath += req_path;
+        if (req_path[1] == '\0') {
             filepath += service->home_page;
         }
-        if (filepath.c_str()[filepath.size()-1] != '/' ||
-            (service->index_of.size() != 0 &&
-             req.path.size() >= service->index_of.size() &&
-             strnicmp(req.path.c_str(), service->index_of.c_str(), service->index_of.size()) == 0)) {
-            fc = files->Open(filepath.c_str(), (void*)req.path.c_str());
+        bool is_dir = filepath.c_str()[filepath.size()-1] == '/';
+        bool is_index_of = false;
+        if (service->index_of.size() != 0 && strstartswith(req_path, service->index_of.c_str())) {
+            is_index_of = true;
+        }
+        if (!is_dir || is_index_of) {
+            fc = files->Open(filepath.c_str(), (void*)req_path);
         }
-
         if (fc == NULL) {
             // Not Found
             res.status_code = HTTP_STATUS_NOT_FOUND;

+ 61 - 4
http/server/HttpService.cpp

@@ -25,13 +25,13 @@ void HttpService::AddApi(const char* path, http_method method, http_api_handler
 int HttpService::GetApi(const char* url, http_method method, http_api_handler* handler) {
     // {base_url}/path?query
     const char* s = url;
-    const char* c = base_url.c_str();
-    while (*s != '\0' && *c != '\0' && *s == *c) {++s;++c;}
-    if (*c != '\0') {
+    const char* b = base_url.c_str();
+    while (*s && *b && *s == *b) {++s;++b;}
+    if (*b != '\0') {
         return HTTP_STATUS_NOT_FOUND;
     }
     const char* e = s;
-    while (*e != '\0' && *e != '?') ++e;
+    while (*e && *e != '?') ++e;
 
     std::string path = std::string(s, e);
     auto iter = api_handlers.find(path);
@@ -50,3 +50,60 @@ int HttpService::GetApi(const char* url, http_method method, http_api_handler* h
     return HTTP_STATUS_METHOD_NOT_ALLOWED;
 }
 
+int HttpService::GetApi(HttpRequest* req, http_api_handler* handler) {
+    // {base_url}/path?query
+    const char* s = req->path.c_str();
+    const char* b = base_url.c_str();
+    while (*s && *b && *s == *b) {++s;++b;}
+    if (*b != '\0') {
+        return HTTP_STATUS_NOT_FOUND;
+    }
+    const char* e = s;
+    while (*e && *e != '?') ++e;
+
+    std::string path = std::string(s, e);
+    const char *kp, *ks, *vp, *vs;
+    for (auto iter = api_handlers.begin(); iter != api_handlers.end(); ++iter) {
+        kp = iter->first.c_str();
+        vp = path.c_str();
+
+        // RESTful API
+        std::map<std::string, std::string> params;
+        while (*kp && *vp && *kp == *vp) {
+            if (kp[0] == '/' && kp[1] == ':') {
+                kp += 2;
+                ks = kp;
+                while (*kp && *kp != '/') {++kp;}
+                vp += 1;
+                vs = vp;
+                while (*vp && *vp != '/') {++vp;}
+                params[std::string(ks, kp-ks)] = std::string(vs, vp-vs);
+            }
+            else {
+                ++kp;
+                ++vp;
+            }
+        }
+
+        if (*kp == '\0' && *vp == '\0') {
+            auto method_handlers = iter->second;
+            for (auto iter = method_handlers->begin(); iter != method_handlers->end(); ++iter) {
+                if (iter->method == req->method) {
+                    for (auto& param : params) {
+                        // RESTful /:field/ => req->query_params
+                        req->query_params[param.first] = param.second;
+                    }
+                    *handler = iter->handler;
+                    return 0;
+                }
+            }
+
+            if (params.size() == 0) {
+                *handler = NULL;
+                return HTTP_STATUS_METHOD_NOT_ALLOWED;
+            }
+        }
+    }
+    *handler = NULL;
+    return HTTP_STATUS_NOT_FOUND;
+}

+ 4 - 2
http/server/HttpService.h

@@ -25,7 +25,7 @@ struct http_method_handler {
         handler = h;
     }
 };
-// Provide Restful API
+// method => http_api_handler
 typedef std::list<http_method_handler> http_method_handlers;
 // path => http_method_handlers
 typedef std::map<std::string, std::shared_ptr<http_method_handlers>> http_api_handlers;
@@ -52,8 +52,10 @@ struct HttpService {
     }
 
     void AddApi(const char* path, http_method method, http_api_handler handler);
+    // @retval 0 OK, else HTTP_STATUS_NOT_FOUND, HTTP_STATUS_METHOD_NOT_ALLOWED
     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);
 };
 
 #endif // HTTP_SERVICE_H_
-

+ 53 - 0
unittest/hstring_test.cpp

@@ -0,0 +1,53 @@
+#include "hstring.h"
+
+int main(int argc, char** argv) {
+    char str1[] = "a1B2*C3d4==";
+    char str2[] = "a1B2*C3d4==";
+    printf("strupper %s\n", strupper(str1));
+    printf("strlower %s\n", strlower(str2));
+    char str3[] = "abcdefg";
+    printf("strreverse %s\n", strreverse(str3));
+
+    char str4[] = "123456789";
+    printf("strstartswith=%d\nstrendswith=%d\nstrcontains=%d\n",
+        (int)strstartswith(str4, "123"),
+        (int)strendswith(str4, "789"),
+        (int)strcontains(str4, "456"));
+
+    std::string str5 = asprintf("%s%d", "hello", 5);
+    printf("asprintf %s\n", str5.c_str());
+
+    std::string str6("123,456,789");
+    StringList strlist = split(str6, ',');
+    printf("split %s\n", str6.c_str());
+    for (auto& str : strlist) {
+        printf("%s\n", str.c_str());
+    }
+
+    std::string str7("user=admin&pswd=123456");
+    KeyValue kv = splitKV(str7, '&', '=');
+    for (auto& pair : kv) {
+        printf("%s=%s\n", pair.first.c_str(), pair.second.c_str());
+    }
+
+    std::string str8("<stdio.h>");
+    std::string str9 = trim_pairs(str8);
+    printf("trim_pairs %s\n", str9.c_str());
+
+    std::string str10("<title>{{title}}</title>");
+    std::string str11 = replace(str10, "{{title}}", "Home");
+    printf("replace %s\n", str11.c_str());
+
+    std::string filepath("/mnt/share/image/test.jpg");
+    std::string base = basename(filepath);
+    std::string dir = dirname(filepath);
+    std::string file = filename(filepath);
+    std::string suffix = suffixname(filepath);
+    printf("filepath %s\n", filepath.c_str());
+    printf("basename %s\n", base.c_str());
+    printf("dirname %s\n", dir.c_str());
+    printf("filename %s\n", file.c_str());
+    printf("suffixname %s\n", suffix.c_str());
+
+    return 0;
+}