Explorar el Código

add WITH_NGHTTP2

ithewei hace 6 años
padre
commit
a09ca22fc3

+ 3 - 0
Makefile.in

@@ -121,6 +121,9 @@ ifeq ($(OS), Windows)
 LIBS += wldap32 advapi32 crypt32
 endif
 endif
+ifneq ($(findstring WITH_NGHTTP2, $(DEFINES)), )
+override LIBS += nghttp2
+endif
 ifneq ($(findstring WITH_OPENSSL, $(DEFINES)), )
 override LIBS += ssl crypto
 endif

+ 4 - 0
base/hdef.h

@@ -156,6 +156,10 @@ typedef void (*procedure_t)(void* userdata);
 #define IS_HEX(c) (IS_NUM(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
 #endif
 
+#ifndef IS_GRAPH
+#define IS_GRAPH(c) ((c) >= 0x20 && (c) <= 0x7E)
+#endif
+
 #ifndef ARRAY_SIZE
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
 #endif

+ 35 - 25
base/hsocket.c

@@ -21,37 +21,63 @@ char *socket_strerror(int err) {
 #endif
 }
 
-int Bind(int port, int type) {
+int Resolver(const char* host, struct sockaddr* addr) {
+    // IPv4
+    struct sockaddr_in* addr4 = (struct sockaddr_in*)addr;
+    addr4->sin_family = AF_INET;
+    if (inet_pton(AF_INET, host, &addr4->sin_addr) == 1) {
+        return 0; // host is ip, so easy ;)
+    }
+    struct hostent* phe = gethostbyname(host);
+    if (phe == NULL) {
+        printd("unknown host %s\n", host);
+        return -h_errno;
+    }
+    memcpy(&addr4->sin_addr, phe->h_addr_list[0], phe->h_length);
+    return 0;
+}
+
+int Bind(int port, const char* host, int type) {
+    struct sockaddr_in localaddr;
+    socklen_t addrlen = sizeof(localaddr);
+    memset(&localaddr, 0, addrlen);
+    localaddr.sin_family = AF_INET;
+    if (host) {
+        int ret = Resolver(host, (struct sockaddr*)&localaddr);
+        if (ret != 0) return ret;
+    }
+    else {
+        localaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+    }
+    localaddr.sin_port = htons(port);
+
     // socket -> setsockopt -> bind
     int sockfd = socket(AF_INET, type, 0);
     if (sockfd < 0) {
         perror("socket");
         return -socket_errno();
     }
-    struct sockaddr_in localaddr;
-    socklen_t addrlen = sizeof(localaddr);
+
     // NOTE: SO_REUSEADDR means that you can reuse sockaddr of TIME_WAIT status
     int reuseaddr = 1;
     if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseaddr, sizeof(int)) < 0) {
         perror("setsockopt");
         goto error;
     }
-    memset(&localaddr, 0, addrlen);
-    localaddr.sin_family = AF_INET;
-    localaddr.sin_addr.s_addr = htonl(INADDR_ANY);
-    localaddr.sin_port = htons(port);
+
     if (bind(sockfd, (struct sockaddr*)&localaddr, addrlen) < 0) {
         perror("bind");
         goto error;
     }
+
     return sockfd;
 error:
     closesocket(sockfd);
     return socket_errno() > 0 ? -socket_errno() : -1;
 }
 
-int Listen(int port) {
-    int sockfd = Bind(port, SOCK_STREAM);
+int Listen(int port, const char* host) {
+    int sockfd = Bind(port, host, SOCK_STREAM);
     if (sockfd < 0) return sockfd;
     if (listen(sockfd, SOMAXCONN) < 0) {
         perror("listen");
@@ -63,22 +89,6 @@ error:
     return socket_errno() > 0 ? -socket_errno() : -1;
 }
 
-int Resolver(const char* host, struct sockaddr* addr) {
-    // IPv4
-    struct sockaddr_in* addr4 = (struct sockaddr_in*)addr;
-    addr4->sin_family = AF_INET;
-    if (inet_pton(AF_INET, host, &addr4->sin_addr) == 1) {
-        return 0; // host is ip, so easy ;)
-    }
-    struct hostent* phe = gethostbyname(host);
-    if (phe == NULL) {
-        printd("unknown host %s\n", host);
-        return -h_errno;
-    }
-    memcpy(&addr4->sin_addr, phe->h_addr_list[0], phe->h_length);
-    return 0;
-}
-
 int Connect(const char* host, int port, int nonblock) {
     // Resolver -> socket -> nonblocking -> connect
     struct sockaddr_in peeraddr;

+ 11 - 7
base/hsocket.h

@@ -9,6 +9,9 @@
 #pragma comment(lib, "ws2_32.lib")
 #endif
 
+#define LOCALHOST   "127.0.0.1"
+#define ANYADDR     "0.0.0.0"
+
 BEGIN_EXTERN_C
 
 static inline int socket_errno() {
@@ -20,18 +23,18 @@ static inline int socket_errno() {
 }
 char* socket_strerror(int err);
 
+// @param host: domain or ip
+// @retval 0:succeed
+int Resolver(const char* host, struct sockaddr* addr);
+
 // socket -> setsockopt -> bind
 // @param type: SOCK_STREAM(tcp) SOCK_DGRAM(udp)
 // @return sockfd
-int Bind(int port, int type DEFAULT(SOCK_STREAM));
+int Bind(int port, const char* host DEFAULT(ANYADDR), int type DEFAULT(SOCK_STREAM));
 
 // Bind -> listen
 // @return sockfd
-int Listen(int port);
-
-// @param host: domain or ip
-// @retval 0:succeed
-int Resolver(const char* host, struct sockaddr* addr);
+int Listen(int port, const char* host DEFAULT(ANYADDR));
 
 // @return sockfd
 // Resolver -> socket -> nonblocking -> connect
@@ -39,7 +42,8 @@ int Connect(const char* host, int port, int nonblock DEFAULT(0));
 // Connect(host, port, 1)
 int ConnectNonblock(const char* host, int port);
 // Connect(host, port, 1) -> select -> blocking
-int ConnectTimeout(const char* host, int port, int ms);
+#define DEFAULT_CONNECT_TIMEOUT 5000 // ms
+int ConnectTimeout(const char* host, int port, int ms DEFAULT(DEFAULT_CONNECT_TIMEOUT));
 
 // @param cnt: ping count
 // @return: ok count

+ 4 - 4
event/hloop.c

@@ -629,8 +629,8 @@ hio_t* hsendto (hloop_t* loop, int sockfd, const void* buf, size_t len, hwrite_c
     return hwrite(loop, sockfd, buf, len, write_cb);
 }
 
-hio_t* create_tcp_server (hloop_t* loop, int port, haccept_cb accept_cb) {
-    int listenfd = Listen(port);
+hio_t* create_tcp_server (hloop_t* loop, const char* host, int port, haccept_cb accept_cb) {
+    int listenfd = Listen(port, host);
     if (listenfd < 0) {
         return NULL;
     }
@@ -663,8 +663,8 @@ hio_t* create_tcp_client (hloop_t* loop, const char* host, int port, hconnect_cb
 
 
 // @server: socket -> bind -> hrecvfrom
-hio_t* create_udp_server(hloop_t* loop, int port) {
-    int bindfd = Bind(port, SOCK_DGRAM);
+hio_t* create_udp_server(hloop_t* loop, const char* host, int port) {
+    int bindfd = Bind(port, host, SOCK_DGRAM);
     if (bindfd < 0) {
         return NULL;
     }

+ 2 - 2
event/hloop.h

@@ -190,12 +190,12 @@ hio_t* hsendto   (hloop_t* loop, int sockfd, const void* buf, size_t len, hwrite
 
 //----------------- top-level apis---------------------------------------------
 // @tcp_server: socket -> bind -> listen -> haccept
-hio_t* create_tcp_server (hloop_t* loop, int port, haccept_cb accept_cb);
+hio_t* create_tcp_server (hloop_t* loop, const char* host, int port, haccept_cb accept_cb);
 // @tcp_client: resolver -> socket -> hio_get -> hio_set_peeraddr -> hconnect
 hio_t* create_tcp_client (hloop_t* loop, const char* host, int port, hconnect_cb connect_cb);
 
 // @udp_server: socket -> bind -> hio_get
-hio_t* create_udp_server (hloop_t* loop, int port);
+hio_t* create_udp_server (hloop_t* loop, const char* host, int port);
 // @udp_client: resolver -> socket -> hio_get -> hio_set_peeraddr
 hio_t* create_udp_client (hloop_t* loop, const char* host, int port);
 

+ 1 - 1
event/nlog.c

@@ -72,6 +72,6 @@ void network_logger(int loglevel, const char* buf, int len) {
 hio_t* nlog_listen(hloop_t* loop, int port) {
     list_init(&s_logger.clients);
     s_logger.loop = loop;
-    s_logger.listenio = create_tcp_server(loop, port, on_accept);
+    s_logger.listenio = create_tcp_server(loop, "0.0.0.0", port, on_accept);
     return s_logger.listenio;
 }

+ 16 - 10
examples/curl.cpp

@@ -9,6 +9,13 @@
 
 #include "http_client.h"
 
+static int  http_version = 1;
+static bool verbose = false;
+static const char* url = NULL;
+static const char* method = NULL;
+static const char* headers = NULL;
+static const char* data = NULL;
+
 static const char* options = "hVvX:H:d:";
 static const struct option long_options[] = {
     {"help",    no_argument,        NULL,   'h'},
@@ -17,6 +24,7 @@ static const struct option long_options[] = {
     {"method",  required_argument,  NULL,   'X'},
     {"header",  required_argument,  NULL,   'H'},
     {"data",    required_argument,  NULL,   'd'},
+    {"http2",   no_argument,        &http_version, 2},
     {NULL,      0,                  NULL,   0}
 };
 static const char* help = R"(Options:
@@ -26,6 +34,7 @@ static const char* help = R"(Options:
     -X|--method         Set http method.
     -H|--header         Add http headers, format -H "Content-Type:application/json Accept:*/*"
     -d|--data           Set http body.
+       --http2          Use http2
 Examples:
     curl -v localhost:8086
     curl -v localhost:8086/v1/api/query?page_no=1&page_size=10
@@ -46,11 +55,6 @@ void print_help() {
     print_version();
 }
 
-bool verbose = false;
-static const char* url = NULL;
-static const char* method = NULL;
-static const char* headers = NULL;
-static const char* data = NULL;
 int parse_cmdline(int argc, char* argv[]) {
     int opt;
     int opt_idx;
@@ -62,9 +66,7 @@ int parse_cmdline(int argc, char* argv[]) {
         case 'X': method = optarg; break;
         case 'H': headers = optarg; break;
         case 'd': data = optarg; break;
-        default:
-            print_usage();
-            exit(-1);
+        default: break;
         }
     }
 
@@ -88,6 +90,10 @@ int main(int argc, char* argv[]) {
 
     int ret = 0;
     HttpRequest req;
+    if (http_version == 2) {
+        req.http_major = 2;
+        req.http_minor = 0;
+    }
     req.url = url;
     if (method) {
         req.method = http_method_enum(method);
@@ -134,14 +140,14 @@ int main(int argc, char* argv[]) {
     HttpResponse res;
     ret = http_client_send(&req, &res, 0);
     if (verbose) {
-        printf("%s\n", req.dump(true,true).c_str());
+        printf("%s\n", req.Dump(true,true).c_str());
     }
     if (ret != 0) {
         printf("* Failed:%s:%d\n", http_client_strerror(ret), ret);
     }
     else {
         if (verbose) {
-            printf("%s\n", res.dump(true,true).c_str());
+            printf("%s\n", res.Dump(true,true).c_str());
         }
         else {
             printf("%s\n", res.body.c_str());

+ 5 - 6
examples/http_api_test.h

@@ -1,7 +1,7 @@
 #ifndef HTTP_API_TEST_H_
 #define HTTP_API_TEST_H_
 
-#include "HttpRequest.h"
+#include "HttpServer.h"
 
 // XXX(path, method, handler)
 #define HTTP_API_MAP(XXX) \
@@ -13,15 +13,14 @@
 
 
 inline int http_api_preprocessor(HttpRequest* req, HttpResponse* res) {
-    //printf("%s\n", req->dump(true, true).c_str());
-    req->parse_url();
-    req->parse_body();
+    //printf("%s\n", req->Dump(true, true).c_str());
+    req->ParseBody();
     return 0;
 }
 
 inline int http_api_postprocessor(HttpRequest* req, HttpResponse* res) {
-    res->dump_body();
-    //printf("%s\n", res->dump(true, true).c_str());
+    res->DumpBody();
+    //printf("%s\n", res->Dump(true, true).c_str());
     return 0;
 }
 

+ 1 - 1
examples/httpd.cpp

@@ -2,7 +2,7 @@
 #include "hmain.h"
 #include "iniparser.h"
 
-#include "http_server.h"
+#include "HttpServer.h"
 #include "http_api_test.h"
 #include "ssl_ctx.h"
 

+ 1 - 1
examples/tcp.c

@@ -43,7 +43,7 @@ int main(int argc, char** argv) {
     int port = atoi(argv[1]);
 
     hloop_t* loop = hloop_new(0);
-    hio_t* listenio = create_tcp_server(loop, port, on_accept);
+    hio_t* listenio = create_tcp_server(loop, "0.0.0.0", port, on_accept);
     if (listenio == NULL) {
         return -20;
     }

+ 1 - 1
examples/udp.c

@@ -29,7 +29,7 @@ int main(int argc, char** argv) {
     int port = atoi(argv[1]);
 
     hloop_t* loop = hloop_new(0);
-    hio_t* io = create_udp_server(loop, port);
+    hio_t* io = create_udp_server(loop, "0.0.0.0", port);
     if (io == NULL) {
         return -20;
     }

+ 0 - 79
http/HttpParser.cpp

@@ -1,79 +0,0 @@
-#include "HttpParser.h"
-
-int HttpParser::on_url(http_parser* parser, const char *at, size_t length) {
-    //printf("on_url:%.*s\n", (int)length, at);
-    http_parser_userdata* userdata = (http_parser_userdata*)parser->data;
-    userdata->state = HP_URL;
-    userdata->url.insert(userdata->url.size(), at, length);
-    return 0;
-}
-
-int HttpParser::on_status(http_parser* parser, const char *at, size_t length) {
-    //printf("on_status:%.*s\n", (int)length, at);
-    http_parser_userdata* userdata = (http_parser_userdata*)parser->data;
-    userdata->state = HP_STATUS;
-    return 0;
-}
-
-int HttpParser::on_header_field(http_parser* parser, const char *at, size_t length) {
-    //printf("on_header_field:%.*s\n", (int)length, at);
-    http_parser_userdata* userdata = (http_parser_userdata*)parser->data;
-    userdata->handle_header();
-    userdata->state = HP_HEADER_FIELD;
-    userdata->header_field.insert(userdata->header_field.size(), at, length);
-    return 0;
-}
-
-int HttpParser::on_header_value(http_parser* parser, const char *at, size_t length) {
-    //printf("on_header_value:%.*s""\n", (int)length, at);
-    http_parser_userdata* userdata = (http_parser_userdata*)parser->data;
-    userdata->state = HP_HEADER_VALUE;
-    userdata->header_value.insert(userdata->header_value.size(), at, length);
-    return 0;
-}
-
-int HttpParser::on_body(http_parser* parser, const char *at, size_t length) {
-    //printf("on_body:%.*s""\n", (int)length, at);
-    http_parser_userdata* userdata = (http_parser_userdata*)parser->data;
-    userdata->state = HP_BODY;
-    userdata->payload->body.insert(userdata->payload->body.size(), at, length);
-    return 0;
-}
-
-int HttpParser::on_message_begin(http_parser* parser) {
-    //printf("on_message_begin\n");
-    http_parser_userdata* userdata = (http_parser_userdata*)parser->data;
-    userdata->state = HP_MESSAGE_BEGIN;
-    return 0;
-}
-
-int HttpParser::on_headers_complete(http_parser* parser) {
-    //printf("on_headers_complete\n");
-    http_parser_userdata* userdata = (http_parser_userdata*)parser->data;
-    userdata->handle_header();
-    auto iter = userdata->payload->headers.find("content-type");
-    if (iter != userdata->payload->headers.end()) {
-        userdata->payload->content_type = http_content_type_enum(iter->second.c_str());
-    }
-    userdata->payload->http_major = parser->http_major;
-    userdata->payload->http_minor = parser->http_minor;
-    if (userdata->type == HTTP_REQUEST) {
-        HttpRequest* req = (HttpRequest*)userdata->payload;
-        req->method = (http_method)parser->method;
-        req->url = userdata->url;
-    }
-    else if (userdata->type == HTTP_RESPONSE) {
-        HttpResponse* res = (HttpResponse*)userdata->payload;
-        res->status_code = (http_status)parser->status_code;
-    }
-    userdata->state = HP_HEADERS_COMPLETE;
-    return 0;
-}
-
-int HttpParser::on_message_complete(http_parser* parser) {
-    //printf("on_message_complete\n");
-    http_parser_userdata* userdata = (http_parser_userdata*)parser->data;
-    userdata->state = HP_MESSAGE_COMPLETE;
-    return 0;
-}
-

+ 0 - 101
http/HttpParser.h

@@ -1,101 +0,0 @@
-#ifndef HW_HTTP_PARSER_H_
-#define HW_HTTP_PARSER_H_
-
-#include "http_parser.h"
-#include "HttpRequest.h"
-#include "hstring.h"
-
-enum http_parser_state {
-    HP_START_REQ_OR_RES,
-    HP_MESSAGE_BEGIN,
-    HP_URL,
-    HP_STATUS,
-    HP_HEADER_FIELD,
-    HP_HEADER_VALUE,
-    HP_HEADERS_COMPLETE,
-    HP_BODY,
-    HP_MESSAGE_COMPLETE
-};
-
-struct http_parser_userdata {
-    http_parser_type    type;
-    http_parser_state   state;
-    HttpInfo*           payload;
-    // tmp
-    std::string url;
-    std::string header_field;
-    std::string header_value;
-
-    void handle_header() {
-        if (header_field.size() != 0 && header_value.size() != 0) {
-            payload->headers[header_field] = header_value;
-            header_field.clear();
-            header_value.clear();
-        }
-    }
-
-    void init() {
-        state = HP_START_REQ_OR_RES;
-        url.clear();
-        header_field.clear();
-        header_value.clear();
-    }
-};
-
-class HttpParser {
-    http_parser_settings            hp_settings;
-    http_parser                     hp_parser;
-    http_parser_userdata            hp_userdata;
-
-public:
-    HttpParser() {
-        http_parser_settings_init(&hp_settings);
-        hp_settings.on_message_begin    = HttpParser::on_message_begin;
-        hp_settings.on_url              = HttpParser::on_url;
-        hp_settings.on_status           = HttpParser::on_status;
-        hp_settings.on_header_field     = HttpParser::on_header_field;
-        hp_settings.on_header_value     = HttpParser::on_header_value;
-        hp_settings.on_headers_complete = HttpParser::on_headers_complete;
-        hp_settings.on_body             = HttpParser::on_body;
-        hp_settings.on_message_complete = HttpParser::on_message_complete;
-        hp_parser.data = &hp_userdata;
-    }
-
-    void parser_request_init(HttpRequest* req) {
-        hp_userdata.init();
-        hp_userdata.type = HTTP_REQUEST;
-        hp_userdata.payload = req;
-        http_parser_init(&hp_parser, HTTP_REQUEST);
-    }
-
-    void parser_response_init(HttpResponse* res) {
-        hp_userdata.init();
-        hp_userdata.type = HTTP_RESPONSE;
-        hp_userdata.payload = res;
-        http_parser_init(&hp_parser, HTTP_RESPONSE);
-    }
-
-    int execute(const char* data, size_t len) {
-        return http_parser_execute(&hp_parser, &hp_settings, data, len);
-    }
-
-    http_errno get_errno() {
-        return (http_errno)hp_parser.http_errno;
-    }
-
-    http_parser_state get_state() {
-        return hp_userdata.state;
-    }
-
-protected:
-    static int on_url(http_parser* parser, const char *at, size_t length);
-    static int on_status(http_parser* parser, const char *at, size_t length);
-    static int on_header_field(http_parser* parser, const char *at, size_t length);
-    static int on_header_value(http_parser* parser, const char *at, size_t length);
-    static int on_body(http_parser* parser, const char *at, size_t length);
-    static int on_message_begin(http_parser* parser);
-    static int on_headers_complete(http_parser* parser);
-    static int on_message_complete(http_parser* parser);
-};
-
-#endif // HW_HTTP_PARSER_H_

+ 276 - 0
http/HttpPayload.cpp

@@ -0,0 +1,276 @@
+#include "HttpPayload.h"
+
+#include <string.h>
+
+#include "http_parser.h" // for http_parser_url
+
+void HttpPayload::FillContentType() {
+    auto iter = headers.find("Content-Type");
+    if (iter != headers.end()) {
+        content_type = http_content_type_enum(iter->second.c_str());
+        goto append;
+    }
+
+    if (content_type == CONTENT_TYPE_NONE) {
+        if (json.size() != 0) {
+            content_type = APPLICATION_JSON;
+        }
+        else if (mp.size() != 0) {
+            content_type = MULTIPART_FORM_DATA;
+        }
+        else if (kv.size() != 0) {
+            content_type = X_WWW_FORM_URLENCODED;
+        }
+        else if (body.size() != 0) {
+            content_type = TEXT_PLAIN;
+        }
+    }
+
+    if (content_type != CONTENT_TYPE_NONE) {
+        headers["Content-Type"] = http_content_type_str(content_type);
+    }
+append:
+    if (content_type == MULTIPART_FORM_DATA) {
+        auto iter = headers.find("Content-Type");
+        if (iter != headers.end()) {
+            const char* boundary = strstr(iter->second.c_str(), "boundary=");
+            if (boundary == NULL) {
+                boundary = DEFAULT_MULTIPART_BOUNDARY;
+                iter->second += "; boundary=";
+                iter->second += boundary;
+            }
+        }
+    }
+}
+
+void HttpPayload::FillContentLength() {
+    auto iter = headers.find("Content-Length");
+    if (iter == headers.end()) {
+        if (content_length == 0) {
+            content_length = body.size();
+        }
+        headers["Content-Length"] = std::to_string(content_length);
+    }
+    else {
+        content_length = atoi(iter->second.c_str());
+    }
+}
+
+void HttpPayload::DumpHeaders(std::string& str) {
+    FillContentType();
+    FillContentLength();
+    for (auto& header: headers) {
+        // http2 :method :path :scheme :authority :status
+        if (*str.c_str() != ':') {
+            // %s: %s\r\n
+            str += header.first;
+            str += ": ";
+            str += header.second;
+            str += "\r\n";
+        }
+    }
+}
+
+void HttpPayload::DumpBody() {
+    if (body.size() != 0) {
+        return;
+    }
+    FillContentType();
+    switch(content_type) {
+    case APPLICATION_JSON:
+        body = dump_json(json);
+        break;
+    case MULTIPART_FORM_DATA:
+    {
+        auto iter = headers.find("Content-Type");
+        if (iter == headers.end()) {
+            return;
+        }
+        const char* boundary = strstr(iter->second.c_str(), "boundary=");
+        if (boundary == NULL) {
+            return;
+        }
+        boundary += strlen("boundary=");
+        body = dump_multipart(mp, boundary);
+    }
+        break;
+    case X_WWW_FORM_URLENCODED:
+        body = dump_query_params(kv);
+        break;
+    default:
+        // nothing to do
+        break;
+    }
+}
+
+int HttpPayload::ParseBody() {
+    if (body.size() == 0) {
+        return -1;
+    }
+    FillContentType();
+    switch(content_type) {
+    case APPLICATION_JSON:
+        return parse_json(body.c_str(), json);
+    case MULTIPART_FORM_DATA:
+    {
+        auto iter = headers.find("Content-Type");
+        if (iter == headers.end()) {
+            return false;
+        }
+        const char* boundary = strstr(iter->second.c_str(), "boundary=");
+        if (boundary == NULL) {
+            return false;
+        }
+        boundary += strlen("boundary=");
+        return parse_multipart(body, mp, boundary);
+    }
+    case X_WWW_FORM_URLENCODED:
+        return parse_query_params(body.c_str(), kv);
+    default:
+        // nothing to do
+        return 0;
+    }
+}
+
+std::string HttpPayload::Dump(bool is_dump_headers, bool is_dump_body) {
+    std::string str;
+    if (is_dump_headers) {
+        DumpHeaders(str);
+    }
+    str += "\r\n";
+    if (is_dump_body) {
+        DumpBody();
+        if (ContentLength() != 0) {
+            str.insert(str.size(), (const char*)Content(), ContentLength());
+        }
+    }
+    return str;
+}
+
+void HttpRequest::DumpUrl() {
+    if (url.size() != 0 && strncmp(url.c_str(), "http", 4) == 0) {
+        // have been complete url
+        return;
+    }
+    std::string str;
+    // scheme://
+    str += "http";
+    if (https) str += 's';
+    str += "://";
+    // host:port
+    char c_str[256] = {0};
+    if (url.size() != 0 && *url.c_str() != '/') {
+        // url begin with host
+        str += url;
+    }
+    else {
+        if (port == 0 ||
+            port == DEFAULT_HTTP_PORT ||
+            port == DEFAULT_HTTPS_PORT) {
+            str += host;
+        }
+        else {
+            snprintf(c_str, sizeof(c_str), "%s:%d", host.c_str(), port);
+            str += c_str;
+        }
+    }
+    // /path
+    if (url.size() != 0 && *url.c_str() == '/') {
+        // url begin with path
+        str += url;
+    }
+    else if (path.size() != 0 && *path.c_str() == '/') {
+        str += path;
+    }
+    else {
+        str += '/';
+    }
+    // ?query
+    if (strchr(str.c_str(), '?') == NULL &&
+        query_params.size() != 0) {
+        str += '?';
+        str += dump_query_params(query_params);
+    }
+    url = str;
+}
+
+void HttpRequest::ParseUrl() {
+    DumpUrl();
+    http_parser_url parser;
+    http_parser_url_init(&parser);
+    http_parser_parse_url(url.c_str(), url.size(), 0, &parser);
+    // scheme
+    https = !strncmp(url.c_str(), "https", 5);
+    // host
+    if (parser.field_set & (1<<UF_HOST)) {
+        host = url.substr(parser.field_data[UF_HOST].off, parser.field_data[UF_HOST].len);
+    }
+    // port
+    port = parser.port ? parser.port : https ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
+    // path
+    if (parser.field_set & (1<<UF_PATH)) {
+        path = url.c_str() + parser.field_data[UF_PATH].off;
+    }
+    // query
+    if (parser.field_set & (1<<UF_QUERY)) {
+        parse_query_params(url.c_str()+parser.field_data[UF_QUERY].off, query_params);
+    }
+}
+
+std::string HttpRequest::Dump(bool is_dump_headers, bool is_dump_body) {
+    ParseUrl();
+
+    char c_str[256] = {0};
+    std::string str;
+    // GET / HTTP/1.1\r\n
+    snprintf(c_str, sizeof(c_str), "%s %s HTTP/%d.%d\r\n", http_method_str(method), path.c_str(), http_major, http_minor);
+    str += c_str;
+    if (is_dump_headers) {
+        // Host:
+        if (headers.find("Host") == headers.end()) {
+            if (port == 0 ||
+                port == DEFAULT_HTTP_PORT ||
+                port == DEFAULT_HTTPS_PORT) {
+                headers["Host"] = host;
+            }
+            else {
+                snprintf(c_str, sizeof(c_str), "%s:%d", host.c_str(), port);
+                headers["Host"] = c_str;
+            }
+        }
+        DumpHeaders(str);
+    }
+    str += "\r\n";
+    if (is_dump_body) {
+        DumpBody();
+        if (ContentLength() != 0) {
+            str.insert(str.size(), (const char*)Content(), ContentLength());
+        }
+    }
+    return str;
+}
+
+#include <time.h>
+std::string HttpResponse::Dump(bool is_dump_headers, bool is_dump_body) {
+    char c_str[256] = {0};
+    std::string str;
+    // HTTP/1.1 200 OK\r\n
+    snprintf(c_str, sizeof(c_str), "HTTP/%d.%d %d %s\r\n", http_major, http_minor, status_code, http_status_str(status_code));
+    str += c_str;
+    if (is_dump_headers) {
+        // Date:
+        time_t tt;
+        time(&tt);
+        strftime(c_str, sizeof(c_str), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tt));
+        headers["Date"] = c_str;
+        DumpHeaders(str);
+    }
+    str += "\r\n";
+    if (is_dump_body) {
+        DumpBody();
+        if (ContentLength() != 0) {
+            str.insert(str.size(), (const char*)Content(), ContentLength());
+        }
+    }
+    return str;
+}

+ 156 - 0
http/HttpPayload.h

@@ -0,0 +1,156 @@
+#ifndef HTTP_PAYLOAD_H_
+#define HTTP_PAYLOAD_H_
+
+#include <string>
+#include <map>
+
+#include "httpdef.h"
+#include "http_content.h"
+#include "hstring.h"
+
+typedef std::map<std::string, std::string, StringCaseLess>  http_headers;
+typedef std::string                                         http_body;
+
+class HttpPayload {
+public:
+    int                 type;
+    unsigned short      http_major;
+    unsigned short      http_minor;
+
+    http_headers        headers;
+    http_body           body;
+
+    // structured content
+    void*               content;    // DATA_NO_COPY
+    int                 content_length;
+    http_content_type   content_type;
+    Json                json;       // APPLICATION_JSON
+    MultiPart           mp;         // FORM_DATA
+    KeyValue            kv;         // X_WWW_FORM_URLENCODED
+
+    HttpPayload() {
+        Init();
+    }
+
+    virtual ~HttpPayload() {}
+
+    void Init() {
+        type = HTTP_BOTH;
+        http_major = 1;
+        http_minor = 1;
+        content = NULL;
+        content_length = 0;
+        content_type = CONTENT_TYPE_NONE;
+    }
+
+    virtual void Reset() {
+        Init();
+        headers.clear();
+        body.clear();
+        json.clear();
+        mp.clear();
+        kv.clear();
+    }
+
+    // structured-content -> content_type <-> headers Content-Type
+    void FillContentType();
+    // body.size -> content_length <-> headers Content-Length
+    void FillContentLength();
+
+    // headers -> string
+    void DumpHeaders(std::string& str);
+    // structured content -> body
+    void DumpBody();
+    // body -> structured content
+    // @retval 0:succeed
+    int  ParseBody();
+
+    virtual std::string Dump(bool is_dump_headers, bool is_dump_body);
+
+    void* Content() {
+        if (content == NULL && body.size() != 0) {
+            content = (void*)body.data();
+        }
+        return content;
+    }
+
+    int ContentLength() {
+        if (content_length == 0) {
+            FillContentLength();
+        }
+        return content_length;
+    }
+
+    http_content_type ContentType() {
+        if (content_type == CONTENT_TYPE_NONE) {
+            FillContentType();
+        }
+        return content_type;
+    }
+};
+
+#define DEFAULT_USER_AGENT "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+class HttpRequest : public HttpPayload {
+public:
+    http_method         method;
+    // scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
+    std::string         url;
+    // structured url
+    bool                https;
+    std::string         host;
+    int                 port;
+    std::string         path;
+    QueryParams         query_params;
+
+    HttpRequest() : HttpPayload() {
+        type = HTTP_REQUEST;
+        Init();
+    }
+
+    void Init() {
+        headers["User-Agent"] = DEFAULT_USER_AGENT;
+        headers["Accept"] = "*/*";
+        method = HTTP_GET;
+        https = 0;
+        host = "127.0.0.1";
+        port = DEFAULT_HTTP_PORT;
+        path = "/";
+    }
+
+    virtual void Reset() {
+        HttpPayload::Reset();
+        Init();
+        url.clear();
+        query_params.clear();
+    }
+
+    virtual std::string Dump(bool is_dump_headers, bool is_dump_body);
+
+    // structed url -> url
+    void DumpUrl();
+    // url -> structed url
+    void ParseUrl();
+};
+
+class HttpResponse : public HttpPayload {
+public:
+    http_status         status_code;
+
+    HttpResponse() : HttpPayload() {
+        type = HTTP_RESPONSE;
+        Init();
+    }
+
+    void Init() {
+        status_code = HTTP_STATUS_OK;
+    }
+
+    virtual void Reset() {
+        HttpPayload::Reset();
+        Init();
+    }
+
+    virtual std::string Dump(bool is_dump_headers = true, bool is_dump_body = false);
+};
+
+#endif // HTTP_PAYLOAD_H_

+ 0 - 385
http/HttpRequest.h

@@ -1,385 +0,0 @@
-#ifndef HTTP_REQUEST_H_
-#define HTTP_REQUEST_H_
-
-#include <time.h>
-#include <string.h>
-
-#include <string>
-#include <map>
-
-// for http_method, http_status
-#include "http_parser.h"
-inline http_method http_method_enum(const char* str) {
-#define XX(num, name, string) \
-    if (strcmp(str, #string) == 0) { \
-        return HTTP_##name; \
-    }
-    HTTP_METHOD_MAP(XX)
-#undef XX
-    return HTTP_GET;
-}
-
-// http_content_type
-// XX(name, string, suffix)
-#define HTTP_CONTENT_TYPE_MAP(XX) \
-    XX(TEXT_PLAIN,              "text/plain",               "txt")   \
-    XX(TEXT_HTML,               "text/html",                "html")    \
-    XX(TEXT_CSS,                "text/css",                 "css")     \
-    XX(APPLICATION_JAVASCRIPT,  "application/javascript",   "js")   \
-    XX(APPLICATION_XML,         "application/xml",          "xml")  \
-    XX(APPLICATION_JSON,        "application/json",         "json") \
-    XX(X_WWW_FORM_URLENCODED,   "application/x-www-form-urlencoded", ".null.") \
-    XX(MULTIPART_FORM_DATA,     "multipart/form-data",               ".null.") \
-    XX(IMAGE_JPEG,              "image/jpeg",               "jpg") \
-    XX(IMAGE_PNG,               "image/png",                "png") \
-    XX(IMAGE_GIF,               "image/gif",                "gif")
-
-enum http_content_type {
-#define XX(name, string, suffix)   name,
-    CONTENT_TYPE_NONE,
-    HTTP_CONTENT_TYPE_MAP(XX)
-    CONTENT_TYPE_UNDEFINED
-#undef XX
-};
-
-inline const char* http_content_type_str(enum http_content_type type) {
-    switch (type) {
-#define XX(name, string, suffix) \
-    case name:  return string;
-    HTTP_CONTENT_TYPE_MAP(XX)
-    default:    return "";
-#undef XX
-    }
-}
-// replace strncmp(s1, s2, strlen(s2))
-inline int mystrcmp(const char* s1, const char* s2) {
-    while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2) {++s1;++s2;}
-    return *s2 == 0 ? 0 : (*s1-*s2);
-}
-inline enum http_content_type http_content_type_enum(const char* str) {
-#define XX(name, string, suffix) \
-    if (mystrcmp(str, string) == 0) { \
-        return name; \
-    }
-    HTTP_CONTENT_TYPE_MAP(XX)
-#undef XX
-    return CONTENT_TYPE_UNDEFINED;
-}
-
-inline enum http_content_type http_content_type_enum_by_suffix(const char* suf) {
-#define XX(name, string, suffix) \
-    if (strcmp(suf, suffix) == 0) { \
-        return name; \
-    }
-    HTTP_CONTENT_TYPE_MAP(XX)
-#undef XX
-    return CONTENT_TYPE_UNDEFINED;
-}
-
-inline const char* http_content_type_str_by_suffix(const char* suf) {
-#define XX(name, string, suffix) \
-    if (strcmp(suf, suffix) == 0) { \
-        return string; \
-    }
-    HTTP_CONTENT_TYPE_MAP(XX)
-#undef XX
-    return "";
-}
-
-#include "http_content.h"
-#include "hstring.h"
-typedef std::map<std::string, std::string, StringCaseLess>  http_headers;
-typedef std::string     http_body;
-class HttpInfo {
-public:
-    unsigned short      http_major;
-    unsigned short      http_minor;
-    http_headers        headers;
-    http_body           body;
-    // parsed content
-    http_content_type   content_type;
-    Json                json;       // APPLICATION_JSON
-    MultiPart           mp;         // FORM_DATA
-    KeyValue            kv;         // X_WWW_FORM_URLENCODED
-
-    HttpInfo() {
-        init();
-    }
-
-    void init() {
-        http_major = 1;
-        http_minor = 1;
-        content_type = CONTENT_TYPE_NONE;
-    }
-
-    void reset() {
-        init();
-        headers.clear();
-        body.clear();
-        json.clear();
-        mp.clear();
-        kv.clear();
-    }
-
-    void fill_content_type() {
-        auto iter = headers.find("Content-Type");
-        if (iter != headers.end()) {
-            content_type = http_content_type_enum(iter->second.c_str());
-            goto append;
-        }
-
-        if (content_type == CONTENT_TYPE_NONE) {
-            if (json.size() != 0) {
-                content_type = APPLICATION_JSON;
-            }
-            else if (mp.size() != 0) {
-                content_type = MULTIPART_FORM_DATA;
-            }
-            else if (kv.size() != 0) {
-                content_type = X_WWW_FORM_URLENCODED;
-            }
-            else if (body.size() != 0) {
-                content_type = TEXT_PLAIN;
-            }
-        }
-
-        if (content_type != CONTENT_TYPE_NONE) {
-            headers["Content-Type"] = http_content_type_str(content_type);
-        }
-append:
-        if (content_type == MULTIPART_FORM_DATA) {
-            auto iter = headers.find("Content-Type");
-            if (iter != headers.end()) {
-                const char* boundary = strstr(iter->second.c_str(), "boundary=");
-                if (boundary == NULL) {
-                    boundary = DEFAULT_MULTIPART_BOUNDARY;
-                    iter->second += "; boundary=";
-                    iter->second += boundary;
-                }
-            }
-        }
-    }
-
-    void fill_content_length() {
-        auto iter = headers.find("Content-Length");
-        if (iter == headers.end()) {
-            headers["Content-Length"] = std::to_string(body.size());
-        }
-    }
-
-    void dump_headers(std::string& str) {
-        fill_content_type();
-        fill_content_length();
-        for (auto& header: headers) {
-            // %s: %s\r\n
-            str += header.first;
-            str += ": ";
-            str += header.second;
-            str += "\r\n";
-        }
-    }
-
-    void dump_body() {
-        if (body.size() != 0) {
-            return;
-        }
-        fill_content_type();
-        switch(content_type) {
-        case APPLICATION_JSON:
-            body = dump_json(json);
-            break;
-        case MULTIPART_FORM_DATA:
-        {
-            auto iter = headers.find("Content-Type");
-            if (iter == headers.end()) {
-                return;
-            }
-            const char* boundary = strstr(iter->second.c_str(), "boundary=");
-            if (boundary == NULL) {
-                return;
-            }
-            boundary += strlen("boundary=");
-            body = dump_multipart(mp, boundary);
-        }
-            break;
-        case X_WWW_FORM_URLENCODED:
-            body = dump_query_params(kv);
-            break;
-        default:
-            // nothing to do
-            break;
-        }
-    }
-
-    bool parse_body() {
-        if (body.size() == 0) {
-            return false;
-        }
-        fill_content_type();
-        switch(content_type) {
-        case APPLICATION_JSON:
-            parse_json(body.c_str(), json);
-            break;
-        case MULTIPART_FORM_DATA:
-        {
-            auto iter = headers.find("Content-Type");
-            if (iter == headers.end()) {
-                return false;
-            }
-            const char* boundary = strstr(iter->second.c_str(), "boundary=");
-            if (boundary == NULL) {
-                return false;
-            }
-            boundary += strlen("boundary=");
-            parse_multipart(body, mp, boundary);
-        }
-            break;
-        case X_WWW_FORM_URLENCODED:
-            parse_query_params(body.c_str(), kv);
-            break;
-        default:
-            // nothing to do
-            break;
-        }
-        return true;
-    }
-};
-
-#define DEFAULT_USER_AGENT "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
-class HttpRequest : public HttpInfo {
-public:
-    http_method         method;
-    // scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
-    std::string         url;
-    QueryParams         query_params;
-
-    HttpRequest() : HttpInfo() {
-        init();
-    }
-
-    void init() {
-        method = HTTP_GET;
-        headers["User-Agent"] = DEFAULT_USER_AGENT;
-        headers["Accept"] = "*/*";
-    }
-
-    void reset() {
-        HttpInfo::init();
-        init();
-        url.clear();
-        query_params.clear();
-    }
-
-    std::string dump_url() {
-        std::string str;
-        if (strstr(url.c_str(), "://") == NULL) {
-            str += "http://";
-        }
-        if (*url.c_str() == '/') {
-            str += headers["Host"];
-        }
-        str += url;
-        if (strchr(url.c_str(), '?') || query_params.size() == 0) {
-            return str;
-        }
-        str += '?';
-        str += dump_query_params(query_params);
-        return str;
-    }
-
-    void parse_url() {
-        if (query_params.size() != 0) {
-            return;
-        }
-        const char* token = strchr(url.c_str(), '?');
-        if (token == NULL) {
-            return;
-        }
-        parse_query_params(token+1, query_params);
-    }
-
-    std::string dump(bool is_dump_headers = true, bool is_dump_body = false) {
-        char c_str[256] = {0};
-        const char* path = "/";
-        if (*url.c_str() == '/') {
-            path = url.c_str();
-        }
-        else {
-            std::string url = dump_url();
-            http_parser_url parser;
-            http_parser_url_init(&parser);
-            http_parser_parse_url(url.c_str(), url.size(), 0, &parser);
-            if (parser.field_set & (1<<UF_HOST)) {
-                std::string host = url.substr(parser.field_data[UF_HOST].off, parser.field_data[UF_HOST].len);
-                int port = parser.port;
-                if (port == 0) {
-                    headers["Host"] = host;
-                }
-                else {
-                    snprintf(c_str, sizeof(c_str), "%s:%d", host.c_str(), port);
-                    headers["Host"] = c_str;
-                }
-            }
-            if (parser.field_set & (1<<UF_PATH)) {
-                path = url.c_str() + parser.field_data[UF_PATH].off;
-            }
-        }
-
-        std::string str;
-        // GET / HTTP/1.1\r\n
-        snprintf(c_str, sizeof(c_str), "%s %s HTTP/%d.%d\r\n", http_method_str(method), path, http_major, http_minor);
-        str += c_str;
-        if (is_dump_headers) {
-            dump_headers(str);
-        }
-        str += "\r\n";
-        if (is_dump_body) {
-            dump_body();
-            str += body;
-        }
-        return str;
-    }
-};
-
-class HttpResponse : public HttpInfo {
-public:
-    http_status         status_code;
-
-    HttpResponse() : HttpInfo() {
-        init();
-    }
-
-    void init() {
-        status_code = HTTP_STATUS_OK;
-    }
-
-    void reset() {
-        HttpInfo::init();
-        init();
-    }
-
-    std::string dump(bool is_dump_headers = true, bool is_dump_body = false) {
-        char c_str[256] = {0};
-        std::string str;
-        // HTTP/1.1 200 OK\r\n
-        snprintf(c_str, sizeof(c_str), "HTTP/%d.%d %d %s\r\n", http_major, http_minor, status_code, http_status_str(status_code));
-        str += c_str;
-        if (is_dump_headers) {
-            // Date:
-            time_t tt;
-            time(&tt);
-            strftime(c_str, sizeof(c_str), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tt));
-            headers["Date"] = c_str;
-            dump_headers(str);
-        }
-        str += "\r\n";
-        if (is_dump_body) {
-            dump_body();
-            str += body;
-        }
-        return str;
-    }
-};
-
-#endif // HTTP_REQUEST_H_
-

+ 543 - 0
http/HttpSession.cpp

@@ -0,0 +1,543 @@
+#include "HttpSession.h"
+
+#include "http_parser.h"
+static int on_url(http_parser* parser, const char *at, size_t length);
+static int on_status(http_parser* parser, const char *at, size_t length);
+static int on_header_field(http_parser* parser, const char *at, size_t length);
+static int on_header_value(http_parser* parser, const char *at, size_t length);
+static int on_body(http_parser* parser, const char *at, size_t length);
+static int on_message_begin(http_parser* parser);
+static int on_headers_complete(http_parser* parser);
+static int on_message_complete(http_parser* parser);
+
+enum http_parser_state {
+    HP_START_REQ_OR_RES,
+    HP_MESSAGE_BEGIN, HP_URL,
+    HP_STATUS,
+    HP_HEADER_FIELD,
+    HP_HEADER_VALUE,
+    HP_HEADERS_COMPLETE,
+    HP_BODY,
+    HP_MESSAGE_COMPLETE
+};
+
+class Http1Session : public HttpSession {
+public:
+    static http_parser_settings*    cbs;
+    http_parser                     parser;
+    http_parser_state               state;
+    HttpPayload*                    submited;
+    HttpPayload*                    parsed;
+    // tmp
+    std::string url;          // for on_url
+    std::string header_field; // for on_header_field
+    std::string header_value; // for on_header_value
+    std::string sendbuf;      // for GetSendData
+
+    void handle_header() {
+        if (header_field.size() != 0 && header_value.size() != 0) {
+            parsed->headers[header_field] = header_value;
+            header_field.clear();
+            header_value.clear();
+        }
+    }
+
+    Http1Session() {
+        if (cbs == NULL) {
+            cbs = (http_parser_settings*)malloc(sizeof(http_parser_settings));
+            http_parser_settings_init(cbs);
+            cbs->on_message_begin    = on_message_begin;
+            cbs->on_url              = on_url;
+            cbs->on_status           = on_status;
+            cbs->on_header_field     = on_header_field;
+            cbs->on_header_value     = on_header_value;
+            cbs->on_headers_complete = on_headers_complete;
+            cbs->on_body             = on_body;
+            cbs->on_message_complete = on_message_complete;
+        }
+        http_parser_init(&parser, HTTP_BOTH);
+        parser.data = this;
+        state = HP_START_REQ_OR_RES;
+        submited = NULL;
+        parsed = NULL;
+    }
+
+    virtual ~Http1Session() {
+    }
+
+    virtual int GetSendData(char** data, size_t* len) {
+        if (!submited) {
+            *data = NULL;
+            *len = 0;
+            return 0;
+        }
+        sendbuf = submited->Dump(true, true);
+        submited = NULL;
+        *data = (char*)sendbuf.data();
+        *len = sendbuf.size();
+        return sendbuf.size();
+    }
+
+    virtual int FeedRecvData(const char* data, size_t len) {
+        return http_parser_execute(&parser, cbs, data, len);
+    }
+
+    virtual bool WantRecv() {
+        return state != HP_MESSAGE_COMPLETE;
+    }
+
+    virtual int SubmitRequest(HttpRequest* req) {
+        submited = req;
+        return 0;
+    }
+
+    virtual int SubmitResponse(HttpResponse* res) {
+        submited = res;
+        return 0;
+    }
+
+    virtual int InitRequest(HttpRequest* req) {
+        req->Reset();
+        parsed = req;
+        http_parser_init(&parser, HTTP_REQUEST);
+        return 0;
+    }
+
+    virtual int InitResponse(HttpResponse* res) {
+        res->Reset();
+        parsed = res;
+        http_parser_init(&parser, HTTP_RESPONSE);
+        return 0;
+    }
+
+    virtual int GetError() {
+        return parser.http_errno;
+    }
+
+    virtual const char* StrError(int error) {
+        return http_errno_description((enum http_errno)error);
+    }
+};
+
+http_parser_settings* Http1Session::cbs = NULL;
+
+int on_url(http_parser* parser, const char *at, size_t length) {
+    printd("on_url:%.*s\n", (int)length, at);
+    Http1Session* hss = (Http1Session*)parser->data;
+    hss->state = HP_URL;
+    hss->url.insert(hss->url.size(), at, length);
+    return 0;
+}
+
+int on_status(http_parser* parser, const char *at, size_t length) {
+    printd("on_status:%.*s\n", (int)length, at);
+    Http1Session* hss = (Http1Session*)parser->data;
+    hss->state = HP_STATUS;
+    return 0;
+}
+
+int on_header_field(http_parser* parser, const char *at, size_t length) {
+    printd("on_header_field:%.*s\n", (int)length, at);
+    Http1Session* hss = (Http1Session*)parser->data;
+    hss->handle_header();
+    hss->state = HP_HEADER_FIELD;
+    hss->header_field.insert(hss->header_field.size(), at, length);
+    return 0;
+}
+
+int on_header_value(http_parser* parser, const char *at, size_t length) {
+    printd("on_header_value:%.*s""\n", (int)length, at);
+    Http1Session* hss = (Http1Session*)parser->data;
+    hss->state = HP_HEADER_VALUE;
+    hss->header_value.insert(hss->header_value.size(), at, length);
+    return 0;
+}
+
+int on_body(http_parser* parser, const char *at, size_t length) {
+    //printd("on_body:%.*s""\n", (int)length, at);
+    Http1Session* hss = (Http1Session*)parser->data;
+    hss->state = HP_BODY;
+    hss->parsed->body.insert(hss->parsed->body.size(), at, length);
+    return 0;
+}
+
+int on_message_begin(http_parser* parser) {
+    printd("on_message_begin\n");
+    Http1Session* hss = (Http1Session*)parser->data;
+    hss->state = HP_MESSAGE_BEGIN;
+    return 0;
+}
+
+int on_headers_complete(http_parser* parser) {
+    printd("on_headers_complete\n");
+    Http1Session* hss = (Http1Session*)parser->data;
+    hss->handle_header();
+    auto iter = hss->parsed->headers.find("content-type");
+    if (iter != hss->parsed->headers.end()) {
+        hss->parsed->content_type = http_content_type_enum(iter->second.c_str());
+    }
+    hss->parsed->http_major = parser->http_major;
+    hss->parsed->http_minor = parser->http_minor;
+    if (hss->parsed->type == HTTP_REQUEST) {
+        HttpRequest* req = (HttpRequest*)hss->parsed;
+        req->method = (http_method)parser->method;
+        req->url = hss->url;
+    }
+    else if (hss->parsed->type == HTTP_RESPONSE) {
+        HttpResponse* res = (HttpResponse*)hss->parsed;
+        res->status_code = (http_status)parser->status_code;
+    }
+    hss->state = HP_HEADERS_COMPLETE;
+    return 0;
+}
+
+int on_message_complete(http_parser* parser) {
+    printd("on_message_complete\n");
+    Http1Session* hss = (Http1Session*)parser->data;
+    hss->state = HP_MESSAGE_COMPLETE;
+    return 0;
+}
+
+#ifdef WITH_NGHTTP2
+#include "nghttp2/nghttp2.h"
+#include "http2def.h"
+static nghttp2_nv make_nv(const char* name, const char* value) {
+    nghttp2_nv nv;
+    nv.name = (uint8_t*)name;
+    nv.value = (uint8_t*)value;
+    nv.namelen = strlen(name);
+    nv.valuelen = strlen(value);
+    nv.flags = NGHTTP2_NV_FLAG_NONE;
+    return nv;
+}
+
+static nghttp2_nv make_nv2(const char* name, const char* value,
+        int namelen, int valuelen) {
+    nghttp2_nv nv;
+    nv.name = (uint8_t*)name;
+    nv.value = (uint8_t*)value;
+    nv.namelen = namelen;
+    nv.valuelen = valuelen;
+    nv.flags = NGHTTP2_NV_FLAG_NONE;
+    return nv;
+}
+
+static void print_frame_hd(const nghttp2_frame_hd* hd) {
+    printd("[frame] length=%d type=%x flags=%x stream_id=%d\n",
+        (int)hd->length, (int)hd->type, (int)hd->flags, hd->stream_id);
+}
+static int on_header_callback(nghttp2_session *session,
+        const nghttp2_frame *frame,
+        const uint8_t *name, size_t namelen,
+        const uint8_t *value, size_t valuelen,
+        uint8_t flags, void *userdata);
+static int on_data_chunk_recv_callback(nghttp2_session *session,
+        uint8_t flags, int32_t stream_id, const uint8_t *data,
+        size_t len, void *userdata);
+static int on_frame_recv_callback(nghttp2_session *session,
+        const nghttp2_frame *frame, void *userdata);
+/*
+static ssize_t data_source_read_callback(nghttp2_session *session,
+        int32_t stream_id, uint8_t *buf, size_t length,
+        uint32_t *data_flags, nghttp2_data_source *source, void *userdata);
+*/
+
+enum http2_session_state {
+    HSS_SEND_MAGIC,
+    HSS_SEND_SETTINGS,
+    HSS_SEND_HEADERS,
+    HSS_SEND_DATA_FRAME_HD,
+    HSS_SEND_DATA
+};
+
+class Http2Session : public HttpSession {
+public:
+    static nghttp2_session_callbacks* cbs;
+    nghttp2_session*                session;
+    http2_session_state             state;
+    HttpPayload*                    submited;
+    HttpPayload*                    parsed;
+    int error;
+    int stream_id;
+    int stream_closed;
+    unsigned char                   frame_hdbuf[HTTP2_FRAME_HDLEN];
+
+    Http2Session(http_session_type type) {
+        if (cbs == NULL) {
+            nghttp2_session_callbacks_new(&cbs);
+            nghttp2_session_callbacks_set_on_header_callback(cbs, on_header_callback);
+            nghttp2_session_callbacks_set_on_data_chunk_recv_callback(cbs, on_data_chunk_recv_callback);
+            nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv_callback);
+        }
+        if (type == HTTP_CLIENT) {
+            nghttp2_session_client_new(&session, cbs, NULL);
+            state = HSS_SEND_MAGIC;
+        }
+        else if (type == HTTP_SERVER) {
+            nghttp2_session_server_new(&session, cbs, NULL);
+            state = HSS_SEND_SETTINGS;
+        }
+        nghttp2_session_set_user_data(session, this);
+        submited = NULL;
+        parsed = NULL;
+        stream_id = -1;
+        stream_closed = 0;
+
+        nghttp2_settings_entry settings[] = {
+            {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}
+        };
+        nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, settings, ARRAY_SIZE(settings));
+        state = HSS_SEND_SETTINGS;
+    }
+
+    virtual ~Http2Session() {
+        if (session) {
+            nghttp2_session_del(session);
+            session = NULL;
+        }
+    }
+
+    virtual int GetSendData(char** data, size_t* len) {
+        // HTTP2_MAGIC,HTTP2_SETTINGS,HTTP2_HEADERS
+        *len = nghttp2_session_mem_send(session, (const uint8_t**)data);
+        if (*len == 0) {
+            if (submited) {
+                void* content = submited->Content();
+                int content_length = submited->ContentLength();
+                if (content_length != 0) {
+                    if (state == HSS_SEND_HEADERS) {
+                        // HTTP2_DATA_FRAME_HD
+                        state = HSS_SEND_DATA_FRAME_HD;
+                        http2_frame_hd hd;
+                        hd.length = content_length;
+                        hd.type = HTTP2_DATA;
+                        hd.flags = HTTP2_FLAG_END_STREAM;
+                        hd.stream_id = stream_id;
+                        http2_frame_hd_pack(&hd, frame_hdbuf);
+                        *data = (char*)frame_hdbuf;
+                        *len = HTTP2_FRAME_HDLEN;
+                    }
+                    else if (state == HSS_SEND_DATA_FRAME_HD) {
+                        // HTTP2_DATA
+                        state = HSS_SEND_DATA;
+                        *data = (char*)content;
+                        *len = content_length;
+                    }
+                }
+            }
+        }
+        return *len;
+    }
+
+    virtual int FeedRecvData(const char* data, size_t len) {
+        int ret = nghttp2_session_mem_recv(session, (const uint8_t*)data, len);
+        if (ret != len) {
+            error = ret;
+        }
+        return ret;
+    }
+
+    virtual bool WantRecv() {
+        return stream_id == -1 || stream_closed == 0;
+    }
+
+    virtual int SubmitRequest(HttpRequest* req) {
+        submited = req;
+
+        std::vector<nghttp2_nv> nvs;
+        char c_str[256] = {0};
+        req->ParseUrl();
+        nvs.push_back(make_nv(":method", http_method_str(req->method)));
+        nvs.push_back(make_nv(":path", req->path.c_str()));
+        nvs.push_back(make_nv(":scheme", req->https ? "https" : "http"));
+        if (req->port == 0 ||
+            req->port == DEFAULT_HTTP_PORT ||
+            req->port == DEFAULT_HTTPS_PORT) {
+            nvs.push_back(make_nv(":authority", req->host.c_str()));
+        }
+        else {
+            snprintf(c_str, sizeof(c_str), "%s:%d", req->host.c_str(), req->port);
+            nvs.push_back(make_nv(":authority", c_str));
+        }
+        req->FillContentType();
+        req->FillContentLength();
+        const char* name;
+        const char* value;
+        for (auto& header : req->headers) {
+            name = header.first.c_str();
+            value = header.second.c_str();
+            strlower((char*)name);
+            if (strcmp(name, "connection") == 0) {
+                // HTTP2 use stream
+                continue;
+            }
+            nvs.push_back(make_nv2(name, value, header.first.size(), header.second.size()));
+        }
+        int flags = NGHTTP2_FLAG_END_HEADERS;
+        if (req->ContentLength() == 0) {
+            flags |= NGHTTP2_FLAG_END_STREAM;
+        }
+        stream_id = nghttp2_submit_headers(session, flags, -1, NULL, &nvs[0], nvs.size(), NULL);
+        // avoid DATA_SOURCE_COPY, we do not use nghttp2_submit_data
+        // nghttp2_data_provider data_prd;
+        // data_prd.read_callback = data_source_read_callback;
+        //stream_id = nghttp2_submit_request(session, NULL, &nvs[0], nvs.size(), &data_prd, NULL);
+        stream_closed = 0;
+        state = HSS_SEND_HEADERS;
+        return 0;
+    }
+
+    virtual int SubmitResponse(HttpResponse* res) {
+        submited = res;
+
+        std::vector<nghttp2_nv> nvs;
+        char c_str[256] = {0};
+        snprintf(c_str, sizeof(c_str), "%d", res->status_code);
+        nvs.push_back(make_nv(":status", c_str));
+        res->FillContentType();
+        res->FillContentLength();
+
+        const char* name;
+        const char* value;
+        for (auto& header : res->headers) {
+            name = header.first.c_str();
+            value = header.second.c_str();
+            strlower((char*)name);
+            if (strcmp(name, "connection") == 0) {
+                // HTTP2 use stream
+                continue;
+            }
+            nvs.push_back(make_nv2(name, value, header.first.size(), header.second.size()));
+        }
+        int flags = NGHTTP2_FLAG_END_HEADERS;
+        if (res->ContentLength() == 0) {
+            flags |= NGHTTP2_FLAG_END_STREAM;
+        }
+        nghttp2_submit_headers(session, flags, stream_id, NULL, &nvs[0], nvs.size(), NULL);
+        // avoid DATA_SOURCE_COPY, we do not use nghttp2_submit_data
+        // data_prd.read_callback = data_source_read_callback;
+        //stream_id = nghttp2_submit_request(session, NULL, &nvs[0], nvs.size(), &data_prd, NULL);
+        //nghttp2_submit_response(session, stream_id, &nvs[0], nvs.size(), &data_prd);
+        stream_closed = 0;
+        state = HSS_SEND_HEADERS;
+        return 0;
+    }
+
+    virtual int InitResponse(HttpResponse* res) {
+        res->Reset();
+        res->http_major = 2;
+        res->http_minor = 0;
+        parsed = res;
+        return 0;
+    }
+
+    virtual int InitRequest(HttpRequest* req) {
+        req->Reset();
+        req->http_major = 2;
+        req->http_minor = 0;
+        parsed = req;
+        return 0;
+    }
+
+    virtual int GetError() {
+        return error;
+    }
+
+    virtual const char* StrError(int error) {
+        return nghttp2_http2_strerror(error);
+    }
+};
+
+nghttp2_session_callbacks* Http2Session::cbs = NULL;
+
+int on_header_callback(nghttp2_session *session,
+        const nghttp2_frame *frame,
+        const uint8_t *_name, size_t namelen,
+        const uint8_t *_value, size_t valuelen,
+        uint8_t flags, void *userdata) {
+    printd("on_header_callback\n");
+    print_frame_hd(&frame->hd);
+    const char* name = (const char*)_name;
+    const char* value = (const char*)_value;
+    printd("%s: %s\n", name, value);
+    Http2Session* hss = (Http2Session*)userdata;
+    if (*name == ':') {
+        if (hss->parsed->type == HTTP_REQUEST) {
+            // :method :path :scheme :authority
+            HttpRequest* req = (HttpRequest*)hss->parsed;
+            if (strcmp(name, ":method") == 0) {
+                req->method = http_method_enum(value);
+            }
+            else if (strcmp(name, ":path") == 0) {
+                req->url = value;
+            }
+            else if (strcmp(name, ":scheme") == 0) {
+                req->headers["Scheme"] = value;
+            }
+            else if (strcmp(name, ":authority") == 0) {
+                req->headers["Host"] = value;
+            }
+        }
+        else if (hss->parsed->type == HTTP_RESPONSE) {
+            HttpResponse* res = (HttpResponse*)hss->parsed;
+            if (strcmp(name, ":status") == 0) {
+                res->status_code = (http_status)atoi(value);
+            }
+        }
+    }
+    else {
+        hss->parsed->headers[name] = value;
+        if (strcmp(name, "content-type") == 0) {
+            hss->parsed->content_type = http_content_type_enum(value);
+        }
+    }
+    return 0;
+}
+
+int on_data_chunk_recv_callback(nghttp2_session *session,
+        uint8_t flags, int32_t stream_id, const uint8_t *data,
+        size_t len, void *userdata) {
+    printd("on_data_chunk_recv_callback\n");
+    printd("stream_id=%d length=%d\n", stream_id, (int)len);
+    //printd("%.*s\n", (int)len, data);
+    Http2Session* hss = (Http2Session*)userdata;
+    hss->parsed->body.insert(hss->parsed->body.size(), (const char*)data, len);
+    return 0;
+}
+
+static int on_frame_recv_callback(nghttp2_session *session,
+        const nghttp2_frame *frame, void *userdata) {
+    printd("on_frame_recv_callback\n");
+    print_frame_hd(&frame->hd);
+    Http2Session* hss = (Http2Session*)userdata;
+    switch (frame->hd.type) {
+    case NGHTTP2_DATA:
+    case NGHTTP2_HEADERS:
+        hss->stream_id = frame->hd.stream_id;
+        if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+            printd("on_stream_closed stream_id=%d\n", hss->stream_id);
+            hss->stream_closed = 1;
+        }
+        break;
+    default:
+        break;
+    }
+
+    return 0;
+}
+#endif
+
+HttpSession* HttpSession::New(http_session_type type, http_version version) {
+    if (version == HTTP_V1) {
+        return new Http1Session;
+    }
+    else if (version == HTTP_V2) {
+#ifdef WITH_NGHTTP2
+        return new Http2Session(type);
+#else
+        fprintf(stderr, "Please recompile WITH_NGHTTP2!\n");
+#endif
+    }
+
+    return NULL;
+}

+ 29 - 0
http/HttpSession.h

@@ -0,0 +1,29 @@
+#ifndef HTTP_SESSION_H_
+#define HTTP_SESSION_H_
+
+#include "HttpPayload.h"
+
+class HttpSession {
+public:
+    static HttpSession* New(http_session_type type = HTTP_CLIENT, http_version version = HTTP_V1);
+    virtual ~HttpSession() {}
+
+    virtual int GetSendData(char** data, size_t* len) = 0;
+    virtual int FeedRecvData(const char* data, size_t len) = 0;
+    virtual bool WantRecv() = 0;
+
+    // client
+    // SubmitRequest -> while(GetSendData) {send} -> InitResponse -> do {recv -> FeedRecvData} while(WantRecv)
+    virtual int SubmitRequest(HttpRequest* req) = 0;
+    virtual int InitResponse(HttpResponse* res) = 0;
+
+    // server
+    // InitRequest -> do {recv -> FeedRecvData} while(WantRecv) -> SubmitResponse -> while(GetSendData) {send}
+    virtual int InitRequest(HttpRequest* req) = 0;
+    virtual int SubmitResponse(HttpResponse* res) = 0;
+
+    virtual int GetError() = 0;
+    virtual const char* StrError(int error) = 0;
+};
+
+#endif // HTTP_SESSION_H_

+ 145 - 152
http/client/http_client.cpp

@@ -9,7 +9,7 @@
 #else
 #include "herr.h"
 #include "hsocket.h"
-#include "HttpParser.h"
+#include "HttpSession.h"
 #include "ssl_ctx.h"
 #endif
 
@@ -17,37 +17,45 @@
 #include "openssl/ssl.h"
 #endif
 
-struct http_session_s {
-    int          use_tls;
+struct http_client_s {
     std::string  host;
     int          port;
-    int          timeout;
+    int          tls;
+    int          http_version;
+    int          timeout; // s
     http_headers headers;
 //private:
 #ifdef WITH_CURL
     CURL* curl;
 #else
     int fd;
+    HttpSession* session;
 #endif
 #ifdef WITH_OPENSSL
     SSL* ssl;
 #endif
 
-    http_session_s() {
-        use_tls = 0;
+    http_client_s() {
         port = DEFAULT_HTTP_PORT;
+        tls = 0;
+        http_version = 1;
         timeout = DEFAULT_HTTP_TIMEOUT;
 #ifdef WITH_CURL
         curl = NULL;
 #else
         fd = -1;
+        session = NULL;
 #endif
 #ifdef WITH_OPENSSL
         ssl = NULL;
 #endif
     }
 
-    ~http_session_s() {
+    ~http_client_s() {
+        Close();
+    }
+
+    void Close() {
 #ifdef WITH_OPENSSL
         if (ssl) {
             SSL_free(ssl);
@@ -64,77 +72,79 @@ struct http_session_s {
             closesocket(fd);
             fd = -1;
         }
+        if (session) {
+            delete session;
+            session = NULL;
+        }
 #endif
     }
 };
 
-static int __http_session_send(http_session_t* hss, HttpRequest* req, HttpResponse* res);
+static int __http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* res);
 
-http_session_t* http_session_new(const char* host, int port) {
-    http_session_t* hss = new http_session_t;
-    hss->host = host;
-    hss->port = port;
-    hss->headers["Host"] = asprintf("%s:%d", host, port);
-    hss->headers["Connection"] = "keep-alive";
-    return hss;
+http_client_t* http_client_new(const char* host, int port, int tls) {
+    http_client_t* cli = new http_client_t;
+    cli->tls = tls;
+    cli->port = port;
+    if (host) {
+        cli->host = host;
+        cli->headers["Host"] = asprintf("%s:%d", host, port);
+    }
+    cli->headers["Connection"] = "keep-alive";
+    return cli;
 }
 
-int http_session_del(http_session_t* hss) {
-    if (hss == NULL) return 0;
-    delete hss;
+int http_client_del(http_client_t* cli) {
+    if (cli == NULL) return 0;
+    delete cli;
     return 0;
 }
 
-int http_session_set_timeout(http_session_t* hss, int timeout) {
-    hss->timeout = timeout;
+int http_client_set_timeout(http_client_t* cli, int timeout) {
+    cli->timeout = timeout;
     return 0;
 }
 
-int http_session_clear_headers(http_session_t* hss) {
-    hss->headers.clear();
+int http_client_clear_headers(http_client_t* cli) {
+    cli->headers.clear();
     return 0;
 }
 
-int http_session_set_header(http_session_t* hss, const char* key, const char* value) {
-    hss->headers[key] = value;
+int http_client_set_header(http_client_t* cli, const char* key, const char* value) {
+    cli->headers[key] = value;
     return 0;
 }
 
-int http_session_del_header(http_session_t* hss, const char* key) {
-    auto iter = hss->headers.find(key);
-    if (iter != hss->headers.end()) {
-        hss->headers.erase(iter);
+int http_client_del_header(http_client_t* cli, const char* key) {
+    auto iter = cli->headers.find(key);
+    if (iter != cli->headers.end()) {
+        cli->headers.erase(iter);
     }
     return 0;
 }
 
-const char* http_session_get_header(http_session_t* hss, const char* key) {
-    auto iter = hss->headers.find(key);
-    if (iter != hss->headers.end()) {
+const char* http_client_get_header(http_client_t* cli, const char* key) {
+    auto iter = cli->headers.find(key);
+    if (iter != cli->headers.end()) {
         return iter->second.c_str();
     }
     return NULL;
 }
 
-int http_session_send(http_session_t* hss, HttpRequest* req, HttpResponse* res) {
-    for (auto& pair : hss->headers) {
+int http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* res) {
+    for (auto& pair : cli->headers) {
         req->headers[pair.first] = pair.second;
     }
-    return __http_session_send(hss, req, res);
+    return __http_client_send(cli, req, res);
 }
 
 int http_client_send(HttpRequest* req, HttpResponse* res, int timeout) {
-    http_session_t hss;
-    hss.timeout = timeout;
-    return __http_session_send(&hss, req, res);
+    http_client_t cli;
+    cli.timeout = timeout;
+    return __http_client_send(&cli, req, res);
 }
 
 #ifdef WITH_CURL
-
-static size_t s_formget_cb(void *arg, const char *buf, size_t len) {
-    return len;
-}
-
 static size_t s_header_cb(char* buf, size_t size, size_t cnt, void* userdata) {
     if (buf == NULL || userdata == NULL)    return 0;
 
@@ -145,10 +155,17 @@ static size_t s_header_cb(char* buf, size_t size, size_t cnt, void* userdata) {
     if (pos == string::npos) {
         if (strncmp(buf, "HTTP/", 5) == 0) {
             // status line
-            // HTTP/1.1 200 OK\r\n
             //hlogd("%s", buf);
             int http_major,http_minor,status_code;
-            sscanf(buf, "HTTP/%d.%d %d", &http_major, &http_minor, &status_code);
+            if (buf[5] == '1') {
+                // HTTP/1.1 200 OK\r\n
+                sscanf(buf, "HTTP/%d.%d %d", &http_major, &http_minor, &status_code);
+            }
+            else if (buf[5] == '2') {
+                // HTTP/2 200\r\n
+                sscanf(buf, "HTTP/%d %d", &http_major, &status_code);
+                http_minor = 0;
+            }
             res->http_major = http_major;
             res->http_minor = http_minor;
             res->status_code = (http_status)status_code;
@@ -171,21 +188,27 @@ static size_t s_body_cb(char *buf, size_t size, size_t cnt, void *userdata) {
     return size*cnt;
 }
 
-int __http_session_send(http_session_t* hss, HttpRequest* req, HttpResponse* res) {
+int __http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* res) {
     if (req == NULL || res == NULL) {
         return -1;
     }
 
-    if (hss->curl == NULL) {
-        hss->curl = curl_easy_init();
+    if (cli->curl == NULL) {
+        cli->curl = curl_easy_init();
     }
-    CURL* curl = hss->curl;
-    int timeout = hss->timeout;
+    CURL* curl = cli->curl;
 
     // SSL
     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
 
+    // http2
+    if (req->http_major == 2) {
+        //curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2_0);
+        //No Connection: Upgrade
+        curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
+    }
+
     // TCP_NODELAY
     curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1);
 
@@ -193,12 +216,12 @@ int __http_session_send(http_session_t* hss, HttpRequest* req, HttpResponse* res
     curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_method_str(req->method));
 
     // url
-    std::string url = req->dump_url();
-    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
-    //hlogd("%s %s HTTP/%d.%d", http_method_str(req->method), url.c_str(), req->http_major, req->http_minor);
+    req->DumpUrl();
+    curl_easy_setopt(curl, CURLOPT_URL, req->url.c_str());
+    //hlogd("%s %s HTTP/%d.%d", http_method_str(req->method), req->url.c_str(), req->http_major, req->http_minor);
 
-    // header
-    req->fill_content_type();
+    // headers
+    req->FillContentType();
     struct curl_slist *headers = NULL;
     for (auto& pair : req->headers) {
         string header = pair.first;
@@ -212,7 +235,7 @@ int __http_session_send(http_session_t* hss, HttpRequest* req, HttpResponse* res
     struct curl_httppost* httppost = NULL;
     struct curl_httppost* lastpost = NULL;
     if (req->body.size() == 0) {
-        req->dump_body();
+        req->DumpBody();
         if (req->body.size() == 0 &&
             req->content_type == MULTIPART_FORM_DATA) {
             for (auto& pair : req->mp) {
@@ -231,7 +254,6 @@ int __http_session_send(http_session_t* hss, HttpRequest* req, HttpResponse* res
             }
             if (httppost) {
                 curl_easy_setopt(curl, CURLOPT_HTTPPOST, httppost);
-                curl_formget(httppost, NULL, s_formget_cb);
             }
         }
     }
@@ -240,8 +262,8 @@ int __http_session_send(http_session_t* hss, HttpRequest* req, HttpResponse* res
         curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, req->body.size());
     }
 
-    if (timeout > 0) {
-        curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
+    if (cli->timeout > 0) {
+        curl_easy_setopt(curl, CURLOPT_TIMEOUT, cli->timeout);
     }
 
     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, s_body_cb);
@@ -281,29 +303,29 @@ const char* http_client_strerror(int errcode) {
     return curl_easy_strerror((CURLcode)errcode);
 }
 #else
-static int __http_session_connect(http_session_t* hss) {
+static int __http_client_connect(http_client_t* cli) {
     int blocktime = MAX_CONNECT_TIMEOUT;
-    if (hss->timeout > 0) {
-        blocktime = MIN(hss->timeout*1000, blocktime);
+    if (cli->timeout > 0) {
+        blocktime = MIN(cli->timeout*1000, blocktime);
     }
-    int connfd = ConnectTimeout(hss->host.c_str(), hss->port, blocktime);
+    int connfd = ConnectTimeout(cli->host.c_str(), cli->port, blocktime);
     if (connfd < 0) {
         return socket_errno();
     }
     tcp_nodelay(connfd, 1);
 
-    if (hss->use_tls) {
+    if (cli->tls) {
 #ifdef WITH_OPENSSL
         if (g_ssl_ctx == NULL) {
             ssl_ctx_init(NULL, NULL, NULL);
         }
-        hss->ssl = SSL_new((SSL_CTX*)g_ssl_ctx);
-        SSL_set_fd(hss->ssl, connfd);
-        if (SSL_connect(hss->ssl) != 1) {
-            int err = SSL_get_error(hss->ssl, -1);
+        cli->ssl = SSL_new((SSL_CTX*)g_ssl_ctx);
+        SSL_set_fd(cli->ssl, connfd);
+        if (SSL_connect(cli->ssl) != 1) {
+            int err = SSL_get_error(cli->ssl, -1);
             fprintf(stderr, "SSL handshark failed: %d\n", err);
-            SSL_free(hss->ssl);
-            hss->ssl = NULL;
+            SSL_free(cli->ssl);
+            cli->ssl = NULL;
             closesocket(connfd);
             return err;
         }
@@ -313,104 +335,86 @@ static int __http_session_connect(http_session_t* hss) {
         return ERR_INVALID_PROTOCOL;
 #endif
     }
-    hss->fd = connfd;
-    return 0;
-}
 
-static int __http_session_close(http_session_t* hss) {
-#ifdef WITH_OPENSSL
-    if (hss->ssl) {
-        SSL_free(hss->ssl);
-        hss->ssl = NULL;
-    }
-#endif
-    if (hss->fd > 0) {
-        closesocket(hss->fd);
-        hss->fd = -1;
+    if (cli->session == NULL) {
+        cli->session = HttpSession::New(HTTP_CLIENT, (http_version)cli->http_version);
     }
+
+    cli->fd = connfd;
     return 0;
 }
 
-static int __http_session_send(http_session_t* hss, HttpRequest* req, HttpResponse* res) {
+int __http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* res) {
     // connect -> send -> recv -> http_parser
     int err = 0;
-    int timeout = hss->timeout;
-    int connfd = hss->fd;
+    int timeout = cli->timeout;
+    int connfd = cli->fd;
 
-    // use_tls ?
-    int use_tls = hss->use_tls;
-    if (strncmp(req->url.c_str(), "https", 5) == 0) {
-        hss->use_tls = use_tls = 1;
+    req->ParseUrl();
+    if (cli->host.size() == 0) {
+        cli->host = req->host;
+        cli->port = req->port;
     }
-
-    // parse host:port from Headers
-    std::string http = req->dump(true, true);
-    if (hss->host.size() == 0) {
-        auto Host = req->headers.find("Host");
-        if (Host == req->headers.end()) {
-            return ERR_INVALID_PARAM;
-        }
-        StringList strlist = split(Host->second, ':');
-        hss->host = strlist[0];
-        if (strlist.size() == 2) {
-            hss->port = atoi(strlist[1].c_str());
-        }
-        else {
-            hss->port = DEFAULT_HTTP_PORT;
-        }
+    if (cli->tls == 0) {
+        cli->tls = req->https;
     }
+    cli->http_version = req->http_major;
 
     time_t start_time = time(NULL);
     time_t cur_time;
     int fail_cnt = 0;
 connect:
     if (connfd <= 0) {
-        int ret = __http_session_connect(hss);
+        int ret = __http_client_connect(cli);
         if (ret != 0) {
             return ret;
         }
-        connfd = hss->fd;
+        connfd = cli->fd;
     }
 
-    HttpParser parser;
-    parser.parser_response_init(res);
+    cli->session->SubmitRequest(req);
     char recvbuf[1024] = {0};
     int total_nsend, nsend, nrecv;
 send:
-    total_nsend = nsend = nrecv = 0;
-    while (1) {
-        if (timeout > 0) {
-            cur_time = time(NULL);
-            if (cur_time - start_time >= timeout) {
-                return ERR_TASK_TIMEOUT;
+    char* data = NULL;
+    size_t len  = 0;
+    while (cli->session->GetSendData(&data, &len)) {
+        total_nsend = 0;
+        while (1) {
+            if (timeout > 0) {
+                cur_time = time(NULL);
+                if (cur_time - start_time >= timeout) {
+                    return ERR_TASK_TIMEOUT;
+                }
+                so_sndtimeo(connfd, (timeout-(cur_time-start_time)) * 1000);
             }
-            so_sndtimeo(connfd, (timeout-(cur_time-start_time)) * 1000);
-        }
 #ifdef WITH_OPENSSL
-        if (use_tls) {
-            nsend = SSL_write(hss->ssl, http.c_str()+total_nsend, http.size()-total_nsend);
-        }
+            if (cli->tls) {
+                nsend = SSL_write(cli->ssl, data+total_nsend, len-total_nsend);
+            }
 #endif
-        if (!use_tls) {
-            nsend = send(connfd, http.c_str()+total_nsend, http.size()-total_nsend, 0);
-        }
-        if (nsend <= 0) {
-            if (++fail_cnt == 1) {
-                // maybe keep-alive timeout, try again
-                __http_session_close(hss);
-                goto connect;
+            if (!cli->tls) {
+                nsend = send(connfd, data+total_nsend, len-total_nsend, 0);
             }
-            else {
-                return socket_errno();
+            if (nsend <= 0) {
+                if (++fail_cnt == 1) {
+                    // maybe keep-alive timeout, try again
+                    cli->Close();
+                    goto connect;
+                }
+                else {
+                    return socket_errno();
+                }
+            }
+            total_nsend += nsend;
+            if (total_nsend == len) {
+                break;
             }
-        }
-        total_nsend += nsend;
-        if (total_nsend == http.size()) {
-            break;
         }
     }
+    cli->session->InitResponse(res);
 recv:
-    while(1) {
+    do {
         if (timeout > 0) {
             cur_time = time(NULL);
             if (cur_time - start_time >= timeout) {
@@ -419,32 +423,21 @@ recv:
             so_rcvtimeo(connfd, (timeout-(cur_time-start_time)) * 1000);
         }
 #ifdef WITH_OPENSSL
-        if (use_tls) {
-            nrecv = SSL_read(hss->ssl, recvbuf, sizeof(recvbuf));
+        if (cli->tls) {
+            nrecv = SSL_read(cli->ssl, recvbuf, sizeof(recvbuf));
         }
 #endif
-        if (!use_tls) {
+        if (!cli->tls) {
             nrecv = recv(connfd, recvbuf, sizeof(recvbuf), 0);
         }
         if (nrecv <= 0) {
             return socket_errno();
         }
-        int nparse = parser.execute(recvbuf, nrecv);
-        if (nparse != nrecv || parser.get_errno() != HPE_OK) {
+        int nparse = cli->session->FeedRecvData(recvbuf, nrecv);
+        if (nparse != nrecv) {
             return ERR_PARSE;
         }
-        if (parser.get_state() == HP_MESSAGE_COMPLETE) {
-            err = 0;
-            break;
-        }
-        if (timeout > 0) {
-            cur_time = time(NULL);
-            if (cur_time - start_time >= timeout) {
-                return ERR_TASK_TIMEOUT;
-            }
-            so_rcvtimeo(connfd, (timeout-(cur_time-start_time)) * 1000);
-        }
-    }
+    } while(cli->session->WantRecv());
     return err;
 }
 

+ 16 - 15
http/client/http_client.h

@@ -1,7 +1,7 @@
 #ifndef HTTP_CLIENT_H_
 #define HTTP_CLIENT_H_
 
-#include "HttpRequest.h"
+#include "HttpPayload.h"
 
 /*
 #include <stdio.h>
@@ -14,33 +14,34 @@ int main(int argc, char* argv[]) {
     req.url = "http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/";
     HttpResponse res;
     int ret = http_client_send(&req, &res);
-    printf("%s\n", req.dump(true,true).c_str());
+    printf("%s\n", req.Dump(true,true).c_str());
     if (ret != 0) {
         printf("* Failed:%s:%d\n", http_client_strerror(ret), ret);
     }
     else {
-        printf("%s\n", res.dump(true,true).c_str());
+        printf("%s\n", res.Dump(true,true).c_str());
     }
     return ret;
 }
 */
 
 #define DEFAULT_HTTP_TIMEOUT    10 // s
-#define DEFAULT_HTTP_PORT       80
-int http_client_send(HttpRequest* req, HttpResponse* res, int timeout = DEFAULT_HTTP_TIMEOUT);
+typedef struct http_client_s http_client_t;
+
+http_client_t* http_client_new(const char* host = NULL, int port = DEFAULT_HTTP_PORT, int tls = 0);
+int http_client_del(http_client_t* cli);
 const char* http_client_strerror(int errcode);
 
-// http_session: Connection: keep-alive
-typedef struct http_session_s http_session_t;
-http_session_t* http_session_new(const char* host, int port = DEFAULT_HTTP_PORT);
-int http_session_del(http_session_t* hss);
+int http_client_set_timeout(http_client_t* cli, int timeout);
 
-int http_session_set_timeout(http_session_t* hss, int timeout);
-int http_session_clear_headers(http_session_t* hss);
-int http_session_set_header(http_session_t* hss, const char* key, const char* value);
-int http_session_del_header(http_session_t* hss, const char* key);
-const char* http_session_get_header(http_session_t* hss, const char* key);
+int http_client_clear_headers(http_client_t* cli);
+int http_client_set_header(http_client_t* cli, const char* key, const char* value);
+int http_client_del_header(http_client_t* cli, const char* key);
+const char* http_client_get_header(http_client_t* cli, const char* key);
 
-int http_session_send(http_session_t* hss, HttpRequest* req, HttpResponse* res);
+int http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* res);
+
+// http_client_new -> http_client_send -> http_client_del
+int http_client_send(HttpRequest* req, HttpResponse* res, int timeout = DEFAULT_HTTP_TIMEOUT);
 
 #endif  // HTTP_CLIENT_H_

+ 86 - 0
http/http2def.h

@@ -0,0 +1,86 @@
+#ifndef HTTP2_DEF_H_
+#define HTTP2_DEF_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define HTTP2_MAGIC             "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
+#define HTTP2_MAGIC_LEN         24
+
+// length:3bytes + type:1byte + flags:1byte + stream_id:4bytes = 9bytes
+#define HTTP2_FRAME_HDLEN       9
+
+#define HTTP2_UPGRADE_RESPONSE \
+"HTTP/1.1 101 Switching Protocols\r\n"\
+"Connection: Upgrade\r\n"\
+"Upgrade: h2c\r\n\r\n"
+
+typedef enum {
+    HTTP2_DATA          = 0,
+    HTTP2_HEADERS       = 0x01,
+    HTTP2_PRIORITY      = 0x02,
+    HTTP2_RST_STREAM    = 0x03,
+    HTTP2_SETTINGS      = 0x04,
+    HTTP2_PUSH_PROMISE  = 0x05,
+    HTTP2_PING          = 0x06,
+    HTTP2_GOAWAY        = 0x07,
+    HTTP2_WINDOW_UPDATE = 0x08,
+    HTTP2_CONTINUATION  = 0x09,
+    HTTP2_ALTSVC        = 0x0a,
+    HTTP2_ORIGIN        = 0x0c
+} http2_frame_type;
+
+typedef enum {
+    HTTP2_FLAG_NONE         = 0,
+    HTTP2_FLAG_END_STREAM   = 0x01,
+    HTTP2_FLAG_END_HEADERS  = 0x04,
+    HTTP2_FLAG_PADDED       = 0x08,
+    HTTP2_FLAG_PRIORITY     = 0x20
+} http2_flag;
+
+typedef struct {
+    int                 length;
+    http2_frame_type    type;
+    http2_flag          flags;
+    int                 stream_id;
+} http2_frame_hd;
+
+static inline void http2_frame_hd_pack(const http2_frame_hd* hd, unsigned char* buf) {
+    // hton
+    int length = hd->length;
+    int stream_id = hd->stream_id;
+    unsigned char* p = buf;
+    *p++ = (length >> 16) & 0xFF;
+    *p++ = (length >>  8) & 0xFF;
+    *p++ =  length        & 0xFF;
+    *p++ = (unsigned char)hd->type;
+    *p++ = (unsigned char)hd->flags;
+    *p++ = (stream_id >> 24) & 0xFF;
+    *p++ = (stream_id >> 16) & 0xFF;
+    *p++ = (stream_id >>  8) & 0xFF;
+    *p++ =  stream_id        & 0xFF;
+}
+
+static inline void http2_frame_hd_unpack(const unsigned char* buf, http2_frame_hd* hd) {
+    // ntoh
+    const unsigned char* p = buf;
+    hd->length  = *p++ << 16;
+    hd->length += *p++ << 8;
+    hd->length += *p++;
+
+    hd->type = (http2_frame_type)*p++;
+    hd->flags = (http2_flag)*p++;
+
+    hd->stream_id  = *p++ << 24;
+    hd->stream_id += *p++ << 16;
+    hd->stream_id += *p++ << 8;
+    hd->stream_id += *p++;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 1 - 1
http/http_content.cpp

@@ -5,7 +5,7 @@
 
 #include "hstring.h"
 
-#include "HttpRequest.h"
+#include "httpdef.h" // for http_content_type_str_by_suffix
 
 #ifndef LOWER
 #define LOWER(c)    ((c) | 0x20)

+ 0 - 18
http/http_parser.c

@@ -2125,24 +2125,6 @@ http_should_keep_alive (const http_parser *parser)
   return !http_message_needs_eof(parser);
 }
 
-
-const char *
-http_method_str (enum http_method m)
-{
-  return ELEM_AT(method_strings, m, "<unknown>");
-}
-
-const char *
-http_status_str (enum http_status s)
-{
-  switch (s) {
-#define XX(num, name, string) case HTTP_STATUS_##name: return #string;
-    HTTP_STATUS_MAP(XX)
-#undef XX
-    default: return "<unknown>";
-  }
-}
-
 void
 http_parser_init (http_parser *parser, enum http_parser_type t)
 {

+ 1 - 133
http/http_parser.h

@@ -23,6 +23,7 @@
 #ifdef __cplusplus
 extern "C" {
 #endif
+#include "httpdef.h"
 
 /* Also update SONAME in the Makefile whenever you change these. */
 #define HTTP_PARSER_VERSION_MAJOR 2
@@ -66,7 +67,6 @@ typedef unsigned __int64 uint64_t;
 typedef struct http_parser http_parser;
 typedef struct http_parser_settings http_parser_settings;
 
-
 /* Callbacks should return non-zero to indicate an error. The parser will
  * then halt execution.
  *
@@ -89,132 +89,6 @@ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
 typedef int (*http_cb) (http_parser*);
 
 
-/* Status Codes */
-#define HTTP_STATUS_MAP(XX)                                                 \
-  XX(100, CONTINUE,                        Continue)                        \
-  XX(101, SWITCHING_PROTOCOLS,             Switching Protocols)             \
-  XX(102, PROCESSING,                      Processing)                      \
-  XX(200, OK,                              OK)                              \
-  XX(201, CREATED,                         Created)                         \
-  XX(202, ACCEPTED,                        Accepted)                        \
-  XX(203, NON_AUTHORITATIVE_INFORMATION,   Non-Authoritative Information)   \
-  XX(204, NO_CONTENT,                      No Content)                      \
-  XX(205, RESET_CONTENT,                   Reset Content)                   \
-  XX(206, PARTIAL_CONTENT,                 Partial Content)                 \
-  XX(207, MULTI_STATUS,                    Multi-Status)                    \
-  XX(208, ALREADY_REPORTED,                Already Reported)                \
-  XX(226, IM_USED,                         IM Used)                         \
-  XX(300, MULTIPLE_CHOICES,                Multiple Choices)                \
-  XX(301, MOVED_PERMANENTLY,               Moved Permanently)               \
-  XX(302, FOUND,                           Found)                           \
-  XX(303, SEE_OTHER,                       See Other)                       \
-  XX(304, NOT_MODIFIED,                    Not Modified)                    \
-  XX(305, USE_PROXY,                       Use Proxy)                       \
-  XX(307, TEMPORARY_REDIRECT,              Temporary Redirect)              \
-  XX(308, PERMANENT_REDIRECT,              Permanent Redirect)              \
-  XX(400, BAD_REQUEST,                     Bad Request)                     \
-  XX(401, UNAUTHORIZED,                    Unauthorized)                    \
-  XX(402, PAYMENT_REQUIRED,                Payment Required)                \
-  XX(403, FORBIDDEN,                       Forbidden)                       \
-  XX(404, NOT_FOUND,                       Not Found)                       \
-  XX(405, METHOD_NOT_ALLOWED,              Method Not Allowed)              \
-  XX(406, NOT_ACCEPTABLE,                  Not Acceptable)                  \
-  XX(407, PROXY_AUTHENTICATION_REQUIRED,   Proxy Authentication Required)   \
-  XX(408, REQUEST_TIMEOUT,                 Request Timeout)                 \
-  XX(409, CONFLICT,                        Conflict)                        \
-  XX(410, GONE,                            Gone)                            \
-  XX(411, LENGTH_REQUIRED,                 Length Required)                 \
-  XX(412, PRECONDITION_FAILED,             Precondition Failed)             \
-  XX(413, PAYLOAD_TOO_LARGE,               Payload Too Large)               \
-  XX(414, URI_TOO_LONG,                    URI Too Long)                    \
-  XX(415, UNSUPPORTED_MEDIA_TYPE,          Unsupported Media Type)          \
-  XX(416, RANGE_NOT_SATISFIABLE,           Range Not Satisfiable)           \
-  XX(417, EXPECTATION_FAILED,              Expectation Failed)              \
-  XX(421, MISDIRECTED_REQUEST,             Misdirected Request)             \
-  XX(422, UNPROCESSABLE_ENTITY,            Unprocessable Entity)            \
-  XX(423, LOCKED,                          Locked)                          \
-  XX(424, FAILED_DEPENDENCY,               Failed Dependency)               \
-  XX(426, UPGRADE_REQUIRED,                Upgrade Required)                \
-  XX(428, PRECONDITION_REQUIRED,           Precondition Required)           \
-  XX(429, TOO_MANY_REQUESTS,               Too Many Requests)               \
-  XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
-  XX(451, UNAVAILABLE_FOR_LEGAL_REASONS,   Unavailable For Legal Reasons)   \
-  XX(500, INTERNAL_SERVER_ERROR,           Internal Server Error)           \
-  XX(501, NOT_IMPLEMENTED,                 Not Implemented)                 \
-  XX(502, BAD_GATEWAY,                     Bad Gateway)                     \
-  XX(503, SERVICE_UNAVAILABLE,             Service Unavailable)             \
-  XX(504, GATEWAY_TIMEOUT,                 Gateway Timeout)                 \
-  XX(505, HTTP_VERSION_NOT_SUPPORTED,      HTTP Version Not Supported)      \
-  XX(506, VARIANT_ALSO_NEGOTIATES,         Variant Also Negotiates)         \
-  XX(507, INSUFFICIENT_STORAGE,            Insufficient Storage)            \
-  XX(508, LOOP_DETECTED,                   Loop Detected)                   \
-  XX(510, NOT_EXTENDED,                    Not Extended)                    \
-  XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
-
-enum http_status
-  {
-#define XX(num, name, string) HTTP_STATUS_##name = num,
-  HTTP_STATUS_MAP(XX)
-#undef XX
-  };
-
-
-/* Request Methods */
-#define HTTP_METHOD_MAP(XX)         \
-  XX(0,  DELETE,      DELETE)       \
-  XX(1,  GET,         GET)          \
-  XX(2,  HEAD,        HEAD)         \
-  XX(3,  POST,        POST)         \
-  XX(4,  PUT,         PUT)          \
-  /* pathological */                \
-  XX(5,  CONNECT,     CONNECT)      \
-  XX(6,  OPTIONS,     OPTIONS)      \
-  XX(7,  TRACE,       TRACE)        \
-  /* WebDAV */                      \
-  XX(8,  COPY,        COPY)         \
-  XX(9,  LOCK,        LOCK)         \
-  XX(10, MKCOL,       MKCOL)        \
-  XX(11, MOVE,        MOVE)         \
-  XX(12, PROPFIND,    PROPFIND)     \
-  XX(13, PROPPATCH,   PROPPATCH)    \
-  XX(14, SEARCH,      SEARCH)       \
-  XX(15, UNLOCK,      UNLOCK)       \
-  XX(16, BIND,        BIND)         \
-  XX(17, REBIND,      REBIND)       \
-  XX(18, UNBIND,      UNBIND)       \
-  XX(19, ACL,         ACL)          \
-  /* subversion */                  \
-  XX(20, REPORT,      REPORT)       \
-  XX(21, MKACTIVITY,  MKACTIVITY)   \
-  XX(22, CHECKOUT,    CHECKOUT)     \
-  XX(23, MERGE,       MERGE)        \
-  /* upnp */                        \
-  XX(24, MSEARCH,     M-SEARCH)     \
-  XX(25, NOTIFY,      NOTIFY)       \
-  XX(26, SUBSCRIBE,   SUBSCRIBE)    \
-  XX(27, UNSUBSCRIBE, UNSUBSCRIBE)  \
-  /* RFC-5789 */                    \
-  XX(28, PATCH,       PATCH)        \
-  XX(29, PURGE,       PURGE)        \
-  /* CalDAV */                      \
-  XX(30, MKCALENDAR,  MKCALENDAR)   \
-  /* RFC-2068, section 19.6.1.2 */  \
-  XX(31, LINK,        LINK)         \
-  XX(32, UNLINK,      UNLINK)       \
-  /* icecast */                     \
-  XX(33, SOURCE,      SOURCE)       \
-
-enum http_method
-  {
-#define XX(num, name, string) HTTP_##name = num,
-  HTTP_METHOD_MAP(XX)
-#undef XX
-  };
-
-
-enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
-
-
 /* Flag values for http_parser.flags field */
 enum flags
   { F_CHUNKED               = 1 << 0
@@ -404,12 +278,6 @@ size_t http_parser_execute(http_parser *parser,
  */
 int http_should_keep_alive(const http_parser *parser);
 
-/* Returns a string version of the HTTP method. */
-const char *http_method_str(enum http_method m);
-
-/* Returns a string version of the HTTP status code. */
-const char *http_status_str(enum http_status s);
-
 /* Return a string name of the given error */
 const char *http_errno_name(enum http_errno err);
 

+ 103 - 0
http/httpdef.c

@@ -0,0 +1,103 @@
+#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);
+}
+
+const char* http_status_str(enum http_status status) {
+    switch (status) {
+#define XX(num, name, string) case HTTP_STATUS_##name: return #string;
+    HTTP_STATUS_MAP(XX)
+#undef XX
+    default: return "<unknown>";
+    }
+}
+
+const char* http_method_str(enum http_method method) {
+    switch (method) {
+#define XX(num, name, string) case HTTP_##name: return #string;
+    HTTP_METHOD_MAP(XX)
+#undef XX
+    default: return "<unknown>";
+    }
+}
+
+const char* http_content_type_str(enum http_content_type type) {
+    switch (type) {
+#define XX(name, string, suffix) case name: return #string;
+    HTTP_CONTENT_TYPE_MAP(XX)
+#undef XX
+    default: return "<unknown>";
+    }
+}
+
+enum http_status http_status_enum(const char* str) {
+#define XX(num, name, string) \
+    if (strcmp(str, #string) == 0) { \
+        return HTTP_STATUS_##name; \
+    }
+    HTTP_STATUS_MAP(XX)
+#undef XX
+    return HTTP_CUSTOM_STATUS;
+}
+
+enum http_method http_method_enum(const char* str) {
+#define XX(num, name, string) \
+    if (strcmp(str, #string) == 0) { \
+        return HTTP_##name; \
+    }
+    HTTP_METHOD_MAP(XX)
+#undef XX
+    return HTTP_CUSTOM_METHOD;
+}
+
+enum http_content_type http_content_type_enum(const char* str) {
+    if (!str || !*str) {
+        return CONTENT_TYPE_NONE;
+    }
+#define XX(name, string, suffix) \
+    if (mystrcmp(str, #string) == 0) { \
+        return name; \
+    }
+    HTTP_CONTENT_TYPE_MAP(XX)
+#undef XX
+    return CONTENT_TYPE_UNDEFINED;
+}
+
+const char* http_content_type_suffix(enum http_content_type type) {
+    switch (type) {
+#define XX(name, string, suffix) case name: return #suffix;
+    HTTP_CONTENT_TYPE_MAP(XX)
+#undef XX
+    default: return "<unknown>";
+    }
+}
+
+const char* http_content_type_str_by_suffix(const char* str) {
+    if (!str || !*str) {
+        return "";
+    }
+#define XX(name, string, suffix) \
+    if (strcmp(str, #suffix) == 0) { \
+        return #string; \
+    }
+    HTTP_CONTENT_TYPE_MAP(XX)
+#undef XX
+    return "";
+}
+
+enum http_content_type http_content_type_enum_by_suffix(const char* str) {
+    if (!str || !*str) {
+        return CONTENT_TYPE_NONE;
+    }
+#define XX(name, string, suffix) \
+    if (strcmp(str, #suffix) == 0) { \
+        return name; \
+    }
+    HTTP_CONTENT_TYPE_MAP(XX)
+#undef XX
+    return CONTENT_TYPE_UNDEFINED;
+}

+ 180 - 0
http/httpdef.h

@@ -0,0 +1,180 @@
+#ifndef HTTP_DEF_H_
+#define HTTP_DEF_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DEFAULT_HTTP_PORT       80
+#define DEFAULT_HTTPS_PORT      443
+
+enum http_version { HTTP_V1 = 1, HTTP_V2 = 2 };
+enum http_session_type { HTTP_CLIENT, HTTP_SERVER };
+enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
+
+// http_status
+// XX(num, name, string)
+#define HTTP_STATUS_MAP(XX)                                                 \
+  XX(100, CONTINUE,                        Continue)                        \
+  XX(101, SWITCHING_PROTOCOLS,             Switching Protocols)             \
+  XX(102, PROCESSING,                      Processing)                      \
+  XX(200, OK,                              OK)                              \
+  XX(201, CREATED,                         Created)                         \
+  XX(202, ACCEPTED,                        Accepted)                        \
+  XX(203, NON_AUTHORITATIVE_INFORMATION,   Non-Authoritative Information)   \
+  XX(204, NO_CONTENT,                      No Content)                      \
+  XX(205, RESET_CONTENT,                   Reset Content)                   \
+  XX(206, PARTIAL_CONTENT,                 Partial Content)                 \
+  XX(207, MULTI_STATUS,                    Multi-Status)                    \
+  XX(208, ALREADY_REPORTED,                Already Reported)                \
+  XX(226, IM_USED,                         IM Used)                         \
+  XX(300, MULTIPLE_CHOICES,                Multiple Choices)                \
+  XX(301, MOVED_PERMANENTLY,               Moved Permanently)               \
+  XX(302, FOUND,                           Found)                           \
+  XX(303, SEE_OTHER,                       See Other)                       \
+  XX(304, NOT_MODIFIED,                    Not Modified)                    \
+  XX(305, USE_PROXY,                       Use Proxy)                       \
+  XX(307, TEMPORARY_REDIRECT,              Temporary Redirect)              \
+  XX(308, PERMANENT_REDIRECT,              Permanent Redirect)              \
+  XX(400, BAD_REQUEST,                     Bad Request)                     \
+  XX(401, UNAUTHORIZED,                    Unauthorized)                    \
+  XX(402, PAYMENT_REQUIRED,                Payment Required)                \
+  XX(403, FORBIDDEN,                       Forbidden)                       \
+  XX(404, NOT_FOUND,                       Not Found)                       \
+  XX(405, METHOD_NOT_ALLOWED,              Method Not Allowed)              \
+  XX(406, NOT_ACCEPTABLE,                  Not Acceptable)                  \
+  XX(407, PROXY_AUTHENTICATION_REQUIRED,   Proxy Authentication Required)   \
+  XX(408, REQUEST_TIMEOUT,                 Request Timeout)                 \
+  XX(409, CONFLICT,                        Conflict)                        \
+  XX(410, GONE,                            Gone)                            \
+  XX(411, LENGTH_REQUIRED,                 Length Required)                 \
+  XX(412, PRECONDITION_FAILED,             Precondition Failed)             \
+  XX(413, PAYLOAD_TOO_LARGE,               Payload Too Large)               \
+  XX(414, URI_TOO_LONG,                    URI Too Long)                    \
+  XX(415, UNSUPPORTED_MEDIA_TYPE,          Unsupported Media Type)          \
+  XX(416, RANGE_NOT_SATISFIABLE,           Range Not Satisfiable)           \
+  XX(417, EXPECTATION_FAILED,              Expectation Failed)              \
+  XX(421, MISDIRECTED_REQUEST,             Misdirected Request)             \
+  XX(422, UNPROCESSABLE_ENTITY,            Unprocessable Entity)            \
+  XX(423, LOCKED,                          Locked)                          \
+  XX(424, FAILED_DEPENDENCY,               Failed Dependency)               \
+  XX(426, UPGRADE_REQUIRED,                Upgrade Required)                \
+  XX(428, PRECONDITION_REQUIRED,           Precondition Required)           \
+  XX(429, TOO_MANY_REQUESTS,               Too Many Requests)               \
+  XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
+  XX(451, UNAVAILABLE_FOR_LEGAL_REASONS,   Unavailable For Legal Reasons)   \
+  XX(500, INTERNAL_SERVER_ERROR,           Internal Server Error)           \
+  XX(501, NOT_IMPLEMENTED,                 Not Implemented)                 \
+  XX(502, BAD_GATEWAY,                     Bad Gateway)                     \
+  XX(503, SERVICE_UNAVAILABLE,             Service Unavailable)             \
+  XX(504, GATEWAY_TIMEOUT,                 Gateway Timeout)                 \
+  XX(505, HTTP_VERSION_NOT_SUPPORTED,      HTTP Version Not Supported)      \
+  XX(506, VARIANT_ALSO_NEGOTIATES,         Variant Also Negotiates)         \
+  XX(507, INSUFFICIENT_STORAGE,            Insufficient Storage)            \
+  XX(508, LOOP_DETECTED,                   Loop Detected)                   \
+  XX(510, NOT_EXTENDED,                    Not Extended)                    \
+  XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
+
+// HTTP_STATUS_##name
+enum http_status {
+#define XX(num, name, string) HTTP_STATUS_##name = num,
+    HTTP_STATUS_MAP(XX)
+#undef XX
+    HTTP_CUSTOM_STATUS
+};
+
+// http_mehtod
+// XX(num, name, string)
+#define HTTP_METHOD_MAP(XX)         \
+  XX(0,  DELETE,      DELETE)       \
+  XX(1,  GET,         GET)          \
+  XX(2,  HEAD,        HEAD)         \
+  XX(3,  POST,        POST)         \
+  XX(4,  PUT,         PUT)          \
+  /* pathological */                \
+  XX(5,  CONNECT,     CONNECT)      \
+  XX(6,  OPTIONS,     OPTIONS)      \
+  XX(7,  TRACE,       TRACE)        \
+  /* WebDAV */                      \
+  XX(8,  COPY,        COPY)         \
+  XX(9,  LOCK,        LOCK)         \
+  XX(10, MKCOL,       MKCOL)        \
+  XX(11, MOVE,        MOVE)         \
+  XX(12, PROPFIND,    PROPFIND)     \
+  XX(13, PROPPATCH,   PROPPATCH)    \
+  XX(14, SEARCH,      SEARCH)       \
+  XX(15, UNLOCK,      UNLOCK)       \
+  XX(16, BIND,        BIND)         \
+  XX(17, REBIND,      REBIND)       \
+  XX(18, UNBIND,      UNBIND)       \
+  XX(19, ACL,         ACL)          \
+  /* subversion */                  \
+  XX(20, REPORT,      REPORT)       \
+  XX(21, MKACTIVITY,  MKACTIVITY)   \
+  XX(22, CHECKOUT,    CHECKOUT)     \
+  XX(23, MERGE,       MERGE)        \
+  /* upnp */                        \
+  XX(24, MSEARCH,     M-SEARCH)     \
+  XX(25, NOTIFY,      NOTIFY)       \
+  XX(26, SUBSCRIBE,   SUBSCRIBE)    \
+  XX(27, UNSUBSCRIBE, UNSUBSCRIBE)  \
+  /* RFC-5789 */                    \
+  XX(28, PATCH,       PATCH)        \
+  XX(29, PURGE,       PURGE)        \
+  /* CalDAV */                      \
+  XX(30, MKCALENDAR,  MKCALENDAR)   \
+  /* RFC-2068, section 19.6.1.2 */  \
+  XX(31, LINK,        LINK)         \
+  XX(32, UNLINK,      UNLINK)       \
+  /* icecast */                     \
+  XX(33, SOURCE,      SOURCE)       \
+
+// HTTP_##name
+enum http_method {
+#define XX(num, name, string) HTTP_##name = num,
+    HTTP_METHOD_MAP(XX)
+#undef XX
+    HTTP_CUSTOM_METHOD
+};
+
+// http_content_type
+// XX(name, string, suffix)
+#define HTTP_CONTENT_TYPE_MAP(XX) \
+    XX(TEXT_PLAIN,              text/plain,               txt)          \
+    XX(TEXT_HTML,               text/html,                html)         \
+    XX(TEXT_CSS,                text/css,                 css)          \
+    XX(IMAGE_JPEG,              image/jpeg,               jpg)          \
+    XX(IMAGE_PNG,               image/png,                png)          \
+    XX(IMAGE_GIF,               image/gif,                gif)          \
+    XX(APPLICATION_JAVASCRIPT,  application/javascript,   js)           \
+    XX(APPLICATION_XML,         application/xml,          xml)          \
+    XX(APPLICATION_JSON,        application/json,         json)         \
+    XX(APPLICATION_GRPC,        application/grpc,         grpc)         \
+    XX(X_WWW_FORM_URLENCODED,   application/x-www-form-urlencoded, kv)  \
+    XX(MULTIPART_FORM_DATA,     multipart/form-data,               mp)  \
+
+enum http_content_type {
+#define XX(name, string, suffix)   name,
+    CONTENT_TYPE_NONE,
+    HTTP_CONTENT_TYPE_MAP(XX)
+    CONTENT_TYPE_UNDEFINED
+#undef XX
+};
+
+const char* http_status_str(enum http_status status);
+const char* http_method_str(enum http_method method);
+const char* http_content_type_str(enum http_content_type type);
+
+enum http_status http_status_enum(const char* str);
+enum http_method http_method_enum(const char* str);
+enum http_content_type http_content_type_enum(const char* str);
+
+const char* http_content_type_suffix(enum http_content_type type);
+const char* http_content_type_str_by_suffix(const char* suffix);
+enum http_content_type http_content_type_enum_by_suffix(const char* suffix);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // HTTP_DEF_H_

+ 100 - 0
http/server/FileCache.cpp

@@ -0,0 +1,100 @@
+#include "FileCache.h"
+
+#include "hscope.h"
+#include "md5.h" // etag
+
+#include "httpdef.h" // for http_content_type_str_by_suffix
+#include "http_page.h" //make_index_of_page
+
+file_cache_t* FileCache::Open(const char* filepath, void* ctx) {
+    file_cache_t* fc = Get(filepath);
+    bool modified = false;
+    if (fc) {
+        time_t tt;
+        time(&tt);
+        if (tt - fc->stat_time > file_stat_interval) {
+            time_t mtime = fc->st.st_mtime;
+            stat(filepath, &fc->st);
+            fc->stat_time = tt;
+            fc->stat_cnt++;
+            if (mtime != fc->st.st_mtime) {
+                modified = true;
+                fc->stat_cnt = 1;
+            }
+        }
+    }
+    if (fc == NULL || modified) {
+        int fd = open(filepath, O_RDONLY);
+        if (fd < 0) {
+            return NULL;
+        }
+        defer(close(fd);)
+        if (fc == NULL) {
+            struct stat st;
+            fstat(fd, &st);
+            if (S_ISREG(st.st_mode) ||
+                (S_ISDIR(st.st_mode) &&
+                 filepath[strlen(filepath)-1] == '/')) {
+                fc = new file_cache_t;
+                //fc->filepath = filepath;
+                fc->st = st;
+                time(&fc->open_time);
+                fc->stat_time = fc->open_time;
+                fc->stat_cnt = 1;
+                cached_files[filepath] = fc;
+            }
+            else {
+                return NULL;
+            }
+        }
+        if (S_ISREG(fc->st.st_mode)) {
+            // FILE
+            fc->resize_buf(fc->st.st_size);
+            read(fd, fc->filebuf.base, fc->filebuf.len);
+            const char* suffix = strrchr(filepath, '.');
+            if (suffix) {
+                fc->content_type = http_content_type_str_by_suffix(++suffix);
+            }
+        }
+        else if (S_ISDIR(fc->st.st_mode)) {
+            // DIR
+            std::string page;
+            make_index_of_page(filepath, page, (const char*)ctx);
+            fc->resize_buf(page.size());
+            memcpy(fc->filebuf.base, page.c_str(), page.size());
+            fc->content_type = http_content_type_str(TEXT_HTML);
+        }
+        time_t tt = fc->st.st_mtime;
+        strftime(fc->last_modified, sizeof(fc->last_modified), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tt));
+        MD5_CTX md5_ctx;
+        MD5Init(&md5_ctx);
+        MD5Update(&md5_ctx, (unsigned char*)fc->filebuf.base, fc->filebuf.len);
+        unsigned char digital[16];
+        MD5Final(digital, &md5_ctx);
+        char* md5 = fc->etag;
+        for (int i = 0; i < 16; ++i) {
+            sprintf(md5, "%02x", digital[i]);
+            md5 += 2;
+        }
+        fc->etag[32] = '\0';
+    }
+    return fc;
+}
+
+int FileCache::Close(const char* filepath) {
+    auto iter = cached_files.find(filepath);
+    if (iter != cached_files.end()) {
+        delete iter->second;
+        iter = cached_files.erase(iter);
+        return 0;
+    }
+    return -1;
+}
+
+file_cache_t* FileCache::Get(const char* filepath) {
+    auto iter = cached_files.find(filepath);
+    if (iter != cached_files.end()) {
+        return iter->second;
+    }
+    return NULL;
+}

+ 3 - 99
http/server/FileCache.h

@@ -7,16 +7,9 @@
 #include "hbuf.h"
 #include "hfile.h"
 #include "hstring.h"
-#include "hscope.h"
-#include "hdir.h"
-
-#include "md5.h"
-#include "HttpRequest.h" // for get_content_type_str_by_suffix
-#include "http_page.h"
 
 #define HTTP_HEADER_MAX_LENGTH      1024 // 1k
 
-
 typedef struct file_cache_s {
     //std::string filepath;
     struct stat st;
@@ -72,99 +65,10 @@ public:
         cached_files.clear();
     }
 
-    file_cache_t* Open(const char* filepath, void* ctx) {
-        file_cache_t* fc = Get(filepath);
-        bool modified = false;
-        if (fc) {
-            time_t tt;
-            time(&tt);
-            if (tt - fc->stat_time > file_stat_interval) {
-                time_t mtime = fc->st.st_mtime;
-                stat(filepath, &fc->st);
-                fc->stat_time = tt;
-                fc->stat_cnt++;
-                if (mtime != fc->st.st_mtime) {
-                    modified = true;
-                    fc->stat_cnt = 1;
-                }
-            }
-        }
-        if (fc == NULL || modified) {
-            int fd = open(filepath, O_RDONLY);
-            if (fd < 0) {
-                return NULL;
-            }
-            ScopeCleanup _(close, fd);
-            if (fc == NULL) {
-                struct stat st;
-                fstat(fd, &st);
-                if (S_ISREG(st.st_mode) ||
-                    (S_ISDIR(st.st_mode) &&
-                     filepath[strlen(filepath)-1] == '/')) {
-                    fc = new file_cache_t;
-                    //fc->filepath = filepath;
-                    fc->st = st;
-                    time(&fc->open_time);
-                    fc->stat_time = fc->open_time;
-                    fc->stat_cnt = 1;
-                    cached_files[filepath] = fc;
-                }
-                else {
-                    return NULL;
-                }
-            }
-            if (S_ISREG(fc->st.st_mode)) {
-                // FILE
-                fc->resize_buf(fc->st.st_size);
-                read(fd, fc->filebuf.base, fc->filebuf.len);
-                const char* suffix = strrchr(filepath, '.');
-                if (suffix) {
-                    fc->content_type = http_content_type_str_by_suffix(++suffix);
-                }
-            }
-            else if (S_ISDIR(fc->st.st_mode)) {
-                // DIR
-                std::string page;
-                make_index_of_page(filepath, page, (const char*)ctx);
-                fc->resize_buf(page.size());
-                memcpy(fc->filebuf.base, page.c_str(), page.size());
-                fc->content_type = http_content_type_str(TEXT_HTML);
-            }
-            time_t tt = fc->st.st_mtime;
-            strftime(fc->last_modified, sizeof(fc->last_modified), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tt));
-            MD5_CTX md5_ctx;
-            MD5Init(&md5_ctx);
-            MD5Update(&md5_ctx, (unsigned char*)fc->filebuf.base, fc->filebuf.len);
-            unsigned char digital[16];
-            MD5Final(digital, &md5_ctx);
-            char* md5 = fc->etag;
-            for (int i = 0; i < 16; ++i) {
-                sprintf(md5, "%02x", digital[i]);
-                md5 += 2;
-            }
-            fc->etag[32] = '\0';
-        }
-        return fc;
-    }
-
-    int Close(const char* filepath) {
-        auto iter = cached_files.find(filepath);
-        if (iter != cached_files.end()) {
-            delete iter->second;
-            iter = cached_files.erase(iter);
-            return 0;
-        }
-        return -1;
-    }
-
+    file_cache_t* Open(const char* filepath, void* ctx = NULL);
+    int Close(const char* filepath);
 protected:
-    file_cache_t* Get(const char* filepath) {
-        auto iter = cached_files.find(filepath);
-        if (iter != cached_files.end()) {
-            return iter->second;
-        }
-        return NULL;
-    }
+    file_cache_t* Get(const char* filepath);
 };
 
 #endif // HW_FILE_CACHE_H_

+ 109 - 0
http/server/HttpHandler.cpp

@@ -0,0 +1,109 @@
+#include "HttpHandler.h"
+
+#include "http_page.h"
+
+int HttpHandler::HandleRequest() {
+    // preprocessor -> api -> web -> postprocessor
+    // preprocessor
+    req.ParseUrl();
+    if (service->preprocessor) {
+        if (service->preprocessor(&req, &res) == HANDLE_DONE) {
+            return HANDLE_DONE;
+        }
+    }
+    http_api_handler api = NULL;
+    int ret = service->GetApi(req.path.c_str(), req.method, &api);
+    if (api) {
+        // api service
+        if (api(&req, &res) == HANDLE_DONE) {
+            return HANDLE_DONE;
+        }
+    }
+    else if (ret == HTTP_STATUS_METHOD_NOT_ALLOWED) {
+        // Method Not Allowed
+        res.status_code = HTTP_STATUS_METHOD_NOT_ALLOWED;
+    }
+    else if (req.method == HTTP_GET) {
+        // web service
+        // check path
+        if (*req.path.c_str() != '/' || strstr(req.path.c_str(), "/../")) {
+            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 += 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());
+        }
+
+        if (fc == NULL) {
+            // Not Found
+            res.status_code = HTTP_STATUS_NOT_FOUND;
+        }
+        else {
+            // Not Modified
+            auto iter = req.headers.find("if-not-match");
+            if (iter != req.headers.end() &&
+                strcmp(iter->second.c_str(), fc->etag) == 0) {
+                res.status_code = HTTP_STATUS_NOT_MODIFIED;
+                fc = NULL;
+            }
+            else {
+                iter = req.headers.find("if-modified-since");
+                if (iter != req.headers.end() &&
+                    strcmp(iter->second.c_str(), fc->last_modified) == 0) {
+                    res.status_code = HTTP_STATUS_NOT_MODIFIED;
+                    fc = NULL;
+                }
+            }
+        }
+    }
+    else {
+        // Not Implemented
+        res.status_code = HTTP_STATUS_NOT_IMPLEMENTED;
+    }
+
+make_http_status_page:
+    // html page
+    if (res.status_code >= 400 && res.body.size() == 0) {
+        // error page
+        if (service->error_page.size() != 0) {
+            std::string filepath = service->document_root;
+            filepath += '/';
+            filepath += service->error_page;
+            fc = files->Open(filepath.c_str(), NULL);
+        }
+        // status page
+        if (fc == NULL && res.body.size() == 0) {
+            res.content_type = TEXT_HTML;
+            make_http_status_page(res.status_code, res.body);
+        }
+    }
+
+    // file
+    if (fc) {
+        res.content = (unsigned char*)fc->filebuf.base;
+        res.content_length = fc->filebuf.len;
+        if (fc->content_type && *fc->content_type != '\0') {
+            res.headers["Content-Type"] = fc->content_type;
+            res.FillContentType();
+        }
+        res.headers["Content-Length"] = std::to_string(res.content_length);
+        res.headers["Last-Modified"] = fc->last_modified;
+        res.headers["Etag"] = fc->etag;
+    }
+
+    // postprocessor
+    if (service->postprocessor) {
+        if (service->postprocessor(&req, &res) == HANDLE_DONE) {
+            return HANDLE_DONE;
+        }
+    }
+    return HANDLE_DONE;
+}

+ 17 - 117
http/server/HttpHandler.h

@@ -1,10 +1,9 @@
 #ifndef HTTP_HANDLER_H_
 #define HTTP_HANDLER_H_
 
+#include "HttpSession.h"
 #include "HttpService.h"
-#include "HttpParser.h"
 #include "FileCache.h"
-#include "http_page.h"
 #include "hloop.h"
 
 #define HTTP_KEEPALIVE_TIMEOUT  75 // s
@@ -16,23 +15,27 @@ static inline void on_keepalive_timeout(htimer_t* timer) {
 
 class HttpHandler {
 public:
+    // peeraddr
+    char                    ip[64];
+    int                     port;
+    // for handle_request
     HttpService*            service;
     FileCache*              files;
-    char                    srcip[64];
-    int                     srcport;
-    HttpParser              parser;
+    HttpSession*            session;
     HttpRequest             req;
     HttpResponse            res;
     file_cache_t*           fc;
+    // for keepalive
     hio_t*                  io;
     htimer_t*               keepalive_timer;
 
     HttpHandler() {
         service = NULL;
         files = NULL;
+        session = NULL;
+        fc = NULL;
         io = NULL;
         keepalive_timer = NULL;
-        init();
     }
 
     ~HttpHandler() {
@@ -42,18 +45,17 @@ public:
         }
     }
 
-    void init() {
-        fc = NULL;
-        parser.parser_request_init(&req);
-    }
+    // @workflow: preprocessor -> api -> web -> postprocessor
+    // @result: HttpRequest -> HttpResponse/file_cache_t
+    int HandleRequest();
 
-    void reset() {
-        init();
-        req.reset();
-        res.reset();
+    void Reset() {
+        fc = NULL;
+        req.Reset();
+        res.Reset();
     }
 
-    void keepalive() {
+    void KeepAlive() {
         if (keepalive_timer == NULL) {
             keepalive_timer = htimer_add(hevent_loop(io), on_keepalive_timeout, HTTP_KEEPALIVE_TIMEOUT*1000, 1);
             hevent_set_userdata(keepalive_timer, io);
@@ -62,108 +64,6 @@ public:
             htimer_reset(keepalive_timer);
         }
     }
-
-    int handle_request() {
-        // preprocessor -> api -> web -> postprocessor
-        // preprocessor
-        if (service->preprocessor) {
-            if (service->preprocessor(&req, &res) == HANDLE_DONE) {
-                return HANDLE_DONE;
-            }
-        }
-        http_api_handler api = NULL;
-        int ret = service->GetApi(req.url.c_str(), req.method, &api);
-        if (api) {
-            // api service
-            if (api(&req, &res) == HANDLE_DONE) {
-                return HANDLE_DONE;
-            }
-        }
-        else if (ret == HTTP_STATUS_METHOD_NOT_ALLOWED) {
-            // Method Not Allowed
-            res.status_code = HTTP_STATUS_METHOD_NOT_ALLOWED;
-        }
-        else if (req.method == HTTP_GET) {
-            // web service
-            // check url
-            if (*req.url.c_str() != '/' || strstr(req.url.c_str(), "/../")) {
-                res.status_code = HTTP_STATUS_BAD_REQUEST;
-                goto make_http_status_page;
-            }
-            std::string filepath = service->document_root;
-            filepath += req.url.c_str();
-            if (strcmp(req.url.c_str(), "/") == 0) {
-                filepath += service->home_page;
-            }
-            if (filepath.c_str()[filepath.size()-1] != '/' ||
-                (service->index_of.size() != 0 &&
-                 req.url.size() >= service->index_of.size() &&
-                 strnicmp(req.url.c_str(), service->index_of.c_str(), service->index_of.size()) == 0)) {
-                fc = files->Open(filepath.c_str(), (void*)req.url.c_str());
-            }
-
-            if (fc == NULL) {
-                // Not Found
-                res.status_code = HTTP_STATUS_NOT_FOUND;
-            }
-            else {
-                // Not Modified
-                auto iter = req.headers.find("if-not-match");
-                if (iter != req.headers.end() &&
-                    strcmp(iter->second.c_str(), fc->etag) == 0) {
-                    res.status_code = HTTP_STATUS_NOT_MODIFIED;
-                    fc = NULL;
-                }
-                else {
-                    iter = req.headers.find("if-modified-since");
-                    if (iter != req.headers.end() &&
-                        strcmp(iter->second.c_str(), fc->last_modified) == 0) {
-                        res.status_code = HTTP_STATUS_NOT_MODIFIED;
-                        fc = NULL;
-                    }
-                }
-            }
-        }
-        else {
-            // Not Implemented
-            res.status_code = HTTP_STATUS_NOT_IMPLEMENTED;
-        }
-
-make_http_status_page:
-        // html page
-        if (res.status_code >= 400 && res.body.size() == 0) {
-            // error page
-            if (service->error_page.size() != 0) {
-                std::string filepath = service->document_root;
-                filepath += '/';
-                filepath += service->error_page;
-                fc = files->Open(filepath.c_str(), NULL);
-            }
-            // status page
-            if (fc == NULL && res.body.size() == 0) {
-                res.content_type = TEXT_HTML;
-                make_http_status_page(res.status_code, res.body);
-            }
-        }
-
-        // file
-        if (fc) {
-            if (fc->content_type && *fc->content_type != '\0') {
-                res.headers["Content-Type"] = fc->content_type;
-            }
-            res.headers["Content-Length"] = std::to_string(fc->filebuf.len);
-            res.headers["Last-Modified"] = fc->last_modified;
-            res.headers["Etag"] = fc->etag;
-        }
-
-        // postprocessor
-        if (service->postprocessor) {
-            if (service->postprocessor(&req, &res) == HANDLE_DONE) {
-                return HANDLE_DONE;
-            }
-        }
-        return HANDLE_DONE;
-    }
 };
 
 #endif // HTTP_HANDLER_H_

+ 135 - 51
http/server/http_server.cpp → http/server/HttpServer.cpp

@@ -1,16 +1,17 @@
-#include "http_server.h"
+#include "HttpServer.h"
 
 #include "h.h"
 #include "hmain.h"
 #include "hloop.h"
-#include "hbuf.h"
 
+#include "http2def.h"
 #include "FileCache.h"
-#include "HttpParser.h"
 #include "HttpHandler.h"
 
 #define RECV_BUFSIZE    8192
 #define SEND_BUFSIZE    8192
+#define MIN_HTTP_REQUEST        "GET / HTTP/1.1\r\n\r\n"
+#define MIN_HTTP_REQUEST_LEN    18
 
 static HttpService  s_default_service;
 static FileCache    s_filecache;
@@ -36,55 +37,128 @@ static void worker_init(void* userdata) {
 #endif
 }
 
-static void on_recv(hio_t* io, void* buf, int readbytes) {
+static void on_recv(hio_t* io, void* _buf, int readbytes) {
     //printf("on_recv fd=%d readbytes=%d\n", hio_fd(io), readbytes);
+    const char* buf = (const char*)_buf;
     HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
-    HttpParser* parser = &handler->parser;
-    // recv -> HttpParser -> HttpRequest -> handle_request -> HttpResponse -> send
-    int nparse = parser->execute((char*)buf, readbytes);
-    if (nparse != readbytes || parser->get_errno() != HPE_OK) {
-        hloge("[%s:%d] http parser error: %s", handler->srcip, handler->srcport, http_errno_description(parser->get_errno()));
+    // HTTP1 / HTTP2 -> HttpSession -> InitRequest
+    // recv -> FeedRecvData -> !WantRecv -> HttpRequest ->
+    // HandleRequest -> HttpResponse -> SubmitResponse -> while (GetSendData) -> send
+    if (handler->session == NULL) {
+        // base check
+        if (readbytes < MIN_HTTP_REQUEST_LEN) {
+            hloge("[%s:%d] http request too small", handler->ip, handler->port);
+            hio_close(io);
+            return;
+        }
+        for (int i = 0; i < 3; ++i) {
+            if (!IS_GRAPH(buf[i])) {
+                hloge("[%s:%d] http check failed", handler->ip, handler->port);
+                hio_close(io);
+                return;
+            }
+        }
+        http_version version = HTTP_V1;
+        if (strncmp((char*)buf, HTTP2_MAGIC, MIN(readbytes, HTTP2_MAGIC_LEN)) == 0) {
+            version = HTTP_V2;
+            handler->req.http_major = 2;
+            handler->req.http_minor = 0;
+        }
+        handler->session = HttpSession::New(HTTP_SERVER, version);
+        if (handler->session == NULL) {
+            hloge("[%s:%d] unsupported HTTP%d", handler->ip, handler->port, (int)version);
+            hio_close(io);
+            return;
+        }
+        handler->session->InitRequest(&handler->req);
+    }
+
+    HttpSession* session = handler->session;
+    HttpRequest* req = &handler->req;
+    HttpResponse* res = &handler->res;
+
+    int nfeed = session->FeedRecvData((const char*)buf, readbytes);
+    if (nfeed != readbytes) {
+        hloge("[%s:%d] http parse error: %s", handler->ip, handler->port, session->StrError(session->GetError()));
         hio_close(io);
         return;
     }
-    if (parser->get_state() == HP_MESSAGE_COMPLETE) {
-        handler->handle_request();
-        // prepare header body
-        // Server:
-        static char s_Server[64] = {'\0'};
-        if (s_Server[0] == '\0') {
-            snprintf(s_Server, sizeof(s_Server), "httpd/%s", get_compile_version());
-        }
-        handler->res.headers["Server"] = s_Server;
-        // Connection:
-        bool keepalive = true;
-        auto iter = handler->req.headers.find("connection");
-        if (iter != handler->req.headers.end()) {
-            if (stricmp(iter->second.c_str(), "keep-alive") == 0) {
-                keepalive = true;
-            }
-            else if (stricmp(iter->second.c_str(), "close") == 0) {
-                keepalive = false;
+
+    if (session->WantRecv()) {
+        return;
+    }
+
+    // Upgrade: h2
+    /*
+    auto iter_upgrade = req->headers.find("upgrade");
+    if (iter_upgrade != req->headers.end()) {
+        hlogi("[%s:%d] Upgrade: %s", handler->ip, handler->port, iter_upgrade->second.c_str());
+        // h2/h2c
+        if (strnicmp(iter_upgrade->second.c_str(), "h2", 2) == 0) {
+            hio_write(io, HTTP2_UPGRADE_RESPONSE, strlen(HTTP2_UPGRADE_RESPONSE));
+            SAFE_DELETE(handler->session);
+            session = handler->session = HttpSession::New(HTTP_SERVER, HTTP_V2);
+            if (session == NULL) {
+                hloge("[%s:%d] unsupported HTTP2", handler->ip, handler->port);
+                hio_close(io);
+                return;
             }
-        }
-        if (keepalive) {
-            handler->res.headers["Connection"] = "keep-alive";
+            HttpRequest http1_req = *req;
+            session->InitRequest(req);
+            *req = http1_req;
+            req->http_major = 2;
+            req->http_minor = 0;
+            // HTTP2_Settings: ignore
+            //session->FeedRecvData(HTTP2_Settings, );
         }
         else {
-            handler->res.headers["Connection"] = "close";
+            hio_close(io);
+            return;
+        }
+    }
+    */
+
+    int ret = handler->HandleRequest();
+    // prepare headers body
+    // Server:
+    static char s_Server[64] = {'\0'};
+    if (s_Server[0] == '\0') {
+        snprintf(s_Server, sizeof(s_Server), "httpd/%s", get_compile_version());
+    }
+    res->headers["Server"] = s_Server;
+    // Connection:
+    bool keepalive = true;
+    auto iter_keepalive = req->headers.find("connection");
+    if (iter_keepalive != req->headers.end()) {
+        if (stricmp(iter_keepalive->second.c_str(), "keep-alive") == 0) {
+            keepalive = true;
+        }
+        else if (stricmp(iter_keepalive->second.c_str(), "close") == 0) {
+            keepalive = false;
         }
-        std::string header = handler->res.dump(true, false);
+    }
+    if (keepalive) {
+        res->headers["Connection"] = "keep-alive";
+    }
+    else {
+        res->headers["Connection"] = "close";
+    }
+
+    if (req->http_major == 1) {
+        std::string header = res->Dump(true, false);
         hbuf_t sendbuf;
         bool send_in_one_packet = true;
+        int content_length = res->ContentLength();
         if (handler->fc) {
+            // no copy filebuf, more efficient
             handler->fc->prepend_header(header.c_str(), header.size());
             sendbuf = handler->fc->httpbuf;
         }
         else {
-            if (handler->res.body.size() > (1<<20)) {
+            if (content_length > (1<<20)) {
                 send_in_one_packet = false;
-            } else if (handler->res.body.size() != 0) {
-                header += handler->res.body;
+            } else if (content_length != 0) {
+                header.insert(header.size(), (const char*)res->Content(), content_length);
             }
             sendbuf.base = (char*)header.c_str();
             sendbuf.len = header.size();
@@ -93,34 +167,44 @@ static void on_recv(hio_t* io, void* buf, int readbytes) {
         hio_write(io, sendbuf.base, sendbuf.len);
         if (send_in_one_packet == false) {
             // send body
-            hio_write(io, handler->res.body.data(), handler->res.body.size());
+            hio_write(io, res->Content(), content_length);
         }
+    }
+    else if (req->http_major == 2) {
+        session->SubmitResponse(res);
+        char* data = NULL;
+        size_t len = 0;
+        while (session->GetSendData(&data, &len)) {
+            hio_write(io, data, len);
+        }
+    }
 
-        hlogi("[%s:%d][%s %s]=>[%d %s]",
-            handler->srcip, handler->srcport,
-            http_method_str(handler->req.method), handler->req.url.c_str(),
-            handler->res.status_code, http_status_str(handler->res.status_code));
+    hlogi("[%s:%d][%s %s]=>[%d %s]",
+        handler->ip, handler->port,
+        http_method_str(req->method), req->path.c_str(),
+        res->status_code, http_status_str(res->status_code));
 
-        if (keepalive) {
-            handler->reset();
-            handler->keepalive();
-        }
-        else {
-            hio_close(io);
-        }
+    if (keepalive) {
+        handler->KeepAlive();
+        handler->Reset();
+        session->InitRequest(req);
+    }
+    else {
+        hio_close(io);
     }
 }
 
 static void on_close(hio_t* io) {
     HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
     if (handler) {
+        SAFE_DELETE(handler->session);
         delete handler;
         hevent_set_userdata(io, NULL);
     }
 }
 
 static void on_accept(hio_t* io) {
-    //printf("on_accept connfd=%d\n", hio_fd(io));
+    printd("on_accept connfd=%d\n", hio_fd(io));
     /*
     char localaddrstr[INET6_ADDRSTRLEN+16] = {0};
     char peeraddrstr[INET6_ADDRSTRLEN+16] = {0};
@@ -139,8 +223,8 @@ static void on_accept(hio_t* io) {
     HttpHandler* handler = new HttpHandler;
     handler->service = (HttpService*)hevent_userdata(io);
     handler->files = &s_filecache;
-    sockaddr_ntop(hio_peeraddr(io), handler->srcip, sizeof(handler->srcip));
-    handler->srcport = sockaddr_htons(hio_peeraddr(io));
+    sockaddr_ntop(hio_peeraddr(io), handler->ip, sizeof(handler->ip));
+    handler->port = sockaddr_htons(hio_peeraddr(io));
     handler->io = io;
     hevent_set_userdata(io, handler);
 }
@@ -207,7 +291,7 @@ int http_server_run(http_server_t* server, int wait) {
         server->service = &s_default_service;
     }
     // port
-    server->listenfd = Listen(server->port);
+    server->listenfd = Listen(server->port, server->host);
     if (server->listenfd < 0) return server->listenfd;
 
 #ifdef OS_WIN

+ 6 - 3
http/server/http_server.h → http/server/HttpServer.h

@@ -3,21 +3,24 @@
 
 #include "HttpService.h"
 
-#define DEFAULT_HTTP_PORT   80
 typedef struct http_server_s {
+    char host[64];
     int port;
+    int ssl;
+    int http_version;
     HttpService* service;
     int worker_processes;
-    int ssl;
 //private:
     int listenfd;
 
 #ifdef __cplusplus
     http_server_s() {
+        strcpy(host, "0.0.0.0");
         port = DEFAULT_HTTP_PORT;
+        ssl = 0;
+        http_version = 1;
         service = NULL;
         worker_processes = 0;
-        ssl = 0;
         listenfd = -1;
     }
 #endif

+ 52 - 0
http/server/HttpService.cpp

@@ -0,0 +1,52 @@
+#include "HttpService.h"
+
+void HttpService::AddApi(const char* path, http_method method, http_api_handler handler) {
+    std::shared_ptr<http_method_handlers> method_handlers = NULL;
+    auto iter = api_handlers.find(path);
+    if (iter == api_handlers.end()) {
+        // add path
+        method_handlers = std::shared_ptr<http_method_handlers>(new http_method_handlers);
+        api_handlers[path] = method_handlers;
+    }
+    else {
+        method_handlers = iter->second;
+    }
+    for (auto iter = method_handlers->begin(); iter != method_handlers->end(); ++iter) {
+        if (iter->method == method) {
+            // update
+            iter->handler = handler;
+            return;
+        }
+    }
+    // add
+    method_handlers->push_back(http_method_handler(method, 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') {
+        return HTTP_STATUS_NOT_FOUND;
+    }
+    const char* e = s;
+    while (*e != '\0' && *e != '?') ++e;
+
+    std::string path = std::string(s, e);
+    auto iter = api_handlers.find(path);
+    if (iter == api_handlers.end()) {
+        *handler = NULL;
+        return HTTP_STATUS_NOT_FOUND;
+    }
+    auto method_handlers = iter->second;
+    for (auto iter = method_handlers->begin(); iter != method_handlers->end(); ++iter) {
+        if (iter->method == method) {
+            *handler = iter->handler;
+            return 0;
+        }
+    }
+    *handler = NULL;
+    return HTTP_STATUS_METHOD_NOT_ALLOWED;
+}
+

+ 3 - 50
http/server/HttpService.h

@@ -7,7 +7,7 @@
 #include <list>
 #include <memory>
 
-#include "HttpRequest.h"
+#include "HttpPayload.h"
 
 #define DEFAULT_BASE_URL        "/v1/api"
 #define DEFAULT_DOCUMENT_ROOT   "/var/www/html"
@@ -51,55 +51,8 @@ struct HttpService {
         home_page = DEFAULT_HOME_PAGE;
     }
 
-    void AddApi(const char* path, http_method method, http_api_handler handler) {
-        std::shared_ptr<http_method_handlers> method_handlers = NULL;
-        auto iter = api_handlers.find(path);
-        if (iter == api_handlers.end()) {
-            // add path
-            method_handlers = std::shared_ptr<http_method_handlers>(new http_method_handlers);
-            api_handlers[path] = method_handlers;
-        }
-        else {
-            method_handlers = iter->second;
-        }
-        for (auto iter = method_handlers->begin(); iter != method_handlers->end(); ++iter) {
-            if (iter->method == method) {
-                // update
-                iter->handler = handler;
-                return;
-            }
-        }
-        // add
-        method_handlers->push_back(http_method_handler(method, handler));
-    }
-
-    int 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') {
-            return HTTP_STATUS_NOT_FOUND;
-        }
-        const char* e = s;
-        while (*e != '\0' && *e != '?') ++e;
-
-        std::string path = std::string(s, e);
-        auto iter = api_handlers.find(path);
-        if (iter == api_handlers.end()) {
-            *handler = NULL;
-            return HTTP_STATUS_NOT_FOUND;
-        }
-        auto method_handlers = iter->second;
-        for (auto iter = method_handlers->begin(); iter != method_handlers->end(); ++iter) {
-            if (iter->method == method) {
-                *handler = iter->handler;
-                return 0;
-            }
-        }
-        *handler = NULL;
-        return HTTP_STATUS_METHOD_NOT_ALLOWED;
-    }
+    void AddApi(const char* path, http_method method, http_api_handler handler);
+    int GetApi(const char* url, http_method method, http_api_handler* handler);
 };
 
 #endif // HTTP_SERVICE_H_