Browse Source

add event html

ithewei 6 years ago
parent
commit
8aa49422f7

+ 22 - 0
base/hstring.cpp

@@ -8,6 +8,28 @@
 #include <iostream>
 #include <sstream>
 
+char* strupper(char* str) {
+    char* p = str;
+    while (*p != '\0') {
+        if (*p >= 'a' && *p <= 'z') {
+            *p &= ~0x20;
+        }
+        ++p;
+    }
+    return str;
+}
+
+char* strlower(char* str) {
+    char* p = str;
+    while (*p != '\0') {
+        if (*p >= 'A' && *p <= 'Z') {
+            *p |= 0x20;
+        }
+        ++p;
+    }
+    return str;
+}
+
 int vscprintf(const char* fmt, va_list ap) {
     return vsnprintf(NULL, 0, fmt, ap);
 }

+ 3 - 0
base/hstring.h

@@ -10,6 +10,9 @@ using std::string;
 
 typedef std::vector<string> StringList;
 
+char* strupper(char* str);
+char* strlower(char* str);
+
 string asprintf(const char* fmt, ...);
 StringList split(const string& str, char delim);
 string trim(const string& str, const char* chars = SPACE_CHARS);

+ 20 - 0
etc/httpd.conf

@@ -0,0 +1,20 @@
+# [root]
+
+# logfile = logs/test.log
+# loglevel = [VERBOSE,DEBUG,INFO,WARN,ERROR,FATAL,SILENT]
+loglevel = INFO
+log_remain_days = 3
+
+# worker_processes = 4
+# auto = ncpu
+worker_processes = auto
+
+# http server
+port = 8080
+base_url = /v1/api
+document_root = html
+home_page = index.html
+error_page = error.html
+
+file_stat_interval = 10 # s
+file_cached_time = 60 # s

+ 97 - 0
event/client.cpp.demo

@@ -0,0 +1,97 @@
+#include "hloop.h"
+
+#include "htime.h"
+#include "hsocket.h"
+
+#define RECV_BUFSIZE    8192
+
+void on_timer(htimer_t* timer, void* userdata) {
+    static int cnt = 0;
+    printf("on_timer timer_id=%d time=%luus cnt=%d\n", timer->timer_id, timer->loop->cur_time, ++cnt);
+}
+
+void on_idle(hidle_t* idle, void* userdata) {
+    static int cnt = 0;
+    printf("on_idle idle_id=%d cnt=%d\n", idle->idle_id, ++cnt);
+}
+
+void on_read(hevent_t* event, void* userdata) {
+    printf("on_read fd=%d\n", event->fd);
+    char recvbuf[RECV_BUFSIZE] = {0};
+    int nrecv;
+recv:
+    memset(recvbuf, 0, sizeof(recvbuf));
+    nrecv = recv(event->fd, recvbuf, sizeof(recvbuf), 0);
+    printf("recv retval=%d\n", nrecv);
+    if (nrecv < 0) {
+        if (sockerrno != NIO_EAGAIN) {
+            //goto recv_done;
+            return;
+        }
+        else {
+            perror("recv");
+            goto recv_error;
+        }
+    }
+    if (nrecv == 0) {
+        goto disconnect;
+    }
+    printf("> %s\n", recvbuf);
+    if (nrecv == sizeof(recvbuf)) {
+        goto recv;
+    }
+
+recv_error:
+disconnect:
+    printf("closesocket fd=%d\n", event->fd);
+    closesocket(event->fd);
+    hevent_del(event);
+}
+
+void on_connect(hevent_t* event, void* userdata) {
+    printf("on_connect connfd=%d\n", event->fd);
+    struct sockaddr_in localaddr,peeraddr;
+    socklen_t addrlen = sizeof(struct sockaddr_in);
+    int ret = getpeername(event->fd, (struct sockaddr*)&peeraddr, &addrlen);
+    if (ret < 0) {
+        printf("connect failed: %s: %d\n", strerror(sockerrno), sockerrno);
+        closesocket(event->fd);
+        return;
+    }
+    addrlen = sizeof(struct sockaddr_in);
+    getsockname(event->fd, (struct sockaddr*)&localaddr, &addrlen);
+    printf("connect connfd=%d [%s:%d] => [%s:%d]\n", event->fd,
+            inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port),
+            inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
+
+    hevent_read(event->loop, event->fd, on_read, NULL);
+
+    static const char* http_request = "GET / HTTP/1.1\r\n\r\n";
+    int nsend = send(event->fd, http_request, strlen(http_request), 0);
+    printf("send retval=%d\n", nsend);
+    printf("< %s\n", http_request);
+}
+
+int main(int argc, char** argv) {
+    if (argc < 3) {
+        printf("Usage: cmd host port\n");
+        return -10;
+    }
+    const char* host = argv[1];
+    int port = atoi(argv[2]);
+
+    int connfd = Connect(host, port, 1);
+    printf("connfd=%d\n", connfd);
+    if (connfd < 0) {
+        return connfd;
+    }
+
+    hloop_t loop;
+    hloop_init(&loop);
+    //hidle_add(&loop, on_idle, NULL);
+    htimer_add(&loop, on_timer, NULL, 1000, INFINITE);
+    hevent_connect(&loop, connfd, on_connect, NULL);
+    hloop_run(&loop);
+
+    return 0;
+}

+ 129 - 0
event/epoll.cpp

@@ -0,0 +1,129 @@
+#include "hevent.h"
+
+#ifdef EVENT_EPOLL
+#include "hplatform.h"
+#ifdef OS_LINUX
+#include <sys/epoll.h>
+#endif
+
+#include "hdef.h"
+
+#define INIT_EVENTS_NUM    64
+
+typedef struct epoll_ctx_s {
+    int                 epfd;
+    int                 capacity;
+    int                 nevents;
+    struct epoll_event* events;
+} epoll_ctx_t;
+
+static void epoll_ctx_resize(epoll_ctx_t* epoll_ctx, int size) {
+    int bytes = sizeof(struct epoll_event) * size;
+    epoll_ctx->events = (struct epoll_event*)realloc(epoll_ctx->events, bytes);
+    epoll_ctx->capacity = size;
+}
+
+int _event_init(hloop_t* loop) {
+    if (loop->event_ctx) return 0;
+    epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)malloc(sizeof(epoll_ctx_t));
+    epoll_ctx->epfd = epoll_create(INIT_EVENTS_NUM);
+    epoll_ctx->capacity = INIT_EVENTS_NUM;
+    epoll_ctx->nevents = 0;
+    int bytes = sizeof(struct epoll_event) * epoll_ctx->capacity;
+    epoll_ctx->events = (struct epoll_event*)malloc(bytes);
+    memset(epoll_ctx->events, 0, bytes);
+    loop->event_ctx = epoll_ctx;
+    return 0;
+}
+
+int _event_cleanup(hloop_t* loop) {
+    if (loop->event_ctx == NULL) return 0;
+    epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)loop->event_ctx;
+    close(epoll_ctx->epfd);
+    SAFE_FREE(epoll_ctx->events);
+    SAFE_FREE(loop->event_ctx);
+    return 0;
+}
+
+int _add_event(hevent_t* event, int type) {
+    hloop_t* loop = event->loop;
+    if (loop->event_ctx == NULL) {
+        hloop_event_init(loop);
+    }
+    epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)loop->event_ctx;
+    int op = event->events == 0 ? EPOLL_CTL_ADD : EPOLL_CTL_MOD;
+    if (type & READ_EVENT) {
+        event->events |= EPOLLIN;
+    }
+    if (type & WRITE_EVENT) {
+        event->events |= EPOLLOUT;
+    }
+    struct epoll_event ee;
+    ee.events = event->events;
+    ee.data.fd = event->fd;
+    epoll_ctl(epoll_ctx->epfd, op, event->fd, &ee);
+    if (op == EPOLL_CTL_ADD) {
+        if (epoll_ctx->nevents == epoll_ctx->capacity) {
+            epoll_ctx_resize(epoll_ctx, epoll_ctx->capacity*2);
+        }
+        epoll_ctx->nevents++;
+    }
+    return 0;
+}
+
+int _del_event(hevent_t* event, int type) {
+    hloop_t* loop = event->loop;
+    epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)loop->event_ctx;
+    if (epoll_ctx == NULL) return 0;
+
+    if (event->events == 0) return 0;
+    if (type & READ_EVENT) {
+        event->events &= ~EPOLLIN;
+    }
+    if (type & WRITE_EVENT) {
+        event->events &= ~EPOLLOUT;
+    }
+    int op = event->events == 0 ? EPOLL_CTL_DEL : EPOLL_CTL_MOD;
+    struct epoll_event ee;
+    ee.events = event->events;
+    ee.data.fd = event->fd;
+    epoll_ctl(epoll_ctx->epfd, op, event->fd, &ee);
+    if (op == EPOLL_CTL_DEL) {
+        epoll_ctx->nevents--;
+    }
+    return 0;
+}
+
+int _handle_events(hloop_t* loop, int timeout) {
+    epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)loop->event_ctx;
+    if (epoll_ctx == NULL)  return 0;
+    if (epoll_ctx->nevents == 0) return 0;
+    int nepoll = epoll_wait(epoll_ctx->epfd, epoll_ctx->events, epoll_ctx->nevents, timeout);
+    if (nepoll < 0) {
+        perror("epoll");
+        return nepoll;
+    }
+    if (nepoll == 0) return 0;
+    int nevent = 0;
+    for (int i = 0; i < epoll_ctx->nevents; ++i) {
+        if (nevent == nepoll) break;
+        int fd = epoll_ctx->events[i].data.fd;
+        uint32_t revents = epoll_ctx->events[i].events;
+        if (revents) {
+            ++nevent;
+            auto iter = loop->events.find(fd);
+            if (iter == loop->events.end()) {
+                continue;
+            }
+            hevent_t* event = iter->second;
+            if (revents & EPOLLIN) {
+                _on_read(event);
+            }
+            if (revents & EPOLLOUT) {
+                _on_write(event);
+            }
+        }
+    }
+    return nevent;
+}
+#endif

+ 85 - 0
event/hevent.cpp

@@ -0,0 +1,85 @@
+#include "hevent.h"
+
+#include "hdef.h"
+#include "hlog.h"
+#include "hsocket.h"
+
+int _on_read(hevent_t* event) {
+    event->readable = 1;
+    //if (event->accept) {
+    //}
+    if (event->read_cb) {
+        event->read_cb(event, event->read_userdata);
+    }
+    event->readable = 0;
+    return 0;
+}
+
+int _on_write(hevent_t* event) {
+    _del_event(event, WRITE_EVENT);
+    event->writeable = 1;
+    //if (event->connect) {
+    //}
+    if (event->write_cb) {
+        event->write_cb(event, event->read_userdata);
+    }
+    event->writeable = 0;
+    return 0;
+}
+
+int hloop_event_init(hloop_t* loop) {
+    return _event_init(loop);
+}
+
+int hloop_event_cleanup(hloop_t* loop) {
+    return _event_cleanup(loop);
+}
+
+int hloop_add_event(hevent_t* event, int type) {
+    return _add_event(event, type);
+}
+
+int hloop_del_event(hevent_t* event, int type) {
+    return _del_event(event, type);
+}
+
+static void remove_bad_fds(hloop_t* loop) {
+    int error = 0;
+    socklen_t optlen = sizeof(int);
+    int ret = 0;
+    auto iter = loop->events.begin();
+    while (iter != loop->events.end()) {
+        int fd = iter->first;
+        ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&error, &optlen);
+        if (ret < 0 || error != 0) {
+            hloge("getsockopt fd=%d retval=%d SO_ERROR=%d", fd, ret, error);
+            hloop_del_event(iter->second);
+            iter = loop->events.erase(iter);
+            continue;
+        }
+        ++iter;
+    }
+}
+
+int hloop_handle_events(hloop_t* loop, int timeout) {
+    /*
+    // remove destroy events
+    hevent_t* event = NULL;
+    auto iter = loop->events.begin();
+    while (iter != loop->events.end()) {
+        event = iter->second;
+        if (event->destroy) {
+            SAFE_FREE(event);
+            iter = loop->events.erase(iter);
+            continue;
+        }
+        ++iter;
+    }
+    */
+    int nevent = _handle_events(loop, timeout);
+    if (nevent < 0) {
+        printf("handle_events error=%d\n", -nevent);
+        remove_bad_fds(loop);
+    }
+    return nevent;
+}

+ 38 - 0
event/hevent.h

@@ -0,0 +1,38 @@
+#ifndef __HW_EVNET_H_
+#define __HW_EVNET_H_
+
+#include "hloop.h"
+#include "hplatform.h"
+
+#define READ_EVENT  0x0001
+#define WRITE_EVENT 0x0004
+
+int hloop_event_init(hloop_t* loop);
+int hloop_event_cleanup(hloop_t* loop);
+int hloop_add_event(hevent_t* event, int type = READ_EVENT|WRITE_EVENT);
+int hloop_del_event(hevent_t* event, int type = READ_EVENT|WRITE_EVENT);
+int hloop_handle_events(hloop_t* loop, int timeout = INFINITE);
+
+int _on_read(hevent_t* event);
+int _on_write(hevent_t* event);
+
+#if !defined(EVENT_SELECT) && !defined(EVENT_POLL) && !defined(EVENT_EPOLL) && \
+    !defined(EVENT_IOCP) && !defined(EVENT_KQUEUE) && !defined(EVENT_NOEVENT)
+#ifdef OS_WIN
+//#define EVENT_IOCP
+#define EVENT_SELECT
+#elif defined(OS_LINUX)
+#define EVENT_EPOLL
+#elif defined(OS_BSD)
+#define EVENT_KQUEUE
+#else
+#define EVENT_SELECT
+#endif
+#endif
+int _event_init(hloop_t* loop);
+int _event_cleanup(hloop_t* loop);
+int _add_event(hevent_t* event, int type);
+int _del_event(hevent_t* event, int type);
+int _handle_events(hloop_t* loop, int timeout);
+
+#endif // __HW_EVNET_H_

+ 286 - 0
event/hloop.cpp

@@ -0,0 +1,286 @@
+#include "hloop.h"
+
+#include "hdef.h"
+#include "htime.h"
+#include "hevent.h"
+
+static void hloop_update_time(hloop_t* loop) {
+    loop->cur_time = gethrtime();
+}
+
+int hloop_init(hloop_t* loop) {
+    loop->status = HLOOP_STATUS_STOP;
+    loop->timer_counter = 0;
+    loop->idle_counter = 0;
+    loop->min_timer_timeout = INFINITE;
+    loop->event_ctx = NULL;
+    // hloop_event_init when add_event first
+    // hloop_event_init(loop);
+    return 0;
+}
+
+void hloop_cleanup(hloop_t* loop) {
+    for (auto& pair : loop->timers) {
+        SAFE_FREE(pair.second);
+    }
+    loop->timers.clear();
+    for (auto& pair : loop->idles) {
+        SAFE_FREE(pair.second);
+    }
+    loop->idles.clear();
+    for (auto& pair : loop->events) {
+        hevent_t* event = pair.second;
+        hloop_del_event(event);
+        SAFE_FREE(event);
+    }
+    loop->events.clear();
+    hloop_event_cleanup(loop);
+}
+
+int hloop_handle_timers(hloop_t* loop) {
+    int ntimer = 0;
+    auto iter = loop->timers.begin();
+    while (iter != loop->timers.end()) {
+        htimer_t* timer = iter->second;
+        if (timer->destroy) goto destroy;
+        if (timer->disable) goto next;
+        if (timer->repeat == 0) goto destroy;
+        if (timer->next_timeout < loop->cur_time) {
+            ++ntimer;
+            if (timer->cb) {
+                timer->cb(timer, timer->userdata);
+            }
+            timer->next_timeout += timer->timeout*1000;
+            if (timer->repeat != INFINITE) {
+                --timer->repeat;
+            }
+        }
+next:
+        ++iter;
+        continue;
+destroy:
+        free(timer);
+        iter = loop->timers.erase(iter);
+    }
+    return ntimer;
+}
+
+int hloop_handle_idles(hloop_t* loop) {
+    int nidle = 0;
+    auto iter = loop->idles.begin();
+    while (iter != loop->idles.end()) {
+        hidle_t* idle = iter->second;
+        if (idle->destroy)  goto destroy;
+        if (idle->disable)  goto next;
+        if (idle->repeat == 0) goto destroy;
+        ++nidle;
+        if (idle->cb) {
+            idle->cb(idle, idle->userdata);
+        }
+        if (idle->repeat != INFINITE) {
+            --idle->repeat;
+        }
+next:
+        ++iter;
+        continue;
+destroy:
+        free(idle);
+        iter = loop->idles.erase(iter);
+    }
+    return nidle;
+}
+
+#define PAUSE_SLEEP_TIME        10      // ms
+#define MIN_EVENT_TIMEOUT       1       // ms
+#define MAX_EVENT_TIMEOUT       1000    // ms
+int hloop_run(hloop_t* loop) {
+    int ntimer, nevent, nidle;
+    int event_timeout;
+
+    loop->start_time = gethrtime();
+    loop->status = HLOOP_STATUS_RUNNING;
+    loop->loop_cnt = 0;
+    while (loop->status != HLOOP_STATUS_STOP) {
+        hloop_update_time(loop);
+        if (loop->status == HLOOP_STATUS_PAUSE) {
+            msleep(PAUSE_SLEEP_TIME);
+            continue;
+        }
+        ++loop->loop_cnt;
+        // timers -> events -> idles
+        ntimer = nevent = nidle = 0;
+        event_timeout = INFINITE;
+        if (loop->timers.size() != 0) {
+            ntimer = hloop_handle_timers(loop);
+            event_timeout = MAX(MIN_EVENT_TIMEOUT, loop->min_timer_timeout/10);
+        }
+        if (loop->events.size() == 0 || loop->idles.size() != 0) {
+            event_timeout = MIN(event_timeout, MAX_EVENT_TIMEOUT);
+        }
+        if (loop->events.size() != 0) {
+            nevent = hloop_handle_events(loop, event_timeout);
+        }
+        else {
+            msleep(event_timeout);
+        }
+        if (ntimer == 0 && nevent == 0 && loop->idles.size() != 0) {
+            nidle = hloop_handle_idles(loop);
+        }
+        //printf("loop_cnt=%lu ntimer=%d nevent=%d nidle=%d\n", loop->loop_cnt, ntimer, nevent, nidle);
+    }
+    loop->status = HLOOP_STATUS_STOP;
+    loop->end_time = gethrtime();
+    hloop_cleanup(loop);
+    return 0;
+}
+
+int hloop_stop(hloop_t* loop) {
+    loop->status = HLOOP_STATUS_STOP;
+    return 0;
+}
+
+int hloop_pause(hloop_t* loop) {
+    if (loop->status == HLOOP_STATUS_RUNNING) {
+        loop->status = HLOOP_STATUS_PAUSE;
+    }
+    return 0;
+}
+
+int hloop_resume(hloop_t* loop) {
+    if (loop->status == HLOOP_STATUS_PAUSE) {
+        loop->status = HLOOP_STATUS_RUNNING;
+    }
+    return 0;
+}
+
+htimer_t* htimer_add(hloop_t* loop, htimer_cb cb, void* userdata, uint64_t timeout, uint32_t repeat) {
+    htimer_t* timer = (htimer_t*)malloc(sizeof(htimer_t));
+    memset(timer, 0, sizeof(htimer_t));
+    timer->loop = loop;
+    timer->timer_id = ++loop->timer_counter;
+    timer->cb = cb;
+    timer->userdata = userdata;
+    timer->timeout = timeout;
+    timer->repeat = repeat;
+    timer->next_timeout = gethrtime() + timeout*1000;
+    loop->timers[timer->timer_id] = timer;
+    loop->min_timer_timeout = MIN(timeout, loop->min_timer_timeout);
+    return timer;
+}
+
+void htimer_del(htimer_t* timer) {
+    timer->destroy = 1;
+}
+
+void htimer_del(hloop_t* loop, uint32_t timer_id) {
+    auto iter = loop->timers.find(timer_id);
+    if (iter != loop->timers.end()) {
+        htimer_t* timer = iter->second;
+        htimer_del(timer);
+    }
+}
+
+hidle_t* hidle_add(hloop_t* loop, hidle_cb cb, void* userdata, uint32_t repeat) {
+    hidle_t* idle = (hidle_t*)malloc(sizeof(hidle_t));
+    memset(idle, 0, sizeof(hidle_t));
+    idle->loop = loop;
+    idle->idle_id = ++loop->idle_counter;
+    idle->cb = cb;
+    idle->userdata = userdata;
+    idle->repeat = repeat;
+    loop->idles[idle->idle_id] = idle;
+    return idle;
+}
+
+void hidle_del(hidle_t* idle) {
+    idle->destroy = 1;
+}
+
+void hidle_del(hloop_t* loop, uint32_t idle_id) {
+    auto iter = loop->idles.find(idle_id);
+    if (iter != loop->idles.end()) {
+        hidle_t* idle = iter->second;
+        hidle_del(idle);
+    }
+}
+
+hevent_t* hevent_add(hloop_t* loop, int fd) {
+#ifdef EVENT_SELECT
+    if (loop->events.size() >= FD_SETSIZE) return NULL;
+#endif
+    hevent_t* event = (hevent_t*)malloc(sizeof(hevent_t));
+    memset(event, 0, sizeof(hevent_t));
+    event->loop = loop;
+    event->fd = fd;
+    event->event_index = -1;
+    loop->events[fd] = event;
+    return event;
+}
+
+hevent_t* hevent_get(hloop_t* loop, int fd) {
+    auto iter = loop->events.find(fd);
+    if (iter != loop->events.end()) {
+        return iter->second;
+    }
+    return NULL;
+}
+
+hevent_t* hevent_get_or_add(hloop_t* loop, int fd) {
+    hevent_t* event = hevent_get(loop, fd);
+    if (event)  {
+        event->destroy = 0;
+        event->disable = 0;
+        return event;
+    }
+    return hevent_add(loop, fd);
+}
+
+void hevent_del(hevent_t* event) {
+    event->destroy = 1;
+    hloop_del_event(event, READ_EVENT|WRITE_EVENT);
+}
+
+void hevent_del(hloop_t* loop, int fd) {
+    auto iter = loop->events.find(fd);
+    if (iter != loop->events.end()) {
+        hevent_del(iter->second);
+    }
+}
+
+hevent_t* hevent_read(hloop_t* loop, int fd, hevent_cb cb, void* userdata) {
+    hevent_t* event = hevent_get_or_add(loop, fd);
+    if (event == NULL) return NULL;
+    event->read_cb = cb;
+    event->read_userdata = userdata;
+    hloop_add_event(event, READ_EVENT);
+    return event;
+}
+
+hevent_t* hevent_write(hloop_t* loop, int fd, hevent_cb cb, void* userdata) {
+    hevent_t* event = hevent_get_or_add(loop, fd);
+    if (event == NULL) return NULL;
+    event->write_cb = cb;
+    event->write_userdata = userdata;
+    hloop_add_event(event, WRITE_EVENT);
+    return event;
+}
+
+#include "hsocket.h"
+hevent_t* hevent_accept(hloop_t* loop, int listenfd, hevent_cb cb, void* userdata) {
+    hevent_t* event = hevent_read(loop, listenfd, cb, userdata);
+    if (event) {
+        nonblocking(listenfd);
+        event->accept = 1;
+    }
+    return event;
+}
+
+hevent_t* hevent_connect(hloop_t* loop, int connfd, hevent_cb cb, void* userdata) {
+    hevent_t* event = hevent_write(loop, connfd, cb, userdata);
+    if (event) {
+        nonblocking(connfd);
+        event->connect = 1;
+    }
+    return event;
+}
+

+ 121 - 0
event/hloop.h

@@ -0,0 +1,121 @@
+#ifndef HW_LOOP_H_
+#define HW_LOOP_H_
+
+#include <map>
+
+#ifndef INFINITE
+#define INFINITE    (uint32_t)-1
+#endif
+
+typedef struct hloop_s  hloop_t;
+typedef struct htimer_s htimer_t;
+typedef struct hidle_s  hidle_t;
+typedef struct hevent_s hevent_t;
+
+typedef void (*htimer_cb)(htimer_t* timer, void* userdata);
+typedef void (*hidle_cb)(hidle_t* idle, void* userdata);
+typedef void (*hevent_cb)(hevent_t* event, void* userdata);
+
+typedef enum {
+    HLOOP_STATUS_STOP,
+    HLOOP_STATUS_RUNNING,
+    HLOOP_STATUS_PAUSE
+} hloop_status_e;
+
+struct hloop_s {
+    hloop_status_e status;
+    uint64_t    start_time;
+    uint64_t    end_time;
+    uint64_t    cur_time;
+    uint64_t    loop_cnt;
+    // timers
+    uint32_t                    timer_counter;
+    // timer_id => timer
+    std::map<int, htimer_t*>    timers;
+    uint32_t                    min_timer_timeout;
+    // idles
+    uint32_t                    idle_counter;
+    // hidle_id => idle
+    std::map<int, hidle_t*>     idles;
+    // events
+    // fd => event
+    std::map<int, hevent_t*>    events;
+    void*                       event_ctx; // private
+};
+
+struct htimer_s {
+    hloop_t*    loop;
+    uint32_t    timer_id;
+    uint32_t    timeout;
+    uint32_t    repeat;
+    htimer_cb   cb;
+    void*       userdata;
+//private:
+    unsigned    destroy     :1;
+    unsigned    disable     :1;
+    uint64_t    next_timeout;
+};
+
+struct hidle_s {
+    hloop_t*    loop;
+    uint32_t    idle_id;
+    uint32_t    repeat;
+    hidle_cb    cb;
+    void*       userdata;
+//private:
+    unsigned    destroy     :1;
+    unsigned    disable     :1;
+};
+
+typedef union {
+    void*       ptr;
+    uint32_t    u32;
+    uint64_t    u64;
+} hevent_data_e;
+
+struct hevent_s {
+    hloop_t*    loop;
+    int         fd;
+    hevent_cb   read_cb;
+    void*       read_userdata;
+    hevent_cb   write_cb;
+    void*       write_userdata;
+//private:
+    unsigned    destroy     :1;
+    unsigned    disable     :1;
+    unsigned    accept      :1;
+    unsigned    connect     :1;
+    unsigned    readable    :1;
+    unsigned    writeable   :1;
+    int         event_index; // for poll
+    int         events;      // for epoll
+};
+
+// loop
+int hloop_init(hloop_t* loop);
+//void hloop_cleanup(hloop_t* loop);
+int hloop_run(hloop_t* loop);
+int hloop_stop(hloop_t* loop);
+int hloop_pause(hloop_t* loop);
+int hloop_resume(hloop_t* loop);
+
+// timer
+// @param timeout: unit(ms)
+htimer_t*   htimer_add(hloop_t* loop, htimer_cb cb, void* userdata, uint64_t timeout, uint32_t repeat = INFINITE);
+void        htimer_del(hloop_t* loop, uint32_t timer_id);
+void        htimer_del(htimer_t* timer);
+
+// idle
+hidle_t*    hidle_add(hloop_t* loop, hidle_cb cb, void* userdata, uint32_t repeat = INFINITE);
+void        hidle_del(hloop_t* loop, uint32_t idle_id);
+void        hidle_del(hidle_t* idle);
+
+// event
+hevent_t* hevent_accept(hloop_t* loop, int listenfd, hevent_cb on_accept, void* userdata);
+hevent_t* hevent_connect(hloop_t* loop, int connfd, hevent_cb on_connect, void* userdata);
+hevent_t* hevent_read(hloop_t* loop, int fd, hevent_cb on_readable, void* userdata);
+hevent_t* hevent_write(hloop_t* loop, int fd, hevent_cb on_writeable, void* userdata);
+void      hevent_del(hloop_t* loop, int fd);
+void      hevent_del(hevent_t* event);
+
+#endif // HW_LOOP_H_

+ 25 - 0
event/iocp.cpp

@@ -0,0 +1,25 @@
+#include "hevent.h"
+
+#ifdef EVENT_IOCP
+int _event_init(hloop_t* loop) {
+    loop->event_ctx = NULL;
+    return 0;
+}
+
+int _event_cleanup(hloop_t* loop) {
+    loop->event_ctx = NULL;
+    return 0;
+}
+
+int _add_event(hevent_t* event, int type) {
+    return 0;
+}
+
+int _del_event(hevent_t* event, int type) {
+    return 0;
+}
+
+int _handle_events(hloop_t* loop, int timeout) {
+    return 0;
+}
+#endif

+ 25 - 0
event/kqueue.cpp

@@ -0,0 +1,25 @@
+#include "hevent.h"
+
+#ifdef EVENT_KQUEUE
+int _event_init(hloop_t* loop) {
+    loop->event_ctx = NULL;
+    return 0;
+}
+
+int _event_cleanup(hloop_t* loop) {
+    loop->event_ctx = NULL;
+    return 0;
+}
+
+int _add_event(hevent_t* event, int type) {
+    return 0;
+}
+
+int _del_event(hevent_t* event, int type) {
+    return 0;
+}
+
+int _handle_events(hloop_t* loop, int timeout) {
+    return 0;
+}
+#endif

+ 25 - 0
event/noevent.cpp

@@ -0,0 +1,25 @@
+#include "hevent.h"
+
+#ifdef EVENT_NOEVENT
+int _event_init(hloop_t* loop) {
+    loop->event_ctx = NULL;
+    return 0;
+}
+
+int _event_cleanup(hloop_t* loop) {
+    loop->event_ctx = NULL;
+    return 0;
+}
+
+int _add_event(hevent_t* event, int type) {
+    return 0;
+}
+
+int _del_event(hevent_t* event, int type) {
+    return 0;
+}
+
+int _handle_events(hloop_t* loop, int timeout) {
+    return 0;
+}
+#endif

+ 132 - 0
event/poll.cpp

@@ -0,0 +1,132 @@
+#include "hevent.h"
+
+#ifdef EVENT_POLL
+#include "hplatform.h"
+#ifdef OS_LINUX
+#include <sys/poll.h>
+#endif
+
+#include "hdef.h"
+
+#define INIT_FDS_NUM    64
+
+typedef struct poll_ctx_s {
+    int            capacity;
+    int            nfds;
+    struct pollfd* fds;
+} poll_ctx_t;
+
+static void poll_ctx_resize(poll_ctx_t* poll_ctx, int size) {
+    int bytes = sizeof(struct pollfd) * size;
+    poll_ctx->fds = (struct pollfd*)realloc(poll_ctx->fds, bytes);
+    poll_ctx->capacity = size;
+}
+
+int _event_init(hloop_t* loop) {
+    if (loop->event_ctx)   return 0;
+    poll_ctx_t* poll_ctx = (poll_ctx_t*)malloc(sizeof(poll_ctx_t));
+    poll_ctx->capacity = INIT_FDS_NUM;
+    poll_ctx->nfds = 0;
+    int bytes = sizeof(struct pollfd) * poll_ctx->capacity;
+    poll_ctx->fds = (struct pollfd*)malloc(bytes);
+    memset(poll_ctx->fds, 0, bytes);
+    loop->event_ctx = poll_ctx;
+    return 0;
+}
+
+int _event_cleanup(hloop_t* loop) {
+    if (loop->event_ctx == NULL)   return 0;
+    poll_ctx_t* poll_ctx = (poll_ctx_t*)loop->event_ctx;
+    SAFE_FREE(poll_ctx->fds);
+    SAFE_FREE(loop->event_ctx);
+    return 0;
+}
+
+int _add_event(hevent_t* event, int type) {
+    hloop_t* loop = event->loop;
+    if (loop->event_ctx == NULL) {
+        hloop_event_init(loop);
+    }
+    poll_ctx_t* poll_ctx = (poll_ctx_t*)loop->event_ctx;
+    int idx = event->event_index;
+    if (idx < 0) {
+        event->event_index = idx = poll_ctx->nfds;
+        poll_ctx->nfds++;
+        if (idx == poll_ctx->capacity) {
+            poll_ctx_resize(poll_ctx, poll_ctx->capacity*2);
+        }
+        poll_ctx->fds[idx].fd = event->fd;
+        poll_ctx->fds[idx].events = 0;
+        poll_ctx->fds[idx].revents = 0;
+    }
+    assert(poll_ctx->fds[idx].fd == event->fd);
+    if (type & READ_EVENT) {
+        poll_ctx->fds[idx].events |= POLLIN;
+    }
+    if (type & WRITE_EVENT) {
+        poll_ctx->fds[idx].events |= POLLOUT;
+    }
+    return 0;
+}
+
+int _del_event(hevent_t* event, int type) {
+    hloop_t* loop = event->loop;
+    poll_ctx_t* poll_ctx = (poll_ctx_t*)loop->event_ctx;
+    if (poll_ctx == NULL)  return 0;
+
+    int idx = event->event_index;
+    if (idx < 0) return 0;
+    assert(poll_ctx->fds[idx].fd == event->fd);
+    if (type & READ_EVENT) {
+        poll_ctx->fds[idx].events &= ~POLLIN;
+    }
+    if (type & WRITE_EVENT) {
+        poll_ctx->fds[idx].events &= ~POLLOUT;
+    }
+    if (poll_ctx->fds[idx].events == 0) {
+        event->event_index = -1;
+        poll_ctx->nfds--;
+        if (idx < poll_ctx->nfds) {
+            poll_ctx->fds[idx] = poll_ctx->fds[poll_ctx->nfds];
+            auto iter = loop->events.find(poll_ctx->fds[idx].fd);
+            if (iter != loop->events.end()) {
+                iter->second->event_index = idx;
+            }
+        }
+    }
+    return 0;
+}
+
+int _handle_events(hloop_t* loop, int timeout) {
+    poll_ctx_t* poll_ctx = (poll_ctx_t*)loop->event_ctx;
+    if (poll_ctx == NULL)  return 0;
+    if (poll_ctx->nfds == 0)   return 0;
+    int npoll = poll(poll_ctx->fds, poll_ctx->nfds, timeout);
+    if (npoll < 0) {
+        perror("poll");
+        return npoll;
+    }
+    if (npoll == 0) return 0;
+    int nevent = 0;
+    for (int i = 0; i < poll_ctx->nfds; ++i) {
+        if (nevent == npoll) break;
+        int fd = poll_ctx->fds[i].fd;
+        short revents = poll_ctx->fds[i].revents;
+        if (revents) {
+            ++nevent;
+            auto iter = loop->events.find(fd);
+            if (iter == loop->events.end()) {
+                continue;
+            }
+            hevent_t* event = iter->second;
+            if (revents & POLLIN) {
+                _on_read(event);
+            }
+            if (revents & POLLOUT) {
+                _on_write(event);
+            }
+        }
+    }
+    return nevent;
+}
+#endif

+ 142 - 0
event/select.cpp

@@ -0,0 +1,142 @@
+#include "hevent.h"
+
+#ifdef EVENT_SELECT
+#include "hplatform.h"
+#ifdef OS_LINUX
+#include <sys/select.h>
+#endif
+
+#include "hdef.h"
+
+typedef struct select_ctx_s {
+    int max_fd;
+    fd_set readfds;
+    fd_set writefds;
+    int nread;
+    int nwrite;
+} select_ctx_t;
+
+int _event_init(hloop_t* loop) {
+    if (loop->event_ctx) return 0;
+    select_ctx_t* select_ctx = (select_ctx_t*)malloc(sizeof(select_ctx_t));
+    select_ctx->max_fd = -1;
+    FD_ZERO(&select_ctx->readfds);
+    FD_ZERO(&select_ctx->writefds);
+    select_ctx->nread = 0;
+    select_ctx->nwrite = 0;
+    loop->event_ctx = select_ctx;
+    return 0;
+}
+
+int _event_cleanup(hloop_t* loop) {
+    SAFE_FREE(loop->event_ctx);
+    return 0;
+}
+
+int _add_event(hevent_t* event, int type) {
+    hloop_t* loop = event->loop;
+    if (loop->event_ctx == NULL) {
+        hloop_event_init(loop);
+    }
+    select_ctx_t* select_ctx = (select_ctx_t*)loop->event_ctx;
+    int fd = event->fd;
+    if (fd > select_ctx->max_fd) {
+        select_ctx->max_fd = fd;
+    }
+    if (type & READ_EVENT) {
+        if (!FD_ISSET(fd, &select_ctx->readfds)) {
+            FD_SET(fd, &select_ctx->readfds);
+            select_ctx->nread++;
+        }
+    }
+    if (type & WRITE_EVENT) {
+        if (!FD_ISSET(fd, &select_ctx->writefds)) {
+            FD_SET(fd, &select_ctx->writefds);
+            select_ctx->nwrite++;
+        }
+    }
+    return 0;
+}
+
+int _del_event(hevent_t* event, int type) {
+    hloop_t* loop = event->loop;
+    select_ctx_t* select_ctx = (select_ctx_t*)loop->event_ctx;
+    if (select_ctx == NULL)    return 0;
+    int fd = event->fd;
+    if (fd == select_ctx->max_fd) {
+        select_ctx->max_fd = -1;
+    }
+    if (type & READ_EVENT) {
+        if (FD_ISSET(fd, &select_ctx->readfds)) {
+            FD_CLR(fd, &select_ctx->readfds);
+            select_ctx->nread--;
+        }
+    }
+    if (type & WRITE_EVENT) {
+        if (FD_ISSET(fd, &select_ctx->writefds)) {
+            FD_CLR(fd, &select_ctx->writefds);
+            select_ctx->nwrite--;
+        }
+    }
+    return 0;
+}
+
+int _handle_events(hloop_t* loop, int timeout) {
+    select_ctx_t* select_ctx = (select_ctx_t*)loop->event_ctx;
+    if (select_ctx == NULL)    return 0;
+    if (select_ctx->nread == 0 && select_ctx->nwrite == 0) {
+        return 0;
+    }
+    int     max_fd = select_ctx->max_fd;
+    fd_set  readfds = select_ctx->readfds;
+    fd_set  writefds = select_ctx->writefds;
+    if (max_fd == -1) {
+        for (auto& pair : loop->events) {
+            int fd = pair.first;
+            if (fd > max_fd) {
+                max_fd = fd;
+            }
+        }
+        select_ctx->max_fd = max_fd;
+    }
+    struct timeval tv, *tp;
+    if (timeout == INFINITE) {
+        tp = NULL;
+    }
+    else {
+        tv.tv_sec = timeout / 1000;
+        tv.tv_usec = (timeout % 1000) * 1000;
+        tp = &tv;
+    }
+    int nselect = select(max_fd+1, &readfds, &writefds, NULL, tp);
+    if (nselect < 0) {
+#ifdef OS_WIN
+        if (WSAGetLastError() == WSAENOTSOCK) {
+#else
+        if (errno == EBADF) {
+            perror("select");
+#endif
+            return -EBADF;
+        }
+        return nselect;
+    }
+    if (nselect == 0)   return 0;
+    int nevent = 0;
+    auto iter = loop->events.begin();
+    while (iter != loop->events.end()) {
+        if (nevent == nselect) break;
+        int fd = iter->first;
+        hevent_t* event = iter->second;
+        if (FD_ISSET(fd, &readfds)) {
+            ++nevent;
+            _on_read(event);
+        }
+        if (FD_ISSET(fd, &writefds)) {
+            ++nevent;
+            _on_write(event);
+        }
+        ++iter;
+    }
+    return nevent;
+}
+#endif

+ 106 - 0
event/server.cpp.demo

@@ -0,0 +1,106 @@
+#include "hloop.h"
+
+#include "htime.h"
+#include "hsocket.h"
+
+#define RECV_BUFSIZE    8192
+
+void on_timer(htimer_t* timer, void* userdata) {
+    static int cnt = 0;
+    printf("on_timer timer_id=%d time=%luus cnt=%d\n", timer->timer_id, timer->loop->cur_time, ++cnt);
+}
+
+void on_idle(hidle_t* idle, void* userdata) {
+    static int cnt = 0;
+    printf("on_idle idle_id=%d cnt=%d\n", idle->idle_id, ++cnt);
+}
+
+void on_read(hevent_t* event, void* userdata) {
+    printf("on_read fd=%d\n", event->fd);
+    char recvbuf[RECV_BUFSIZE] = {0};
+    int nrecv, nsend;
+recv:
+    memset(recvbuf, 0, sizeof(recvbuf));
+    nrecv = recv(event->fd, recvbuf, sizeof(recvbuf), 0);
+    printf("recv retval=%d\n", nrecv);
+    if (nrecv < 0) {
+        if (sockerrno != NIO_EAGAIN) {
+            goto recv_done;
+        }
+        else {
+            perror("recv");
+            goto recv_error;
+        }
+    }
+    if (nrecv == 0) {
+        goto disconnect;
+    }
+    printf("> %s\n", recvbuf);
+    if (nrecv == sizeof(recvbuf)) {
+        goto recv;
+    }
+
+recv_done:
+send:
+    static const char* http_response = "HTTP/1.1 200 OK\r\n\r\n";
+    nsend = send(event->fd, http_response, strlen(http_response), 0);
+    printf("send retval=%d\n", nsend);
+    printf("< %s\n", http_response);
+
+recv_error:
+disconnect:
+    printf("closesocket fd=%d\n", event->fd);
+    closesocket(event->fd);
+    hevent_del(event);
+}
+
+void on_accept(hevent_t* event, void* userdata) {
+    printf("on_accept listenfd=%d\n", event->fd);
+    struct sockaddr_in localaddr, peeraddr;
+    socklen_t addrlen = sizeof(struct sockaddr_in);
+    getsockname(event->fd, (struct sockaddr*)&localaddr, &addrlen);
+accept:
+    addrlen = sizeof(struct sockaddr_in);
+    int connfd = accept(event->fd, (struct sockaddr*)&peeraddr, &addrlen);
+    if (connfd < 0) {
+        if (sockerrno != NIO_EAGAIN) {
+            perror("accept");
+            goto accept_error;
+        }
+        //goto accept_done;
+        return;
+    }
+    printf("accept connfd=%d [%s:%d] => [%s:%d]\n", connfd,
+            inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port),
+            inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));
+
+    nonblocking(connfd);
+    hevent_read(event->loop, connfd, on_read, NULL);
+
+    goto accept;
+
+accept_error:
+    closesocket(event->fd);
+    hevent_del(event);
+}
+
+int main(int argc, char** argv) {
+    if (argc < 2) {
+        printf("Usage: cmd port\n");
+        return -10;
+    }
+    int port = atoi(argv[1]);
+
+    int listenfd = Listen(port);
+    printf("listenfd=%d\n", listenfd);
+    if (listenfd < 0) {
+        return listenfd;
+    }
+
+    hloop_t loop;
+    hloop_init(&loop);
+    //hidle_add(&loop, on_idle, NULL);
+    //htimer_add(&loop, on_timer, NULL, 1000, INFINITE);
+    hevent_accept(&loop, listenfd, on_accept, NULL);
+    hloop_run(&loop);
+}

+ 12 - 0
html/error.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Error</title>
+</head>
+<body>
+  <h1>An error occurred.</h1>
+  <p>Sorry, the page you are looking for is currently unavailable.<br/>Please try again later.</p>
+  <p>If you are the system administrator of this resource then you should check the error log for details.</p>
+  <p><em>Faithfully yours, httpd.</em></p>
+</body>
+</html>

+ 10 - 0
html/index.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>httpd</title>
+</head>
+<body>
+  <center><h1>Welcome to httpd!</h1></center>
+</body>
+</html>
+

+ 125 - 0
http/FileCache.h

@@ -0,0 +1,125 @@
+#ifndef HW_FILE_CACHE_H_
+#define HW_FILE_CACHE_H_
+
+#include <map>
+#include <string>
+
+#include "hbuf.h"
+#include "hfile.h"
+#include "md5.h"
+#include "HttpRequest.h" // for get_content_type_str_by_suffix
+#include "httpd_conf.h"
+
+#ifndef INVALID_FD
+#define INVALID_FD  -1
+#endif
+
+typedef struct file_cache_s {
+    //std::string filepath;
+    struct stat st;
+    time_t      open_time;
+    time_t      stat_time;
+    uint32_t    stat_cnt;
+    HBuf        filebuf;
+    char        last_modified[64];
+    char        etag[64];
+    const char* content_type;
+
+    file_cache_s() {
+        stat_cnt = 0;
+        content_type = NULL;
+    }
+} file_cache_t;
+
+// filepath => file_cache_t
+typedef std::map<std::string, file_cache_t*> FileCacheMap;
+
+class FileCache {
+public:
+    FileCacheMap cached_files;
+
+    ~FileCache() {
+        for (auto& pair : cached_files) {
+            delete pair.second;
+        }
+        cached_files.clear();
+    }
+
+    file_cache_t* Open(const char* filepath) {
+        file_cache_t* fc = Get(filepath);
+        bool filechanged = false;
+        if (fc) {
+            time_t tt;
+            time(&tt);
+            if (tt - fc->stat_time > g_conf_ctx.file_stat_interval) {
+                struct timespec mtime = fc->st.st_mtim;
+                stat(filepath, &fc->st);
+                fc->stat_time = tt;
+                fc->stat_cnt++;
+                if (mtime.tv_sec != fc->st.st_mtim.tv_sec ||
+                    mtime.tv_nsec != fc->st.st_mtim.tv_nsec) {
+                    filechanged = true;
+                    fc->stat_cnt = 1;
+                }
+            }
+        }
+        if (fc == NULL || filechanged) {
+            int fd = open(filepath, O_RDONLY);
+            if (fd < 0) {
+                return NULL;
+            }
+            if (fc == NULL) {
+                fc = new file_cache_t;
+                //fc->filepath = filepath;
+                fstat(fd, &fc->st);
+                time(&fc->open_time);
+                fc->stat_time = fc->open_time;
+                fc->stat_cnt = 1;
+                cached_files[filepath] = fc;
+            }
+            fc->filebuf.resize(fc->st.st_size);
+            read(fd, fc->filebuf.base, fc->filebuf.len);
+            close(fd);
+            time_t tt = fc->st.st_mtim.tv_sec;
+            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, 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';
+            const char* suffix = strrchr(filepath, '.');
+            if (suffix) {
+                ++suffix;
+                fc->content_type = http_content_type_str_by_suffix(suffix);
+            }
+        }
+        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;
+    }
+
+protected:
+    file_cache_t* Get(const char* filepath) {
+        auto iter = cached_files.find(filepath);
+        if (iter != cached_files.end()) {
+            return iter->second;
+        }
+        return NULL;
+    }
+};
+
+#endif // HW_FILE_CACHE_H_

+ 79 - 0
http/HttpParser.cpp

@@ -0,0 +1,79 @@
+#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;
+}
+

+ 96 - 0
http/HttpParser.h

@@ -0,0 +1,96 @@
+#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) {
+            strlower((char*)header_field.c_str());
+            payload->headers[header_field] = header_value;
+            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;
+    }
+
+    void parser_request_init(HttpRequest* req) {
+        hp_userdata.type = HTTP_REQUEST;
+        hp_userdata.state = HP_START_REQ_OR_RES;
+        hp_userdata.payload = req;
+        http_parser_init(&hp_parser, HTTP_REQUEST);
+        hp_parser.data = &hp_userdata;
+    }
+
+    void parser_response_init(HttpResponse* res) {
+        hp_userdata.type = HTTP_RESPONSE;
+        hp_userdata.state = HP_START_REQ_OR_RES;
+        hp_userdata.payload = res;
+        http_parser_init(&hp_parser, HTTP_REQUEST);
+        hp_parser.data = &hp_userdata;
+    }
+
+    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_

+ 317 - 0
http/HttpRequest.h

@@ -0,0 +1,317 @@
+#ifndef HTTP_REQUEST_H_
+#define HTTP_REQUEST_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"
+typedef std::map<std::string, std::string>  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() {
+        http_major = 1;
+        http_minor = 1;
+        content_type = CONTENT_TYPE_NONE;
+    }
+
+    void fill_content_type() {
+        auto iter = headers.find("Content-Type");
+        if (iter != headers.end()) {
+            content_type = http_content_type_enum(iter->second.c_str());
+            return;
+        }
+
+        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);
+        }
+    }
+
+    void fill_content_lenght() {
+        if (body.size() != 0) {
+            headers["Content-Length"] = std::to_string(body.size());
+        }
+    }
+
+    void dump_headers(std::string& str) {
+        fill_content_type();
+        fill_content_lenght();
+        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:
+            body = dump_multipart(mp);
+            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() {
+        method = HTTP_GET;
+        headers["User-Agent"] = DEFAULT_USER_AGENT;
+        headers["Accept"] = "*/*";
+    }
+
+    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);
+            std::string host;
+            if (parser.field_set & (1<<UF_HOST)) {
+                host = url.substr(parser.field_data[UF_HOST].off, parser.field_data[UF_HOST].len);
+            }
+            int port = parser.port;
+            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() {
+        status_code = HTTP_STATUS_OK;
+    }
+
+    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) {
+            dump_headers(str);
+        }
+        str += "\r\n";
+        if (is_dump_body) {
+            dump_body();
+            str += body;
+        }
+        return str;
+    }
+};
+
+#endif // HTTP_REQUEST_H_
+

+ 375 - 0
http/HttpServer.cpp

@@ -0,0 +1,375 @@
+#include "HttpServer.h"
+
+#include "hmain.h"
+#include "hversion.h"
+#include "htime.h"
+#include "hsocket.h"
+#include "hbuf.h"
+#include "hlog.h"
+#include "hscope.h"
+#include "hfile.h"
+
+#include "hloop.h"
+#include "HttpParser.h"
+#include "FileCache.h"
+#include "httpd_conf.h"
+
+#define RECV_BUFSIZE    4096
+#define SEND_BUFSIZE    4096
+
+static FileCache s_filecache;
+
+/*
+<!DOCTYPE html>
+<html>
+<head>
+  <title>404 Not Found</title>
+</head>
+<body>
+  <center><h1>404 Not Found</h1></center>
+  <hr>
+</body>
+</html>
+ */
+static void make_http_status_page(http_status status_code, std::string& page) {
+    char szCode[8];
+    snprintf(szCode, sizeof(szCode), "%d ", status_code);
+    const char* status_message = http_status_str(status_code);
+    page += R"(<!DOCTYPE html>
+<html>
+<head>
+  <title>)";
+    page += szCode; page += status_message;
+    page += R"(</title>
+</head>
+<body>
+  <center><h1>)";
+    page += szCode; page += status_message;
+    page += R"(</h1></center>
+  <hr>
+</body>
+</html>)";
+}
+
+static void master_init(void* userdata) {
+#ifdef OS_UNIX
+    char proctitle[256] = {0};
+    snprintf(proctitle, sizeof(proctitle), "%s: master process", g_main_ctx.program_name);
+    setproctitle(proctitle);
+#endif
+}
+static void master_proc(void* userdata) {
+    while(1) sleep(1);
+}
+
+static void worker_init(void* userdata) {
+#ifdef OS_UNIX
+    char proctitle[256] = {0};
+    snprintf(proctitle, sizeof(proctitle), "%s: worker process", g_main_ctx.program_name);
+    setproctitle(proctitle);
+    signal(SIGNAL_RELOAD, signal_handler);
+#endif
+}
+
+struct http_connect_userdata {
+    HttpServer*             server;
+    std::string             log;
+    HttpParser              parser;
+    HttpRequest             req;
+    HttpResponse            res;
+
+    http_connect_userdata() {
+        parser.parser_request_init(&req);
+    }
+};
+
+static void on_read(hevent_t* event, void* userdata) {
+    //printf("on_read fd=%d\n", event->fd);
+    http_connect_userdata* hcu = (http_connect_userdata*)userdata;
+    HttpService* service = hcu->server->service;
+    HttpRequest* req = &hcu->req;
+    HttpResponse* res = &hcu->res;
+    char recvbuf[RECV_BUFSIZE] = {0};
+    int ret, nrecv, nparse, nsend;
+recv:
+    // recv -> http_parser -> http_request -> http_request_handler -> http_response -> send
+    nrecv = recv(event->fd, recvbuf, sizeof(recvbuf), 0);
+    //printf("recv retval=%d\n", nrecv);
+    if (nrecv < 0) {
+        if (sockerrno != NIO_EAGAIN) {
+            perror("recv");
+            hcu->log += asprintf("recv: %s", strerror(errno));
+            goto recv_error;
+        }
+        //goto recv_done;
+        return;
+    }
+    if (nrecv == 0) {
+        hcu->log += "disconnect";
+        goto disconnect;
+    }
+    //printf("%s\n", recvbuf);
+    nparse = hcu->parser.execute(recvbuf, nrecv);
+    if (nparse != nrecv || hcu->parser.get_errno() != HPE_OK) {
+        hcu->log += asprintf("http parser error: %s", http_errno_description(hcu->parser.get_errno()));
+        goto parser_error;
+    }
+    if (hcu->parser.get_state() == HP_MESSAGE_COMPLETE) {
+        http_api_handler api = NULL;
+        file_cache_t* fc = NULL;
+        const char* content = NULL;
+        int content_length = 0;
+        bool send_in_one_packet = false;
+
+        hcu->log += asprintf("[%s %s]", http_method_str(req->method), req->url.c_str());
+        static std::string s_Server = std::string("httpd/") + std::string(get_compile_version());
+        res->headers["Server"] = s_Server;
+        // preprocessor
+        if (service->preprocessor) {
+            service->preprocessor(req, res);
+        }
+        ret = service->GetApi(req->url.c_str(), req->method, &api);
+        if (api) {
+            // api service
+            api(req, res);
+        }
+        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
+                std::string filepath = service->document_root;
+                filepath += req->url.c_str();
+                if (strcmp(req->url.c_str(), "/") == 0) {
+                    filepath += service->home_page;
+                }
+                fc = s_filecache.Open(filepath.c_str());
+                // Not Found
+                if (fc == NULL) {
+                    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;
+            }
+
+            // 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 = s_filecache.Open(filepath.c_str());
+                }
+
+                // status page
+                if (fc == NULL && res->body.size() == 0) {
+                    res->content_type = TEXT_HTML;
+                    make_http_status_page(res->status_code, res->body);
+                }
+            }
+        }
+        // postprocessor
+        if (service->postprocessor) {
+            service->postprocessor(req, res);
+        }
+        // send
+        std::string header;
+        time_t tt;
+        time(&tt);
+        char c_str[256] = {0};
+        strftime(c_str, sizeof(c_str), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tt));
+        res->headers["Date"] = c_str;
+        if (fc && fc->filebuf.len) {
+            content = (const char*)fc->filebuf.base;
+            content_length = fc->filebuf.len;
+            if (fc->content_type && *fc->content_type != '\0') {
+                res->headers["Content-Type"] = fc->content_type;
+            }
+            res->headers["Content-Length"] = std::to_string(content_length);
+            res->headers["Last-Modified"] = fc->last_modified;
+            res->headers["Etag"] = fc->etag;
+        }
+        else if (res->body.size()) {
+            content = res->body.c_str();
+            content_length = res->body.size();
+        }
+        header = res->dump(true, false);
+        if (header.size() + content_length <= SEND_BUFSIZE) {
+            header.insert(header.size(), content, content_length);
+            send_in_one_packet = true;
+        }
+
+        // send header
+        nsend = send(event->fd, header.c_str(), header.size(), 0);
+        if (nsend != header.size()) {
+            hcu->log += asprintf("send header: %s", strerror(errno));
+            goto send_error;
+        }
+        // send body
+        if (!send_in_one_packet && content_length != 0) {
+            //queue send ?
+            //if (content_length > SEND_BUFSIZE) {
+            //}
+            nsend = send(event->fd, content, content_length, 0);
+            if (nsend != res->body.size()) {
+                hcu->log += asprintf("send body: %s", strerror(errno));
+                goto send_error;
+            }
+        }
+        hcu->log += asprintf("=>[%d %s]", res->status_code, http_status_str(res->status_code));
+        hlogi("%s", hcu->log.c_str());
+        goto end;
+    }
+    if (nrecv == sizeof(recvbuf)) {
+        goto recv;
+    }
+    goto end;
+
+recv_error:
+disconnect:
+parser_error:
+send_error:
+    hloge("%s", hcu->log.c_str());
+end:
+    closesocket(event->fd);
+    hevent_del(event);
+    delete hcu;
+}
+
+static void on_accept(hevent_t* event, void* userdata) {
+    //printf("on_accept listenfd=%d\n", event->fd);
+    struct sockaddr_in localaddr, peeraddr;
+    socklen_t addrlen = sizeof(struct sockaddr_in);
+    getsockname(event->fd, (struct sockaddr*)&localaddr, &addrlen);
+accept:
+    addrlen = sizeof(struct sockaddr_in);
+    int connfd = accept(event->fd, (struct sockaddr*)&peeraddr, &addrlen);
+    if (connfd < 0) {
+        if (sockerrno != NIO_EAGAIN) {
+            perror("accept");
+            hloge("accept failed: %s: %d", strerror(sockerrno), sockerrno);
+            goto accept_error;
+        }
+        //goto accept_done;
+        return;
+    }
+
+    {
+        // new http_connect
+        // delete on on_read
+        http_connect_userdata* hcu = new http_connect_userdata;
+        hcu->server = (HttpServer*)userdata;
+        hcu->log += asprintf("[%s:%d]", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
+
+        nonblocking(connfd);
+        hevent_read(event->loop, connfd, on_read, hcu);
+    }
+
+    goto accept;
+
+accept_error:
+    closesocket(event->fd);
+    hevent_del(event);
+}
+
+void handle_cached_files(htimer_t* timer, void* userdata) {
+    FileCache* pfc = (FileCache*)userdata;
+    if (pfc == NULL) {
+        htimer_del(timer);
+        return;
+    }
+    file_cache_t* fc = NULL;
+    time_t tt;
+    time(&tt);
+    auto iter = pfc->cached_files.begin();
+    while (iter != pfc->cached_files.end()) {
+        fc = iter->second;
+        if (tt - fc->stat_time > g_conf_ctx.file_cached_time) {
+            delete fc;
+            iter = pfc->cached_files.erase(iter);
+            continue;
+        }
+        ++iter;
+    }
+}
+
+static void worker_proc(void* userdata) {
+    HttpServer* server = (HttpServer*)userdata;
+    int listenfd = server->listenfd_;
+    hloop_t loop;
+    hloop_init(&loop);
+    htimer_add(&loop, handle_cached_files, &s_filecache, MAX(60000, g_conf_ctx.file_cached_time*1000));
+    hevent_accept(&loop, listenfd, on_accept, server);
+    hloop_run(&loop);
+}
+
+static HttpService s_default_service;
+#define DEFAULT_HTTP_PORT   80
+HttpServer::HttpServer() {
+    port = DEFAULT_HTTP_PORT;
+    worker_processes = 0;
+    service = NULL;
+    listenfd_ = 0;
+}
+
+int HttpServer::SetListenPort(int port) {
+    this->port = port;
+    listenfd_ = Listen(port);
+    return listenfd_ < 0 ? listenfd_ : 0;
+}
+
+void HttpServer::Run(bool wait) {
+    if (service == NULL) {
+        service = &s_default_service;
+    }
+    if (worker_processes == 0) {
+        worker_proc(this);
+    }
+    else {
+        // master-workers processes
+        g_worker_processes_num = worker_processes;
+        int bytes = worker_processes * sizeof(proc_ctx_t);
+        g_worker_processes = (proc_ctx_t*)malloc(bytes);
+        if (g_worker_processes == NULL) {
+            perror("malloc");
+            abort();
+        }
+        memset(g_worker_processes, 0, bytes);
+        for (int i = 0; i < worker_processes; ++i) {
+            proc_ctx_t* ctx = g_worker_processes + i;
+            ctx->init = worker_init;
+            ctx->init_userdata = NULL;
+            ctx->proc = worker_proc;
+            ctx->proc_userdata = this;
+            spawn_proc(ctx);
+        }
+    }
+
+    if (wait) {
+        master_init(NULL);
+        master_proc(this);
+    }
+}
+

+ 29 - 0
http/HttpServer.h

@@ -0,0 +1,29 @@
+#ifndef HTTP_SERVER_H_
+#define HTTP_SERVER_H_
+
+#include "HttpService.h"
+
+class HttpServer {
+public:
+    HttpServer();
+
+    int SetListenPort(int port);
+
+    void SetWorkerProcesses(int worker_processes) {
+        this->worker_processes = worker_processes;
+    }
+
+    void RegisterService(HttpService* service) {
+        this->service = service;
+    }
+
+    void Run(bool wait = true);
+
+public:
+    int port;
+    int worker_processes;
+    HttpService* service;
+    int listenfd_;
+};
+
+#endif

+ 102 - 0
http/HttpService.h

@@ -0,0 +1,102 @@
+#ifndef HTTP_SERVICE_H_
+#define HTTP_SERVICE_H_
+
+#include <string.h>
+#include <string>
+#include <map>
+#include <list>
+#include <memory>
+
+#include "HttpRequest.h"
+
+#define DEFAULT_BASE_URL        "/v1/api"
+#define DEFAULT_DOCUMENT_ROOT   "/var/www/html"
+#define DEFAULT_HOME_PAGE       "index.html"
+
+typedef void (*http_api_handler)(HttpRequest* req, HttpResponse* res);
+
+struct http_method_handler {
+    http_method         method;
+    http_api_handler    handler;
+    http_method_handler(http_method m = HTTP_POST, http_api_handler h = NULL) {
+        method = m;
+        handler = h;
+    }
+};
+// Provide Restful API
+typedef std::list<http_method_handler> http_method_handlers;
+// path => http_method_handlers
+typedef std::map<std::string, std::shared_ptr<http_method_handlers>> http_api_handlers;
+
+struct HttpService {
+    http_api_handler    preprocessor;
+    http_api_handler    postprocessor;
+    // api service
+    std::string         base_url;
+    http_api_handlers   api_handlers;
+    // web service
+    std::string document_root;
+    std::string home_page;
+    std::string error_page;
+
+    HttpService() {
+        preprocessor = NULL;
+        postprocessor = NULL;
+        base_url = DEFAULT_BASE_URL;
+        document_root = DEFAULT_DOCUMENT_ROOT;
+        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;
+    }
+};
+
+#endif // HTTP_SERVICE_H_
+

+ 60 - 0
http/api_test.h

@@ -0,0 +1,60 @@
+#ifndef HTTP_API_TEST_H_
+#define HTTP_API_TEST_H_
+
+#include "HttpRequest.h"
+
+// XXX(path, method, handler)
+#define HTTP_API_MAP(XXX) \
+    XXX("/json",    POST,   http_api_json)      \
+    XXX("/mp",      POST,   http_api_mp)        \
+    XXX("/kv",      POST,   http_api_kv)        \
+    XXX("/query",   GET,    http_api_query)     \
+    XXX("/echo",    POST,   http_api_echo)      \
+
+
+inline void http_api_preprocessor(HttpRequest* req, HttpResponse* res) {
+    //printf("%s\n", req->dump(true, true).c_str());
+    req->parse_url();
+    req->parse_body();
+}
+
+inline void http_api_postprocessor(HttpRequest* req, HttpResponse* res) {
+    res->dump_body();
+    //printf("%s\n", res->dump(true, true).c_str());
+}
+
+inline void http_api_json(HttpRequest* req, HttpResponse* res) {
+    if (req->content_type != APPLICATION_JSON) {
+        res->status_code = HTTP_STATUS_BAD_REQUEST;
+        return;
+    }
+    res->json = req->json;
+}
+
+inline void http_api_mp(HttpRequest* req, HttpResponse* res) {
+    if (req->content_type != MULTIPART_FORM_DATA) {
+        res->status_code = HTTP_STATUS_BAD_REQUEST;
+        return;
+    }
+    res->mp = req->mp;
+}
+
+inline void http_api_kv(HttpRequest*req, HttpResponse* res) {
+    if (req->content_type != X_WWW_FORM_URLENCODED) {
+        res->status_code = HTTP_STATUS_BAD_REQUEST;
+        return;
+    }
+    res->kv = req->kv;
+}
+
+inline void http_api_query(HttpRequest* req, HttpResponse* res) {
+    res->kv = req->query_params;
+}
+
+inline void http_api_echo(HttpRequest* req, HttpResponse* res) {
+    res->content_type = req->content_type;
+    res->body = req->body;
+}
+
+#endif // HTTP_API_TEST_H_
+

+ 151 - 0
http/curl.cpp.demo

@@ -0,0 +1,151 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _MSC_VER
+#include "misc/win32_getopt.h"
+#else
+#include <getopt.h>
+#endif
+
+#include "http_client.h"
+
+static const char* options = "hVvX:H:d:";
+static const struct option long_options[] = {
+    {"help",    no_argument,        NULL,   'h'},
+    {"verion",  no_argument,        NULL,   'V'},
+    {"verbose", no_argument,        NULL,   'v'},
+    {"method",  required_argument,  NULL,   'X'},
+    {"header",  required_argument,  NULL,   'H'},
+    {"data",    required_argument,  NULL,   'd'},
+    {NULL,      0,                  NULL,   0}
+};
+static const char* help = R"(Options:
+    -h|--help           Print this message.
+    -V|--version        Print version.
+    -v|--verbose        Show verbose infomation.
+    -X|--method         Set http method.
+    -H|--header         Add http headers, format -H "Content-Type:application/json Accept:*/*"
+    -d|--data           Set http body.
+Examples:
+    curl -v localhost:8086
+    curl -v localhost:8086/v1/api/query?page_no=1&page_size=10
+    curl -v -X POST localhost:8086/v1/api/json  -H "Content-Type:application/json"                  -d '{"user":"admin","pswd":"123456"}'
+    curl -v -X POST localhost:8086/v1/api/kv    -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
+    curl -v -X POST localhost:8086/v1/api/echo  -H "Content-Type:text/plain"                        -d 'hello,world!'
+)";
+
+void print_usage() {
+    printf("Usage: curl [%s] url\n", options);
+}
+void print_version() {
+    printf("curl version 1.0.0\n");
+}
+void print_help() {
+    print_usage();
+    puts(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;
+    while ((opt = getopt_long(argc, argv, options, long_options, &opt_idx)) != EOF) {
+        switch(opt) {
+        case 'h': print_help(); exit(0);
+        case 'V': print_version(); exit(0);
+        case 'v': verbose = true; break;
+        case 'X': method = optarg; break;
+        case 'H': headers = optarg; break;
+        case 'd': data = optarg; break;
+        default:
+            print_usage();
+            exit(-1);
+        }
+    }
+
+    if (optind == argc) {
+        printf("Missing url\n");
+        print_usage();
+        exit(-1);
+    }
+    url = argv[optind];
+
+    return 0;
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 2) {
+        print_usage();
+        return 0;
+    }
+
+    parse_cmdline(argc, argv);
+
+    int ret = 0;
+    HttpRequest req;
+    req.url = url;
+    if (method) {
+        req.method = http_method_enum(method);
+    }
+    if (headers) {
+        enum {
+            s_key,
+            s_value,
+        } state = s_key;
+        const char* p = headers;
+        const char* key = p;
+        const char* value = NULL;
+        int key_len = 0;
+        int value_len = 0;
+        while (*p != '\0') {
+            if (*p == ' ') {
+                if (key_len && value_len) {
+                    req.headers[std::string(key,key_len)] = std::string(value,value_len);
+                    key_len = value_len = 0;
+                }
+                state = s_key;
+                key = p+1;
+            }
+            else if (*p == ':') {
+                state = s_value;
+                value = p+1;
+            }
+            else {
+                state == s_key ? ++key_len : ++value_len;
+            }
+            ++p;
+        }
+        if (key_len && value_len) {
+            req.headers[std::string(key,key_len)] = std::string(value,value_len);
+            key_len = value_len = 0;
+        }
+    }
+    if (data) {
+        if (method == NULL) {
+            req.method = HTTP_POST;
+        }
+        req.body = data;
+    }
+    HttpResponse res;
+    ret = http_client_send(&req, &res, 0);
+    if (verbose) {
+        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());
+        }
+        else {
+            printf("%s\n", res.body.c_str());
+        }
+    }
+    return ret;
+}

+ 171 - 0
http/http_client.cpp.curl

@@ -0,0 +1,171 @@
+#include "http_client.h"
+
+#include <string.h>
+#include <string>
+using std::string;
+
+#define SPACE_CHARS     " \t\r\n"
+static string trim(const string& str) {
+    string::size_type pos1 = str.find_first_not_of(SPACE_CHARS);
+    if (pos1 == string::npos)   return "";
+
+    string::size_type pos2 = str.find_last_not_of(SPACE_CHARS);
+    return str.substr(pos1, pos2-pos1+1);
+}
+
+/***************************************************************
+HttpClient based libcurl
+***************************************************************/
+#include "curl/curl.h"
+
+//#include "hlog.h"
+
+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;
+
+    HttpResponse* res = (HttpResponse*)userdata;
+
+    string str(buf);
+    string::size_type pos = str.find_first_of(':');
+    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);
+            res->http_major = http_major;
+            res->http_minor = http_minor;
+            res->status_code = (http_status)status_code;
+        }
+    }
+    else {
+        // headers
+        string key = trim(str.substr(0, pos));
+        string value = trim(str.substr(pos+1));
+        res->headers[key] = value;
+    }
+    return size*cnt;
+}
+
+static size_t s_body_cb(char *buf, size_t size, size_t cnt, void *userdata) {
+    if (buf == NULL || userdata == NULL)    return 0;
+
+    HttpResponse* res = (HttpResponse*)userdata;
+    res->body.append(buf, size*cnt);
+    return size*cnt;
+}
+
+#include <atomic>
+static std::atomic_flag s_curl_global_init(false);
+int http_client_send(HttpRequest* req, HttpResponse* res, int timeout) {
+    if (req == NULL || res == NULL) {
+        return -1;
+    }
+
+    if (!s_curl_global_init.test_and_set()) {
+        curl_global_init(CURL_GLOBAL_ALL);
+    }
+
+    CURL* handle = curl_easy_init();
+
+    // SSL
+    curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0);
+    curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0);
+
+    // method
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, http_method_str(req->method));
+
+    // url
+    std::string url = req->dump_url();
+    curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
+    //hlogd("%s %s HTTP/%d.%d", http_method_str(req->method), url.c_str(), http_major, http_minor);
+
+    // header
+    req->fill_content_type();
+    struct curl_slist *headers = NULL;
+    for (auto& pair : req->headers) {
+        string header = pair.first;
+        header += ": ";
+        header += pair.second;
+        headers = curl_slist_append(headers, header.c_str());
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+    // body
+    struct curl_httppost* httppost = NULL;
+    struct curl_httppost* lastpost = NULL;
+    if (req->body.size() == 0) {
+        req->dump_body();
+        if (req->body.size() == 0 &&
+            req->content_type == MULTIPART_FORM_DATA) {
+            for (auto& pair : req->mp) {
+                if (pair.second.filename.size() != 0) {
+                    curl_formadd(&httppost, &lastpost,
+                            CURLFORM_COPYNAME, pair.first.c_str(),
+                            CURLFORM_FILE, pair.second.filename.c_str(),
+                            CURLFORM_END);
+                }
+                else if (pair.second.content.size() != 0) {
+                    curl_formadd(&httppost, &lastpost,
+                            CURLFORM_COPYNAME, pair.first.c_str(),
+                            CURLFORM_COPYCONTENTS, pair.second.content.c_str(),
+                            CURLFORM_END);
+                }
+            }
+            if (httppost) {
+                curl_easy_setopt(handle, CURLOPT_HTTPPOST, httppost);
+                curl_formget(httppost, NULL, s_formget_cb);
+            }
+        }
+    }
+    if (req->body.size() != 0) {
+        curl_easy_setopt(handle, CURLOPT_POSTFIELDS, req->body.c_str());
+    }
+
+    if (timeout > 0) {
+        curl_easy_setopt(handle, CURLOPT_TIMEOUT, timeout);
+    }
+
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, s_body_cb);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, res);
+
+    curl_easy_setopt(handle, CURLOPT_HEADER, 0);
+    curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, s_header_cb);
+    curl_easy_setopt(handle, CURLOPT_HEADERDATA, res);
+
+    int ret = curl_easy_perform(handle);
+    /*
+    if (ret != 0) {
+        hloge("curl error: %d: %s", ret, curl_easy_strerror((CURLcode)ret));
+    }
+    if (res->body.length() != 0) {
+        hlogd("[Response]\n%s", res->body.c_str());
+    }
+    double total_time, name_time, conn_time, pre_time;
+    curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &total_time);
+    curl_easy_getinfo(handle, CURLINFO_NAMELOOKUP_TIME, &name_time);
+    curl_easy_getinfo(handle, CURLINFO_CONNECT_TIME, &conn_time);
+    curl_easy_getinfo(handle, CURLINFO_PRETRANSFER_TIME, &pre_time);
+    hlogd("TIME_INFO: %lf,%lf,%lf,%lf", total_time, name_time, conn_time, pre_time);
+    */
+
+    if (headers) {
+        curl_slist_free_all(headers);
+    }
+    if (httppost) {
+        curl_formfree(httppost);
+    }
+
+    curl_easy_cleanup(handle);
+
+    return ret;
+}
+
+const char* http_client_strerror(int errcode) {
+    return curl_easy_strerror((CURLcode)errcode);
+}

+ 32 - 0
http/http_client.h

@@ -0,0 +1,32 @@
+#ifndef HTTP_CLIENT_H_
+#define HTTP_CLIENT_H_
+
+#include "HttpRequest.h"
+
+/*
+#include <stdio.h>
+
+#include "http_client.h"
+
+int main(int argc, char* argv[]) {
+    HttpRequest req;
+    req.method = HTTP_GET;
+    req.url = "www.baidu.com";
+    HttpResponse res;
+    int ret = http_client_send(&req, &res);
+    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());
+    }
+    return ret;
+}
+*/
+
+#define DEFAULT_HTTP_TIMEOUT    10 // s
+int http_client_send(HttpRequest* req, HttpResponse* res, int timeout = DEFAULT_HTTP_TIMEOUT);
+const char* http_client_strerror(int errcode);
+
+#endif  // HTTP_CLIENT_H_

+ 309 - 0
http/http_content.cpp

@@ -0,0 +1,309 @@
+#include "http_content.h"
+
+#include "hfile.h"
+#include "hstring.h"
+
+#ifndef LOWER
+#define LOWER(c)    ((c) | 0x20)
+#endif
+
+#ifndef UPPER
+#define UPPER(c)    ((c) & ~0x20)
+#endif
+
+#ifndef IS_NUM
+#define IS_NUM(c)   ((c) >= '0' && (c) <= '9')
+#endif
+
+#ifndef IS_ALPHA
+#define IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'A'))
+#endif
+
+#ifndef IS_ALPHANUM
+#define IS_ALPHANUM(c) (IS_NUM(c) || IS_ALPHA(c))
+#endif
+
+#ifndef IS_HEX
+#define IS_HEX(c) (IS_NUM(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
+#endif
+
+#ifndef C2I
+#define C2I(c)  ((c)-'0')
+#endif
+
+static char hex2i(char hex) {
+    if (hex >= '0' && hex <= '9') {
+        return hex - '0';
+    }
+    if (hex >= 'A' && hex <= 'F') {
+        return hex - 'A';
+    }
+    if (hex >= 'a' && hex <= 'f') {
+        return hex - 'a';
+    }
+    return 0;
+}
+
+// scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
+static std::string escape(const std::string& param) {
+    std::string str;
+    const char* p = param.c_str();
+    char escape[4] = {0};
+    while (*p != '\0') {
+        if (*p == ' ' ||
+            *p == ':' ||
+            *p == '/' ||
+            *p == '@' ||
+            *p == '?' ||
+            *p == '=' ||
+            *p == '&' ||
+            *p == '#' ||
+            *p == '%') {
+            sprintf(escape, "%%%02X", *p);
+            str += escape;
+        }
+        else {
+            str += *p;
+        }
+        ++p;
+    }
+    return str;
+}
+
+static std::string unescape(const char* escape_param) {
+    std::string str;
+    const char* p = escape_param;
+    while (*p != '\0') {
+        if (*p == '%' &&
+            IS_HEX(p[1]) &&
+            IS_HEX(p[2])) {
+            str += (hex2i(p[1]) << 4 | hex2i(p[2]));
+            p += 3;
+            continue;
+        }
+        str += *p;
+        ++p;
+    }
+    return str;
+}
+
+std::string dump_query_params(QueryParams& query_params) {
+    std::string query_string;
+    for (auto& pair : query_params) {
+        if (query_string.size() != 0) {
+            query_string += '&';
+        }
+        query_string += escape(pair.first);
+        query_string += '=';
+        query_string += escape(pair.second);
+    }
+    return query_string;
+}
+
+int parse_query_params(const char* query_string, QueryParams& query_params) {
+    const char* p = strchr(query_string, '?');
+    p = p ? p+1 : query_string;
+    p = unescape(p).c_str();
+
+    enum {
+        s_key,
+        s_value,
+    } state = s_key;
+
+    const char* key = p;
+    const char* value = NULL;
+    int key_len = 0;
+    int value_len = 0;
+    while (*p != '\0') {
+        if (*p == '&') {
+            if (key_len && value_len) {
+                query_params[std::string(key,key_len)] = std::string(value,value_len);
+                key_len = value_len = 0;
+            }
+            state = s_key;
+            key = p+1;
+        }
+        else if (*p == '=') {
+            state = s_value;
+            value = p+1;
+        }
+        else {
+            state == s_key ? ++key_len : ++value_len;
+        }
+        ++p;
+    }
+    if (key_len && value_len) {
+        query_params[std::string(key,key_len)] = std::string(value,value_len);
+        key_len = value_len = 0;
+    }
+    return query_params.size() == 0 ? -1 : 0;
+}
+
+std::string dump_json(Json& json) {
+    return json.dump();
+}
+
+std::string g_parse_json_errmsg;
+int parse_json(const char* str, Json& json, std::string& errmsg) {
+    try {
+        json = Json::parse(str);
+    }
+    catch(nlohmann::detail::exception e) {
+        errmsg = e.what();
+        return -1;
+    }
+    return (json.is_discarded() || json.is_null()) ? -1 : 0;
+}
+
+std::string dump_multipart(MultiPart& mp, const char* boundary) {
+    char c_str[256] = {0};
+    std::string str;
+    for (auto& pair : mp) {
+        str += "--";
+        str += boundary;
+        str += "\r\n";
+        str += "Content-Disposition: form-data";
+        snprintf(c_str, sizeof(c_str), "; name=\"%s\"", pair.first.c_str());
+        str += c_str;
+        auto& form = pair.second;
+        if (form.filename.size() != 0) {
+            if (form.content.size() == 0) {
+                HFile file;
+                if (file.open(form.filename.c_str(), "r") == 0) {
+                    file.readall(form.content);
+                }
+            }
+            snprintf(c_str, sizeof(c_str), "; filename=\"%s\"", basename(form.filename).c_str());
+            str += c_str;
+        }
+        str += "\r\n\r\n";
+        str += form.content;
+        str += "\r\n";
+    }
+    str += "--";
+    str += boundary;
+    str += "--";
+    return str;
+}
+
+#include "multipart_parser.h"
+enum multipart_parser_state_e {
+    MP_START,
+    MP_PART_DATA_BEGIN,
+    MP_HEADER_FIELD,
+    MP_HEADER_VALUE,
+    MP_HEADERS_COMPLETE,
+    MP_PART_DATA,
+    MP_PART_DATA_END,
+    MP_BODY_END
+};
+struct multipart_parser_userdata {
+    MultiPart* mp;
+    // tmp
+    multipart_parser_state_e state;
+    std::string header_field;
+    std::string header_value;
+    std::string part_data;
+    std::string name;
+    std::string filename;
+
+    void handle_header() {
+        if (header_field.size() == 0 || header_value.size() == 0) return;
+        if (stricmp(header_field.c_str(), "Content-Disposition") == 0) {
+            StringList strlist = split(header_value, ';');
+            for (auto& str : strlist) {
+                StringList kv = split(trim(str, " "), '=');
+                if (kv.size() == 2) {
+                    const char* key = kv.begin()->c_str();
+                    const char* value = trim_pairs(*(kv.begin()+1), "\"\"").c_str();
+                    if (strcmp(key, "name") == 0) {
+                        name = value;
+                    }
+                    else if (strcmp(key, "filename") == 0) {
+                        filename = value;
+                    }
+                }
+            }
+        }
+        header_field.clear();
+        header_value.clear();
+    }
+
+    void handle_data() {
+        if (name.c_str() != 0) {
+            (*mp)[name] = FormData(part_data.c_str(), filename.c_str());
+        }
+        name.clear();
+        filename.clear();
+        part_data.clear();
+    }
+};
+static int on_header_field(multipart_parser* parser, const char *at, size_t length) {
+    printf("on_header_field:%.*s\n", (int)length, at);
+    multipart_parser_userdata* userdata = (multipart_parser_userdata*)multipart_parser_get_data(parser);
+    userdata->handle_header();
+    userdata->state = MP_HEADER_FIELD;
+    userdata->header_field.insert(userdata->header_field.size(), at, length);
+    return 0;
+}
+static int on_header_value(multipart_parser* parser, const char *at, size_t length) {
+    printf("on_header_value:%.*s\n", (int)length, at);
+    multipart_parser_userdata* userdata = (multipart_parser_userdata*)multipart_parser_get_data(parser);
+    userdata->state = MP_HEADER_VALUE;
+    userdata->header_value.insert(userdata->header_value.size(), at, length);
+    return 0;
+}
+static int on_part_data(multipart_parser* parser, const char *at, size_t length) {
+    printf("on_part_data:%.*s\n", (int)length, at);
+    multipart_parser_userdata* userdata = (multipart_parser_userdata*)multipart_parser_get_data(parser);
+    userdata->state = MP_PART_DATA;
+    userdata->part_data.insert(userdata->part_data.size(), at, length);
+    return 0;
+}
+static int on_part_data_begin(multipart_parser* parser) {
+    printf("on_part_data_begin\n");
+    multipart_parser_userdata* userdata = (multipart_parser_userdata*)multipart_parser_get_data(parser);
+    userdata->state = MP_PART_DATA_BEGIN;
+    return 0;
+}
+static int on_headers_complete(multipart_parser* parser) {
+    printf("on_headers_complete\n");
+    multipart_parser_userdata* userdata = (multipart_parser_userdata*)multipart_parser_get_data(parser);
+    userdata->handle_header();
+    userdata->state = MP_HEADERS_COMPLETE;
+    return 0;
+}
+static int on_part_data_end(multipart_parser* parser) {
+    printf("on_part_data_end\n");
+    multipart_parser_userdata* userdata = (multipart_parser_userdata*)multipart_parser_get_data(parser);
+    userdata->state = MP_PART_DATA_END;
+    userdata->handle_data();
+    return 0;
+}
+static int on_body_end(multipart_parser* parser) {
+    printf("on_body_end\n");
+    multipart_parser_userdata* userdata = (multipart_parser_userdata*)multipart_parser_get_data(parser);
+    userdata->state = MP_BODY_END;
+    return 0;
+}
+int parse_multipart(std::string& str, MultiPart& mp, const char* boundary) {
+    printf("boundary=%s\n", boundary);
+    std::string __boundary("--");
+    __boundary += boundary;
+    multipart_parser_settings settings;
+    settings.on_header_field = on_header_field;
+    settings.on_header_value = on_header_value;
+    settings.on_part_data    = on_part_data;
+    settings.on_part_data_begin  = on_part_data_begin;
+    settings.on_headers_complete = on_headers_complete;
+    settings.on_part_data_end    = on_part_data_end;
+    settings.on_body_end         = on_body_end;
+    multipart_parser* parser = multipart_parser_init(__boundary.c_str(), &settings);
+    multipart_parser_userdata userdata;
+    userdata.state = MP_START;
+    userdata.mp = &mp;
+    multipart_parser_set_data(parser, &userdata);
+    size_t nparse = multipart_parser_execute(parser, str.c_str(), str.size());
+    multipart_parser_free(parser);
+    return nparse == str.size() ? 0 : -1;
+}

+ 116 - 0
http/http_content.h

@@ -0,0 +1,116 @@
+#ifndef HTTP_CONTENT_H_
+#define HTTP_CONTENT_H_
+
+#include <string>
+#include <map>
+
+// MultiMap
+namespace std {
+/*
+int main() {
+    std::MultiMap<std::string, std::string> kvs;
+    kvs["name"] = "hw";
+    kvs["filename"] = "1.jpg";
+    kvs["filename"] = "2.jpg";
+    //kvs.insert(std::pair<std::string,std::string>("name", "hw"));
+    //kvs.insert(std::pair<std::string,std::string>("filename", "1.jpg"));
+    //kvs.insert(std::pair<std::string,std::string>("filename", "2.jpg"));
+    for (auto& pair : kvs) {
+        printf("%s:%s\n", pair.first.c_str(), pair.second.c_str());
+    }
+    auto iter = kvs.find("filename");
+    if (iter != kvs.end()) {
+        for (int i = 0; i < kvs.count("filename"); ++i, ++iter) {
+            printf("%s:%s\n", iter->first.c_str(), iter->second.c_str());
+        }
+    }
+    return 0;
+}
+ */
+template<typename Key,typename Value>
+class MultiMap : public multimap<Key, Value> {
+public:
+    Value& operator[](Key key) {
+        auto iter = this->insert(std::pair<Key,Value>(key,Value()));
+        return (*iter).second;
+    }
+};
+}
+
+// MAP
+#ifdef USE_MULTIMAP
+#define MAP     std::Multipart
+#else
+#define MAP     std::map
+#endif
+
+// KeyValue
+typedef MAP<std::string, std::string> KeyValue;
+
+// QueryParams
+typedef KeyValue    QueryParams;
+std::string dump_query_params(QueryParams& query_params);
+int         parse_query_params(const char* query_string, QueryParams& query_params);
+
+// Json
+#include "json.hpp"
+using Json = nlohmann::json;
+extern std::string g_parse_json_errmsg;
+std::string dump_json(Json& json);
+int         parse_json(const char* str, Json& json, std::string& errmsg = g_parse_json_errmsg);
+
+/**************multipart/form-data*************************************
+--boundary
+Content-Disposition: form-data; name="user"
+
+content
+--boundary
+Content-Disposition: form-data; name="avatar"; filename="user.jpg"
+Content-Type: image/jpeg
+
+content
+--boundary--
+***********************************************************************/
+// FormData
+struct FormData {
+    std::string     filename;
+    std::string     content;
+
+    FormData(const char* content = NULL, const char* filename = NULL) {
+        if (content) {
+            this->content = content;
+        }
+        if (filename) {
+            this->filename = filename;
+        }
+    }
+    FormData(const char* c_str, bool file = false) {
+        if (c_str) {
+            file ? filename = c_str : content = c_str;
+        }
+    }
+    FormData(const std::string& str, bool file = false) {
+        file ? filename = str : content = str;
+    }
+    FormData(int n) {
+        content = std::to_string(n);
+    }
+    FormData(long long n) {
+        content = std::to_string(n);
+    }
+    FormData(float f) {
+        content = std::to_string(f);
+    }
+    FormData(double lf) {
+        content = std::to_string(lf);
+    }
+};
+
+// Multipart
+// name => FormData
+typedef MAP<std::string, FormData>          MultiPart;
+#define DEFAULT_MULTIPART_BOUNDARY  "----WebKitFormBoundary7MA4YWxkTrZu0gW"
+std::string dump_multipart(MultiPart& mp, const char* boundary = DEFAULT_MULTIPART_BOUNDARY);
+int         parse_multipart(std::string& str, MultiPart& mp, const char* boundary);
+
+#endif // HTTP_CONTENT_H_

+ 2497 - 0
http/http_parser.c

@@ -0,0 +1,2497 @@
+/* Copyright Joyent, Inc. and other Node contributors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <assert.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+
+static uint32_t max_header_size = HTTP_MAX_HEADER_SIZE;
+
+#ifndef ULLONG_MAX
+# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
+#endif
+
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#ifndef BIT_AT
+# define BIT_AT(a, i)                                                \
+  (!!((unsigned int) (a)[(unsigned int) (i) >> 3] &                  \
+   (1 << ((unsigned int) (i) & 7))))
+#endif
+
+#ifndef ELEM_AT
+# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
+#endif
+
+#define SET_ERRNO(e)                                                 \
+do {                                                                 \
+  parser->nread = nread;                                             \
+  parser->http_errno = (e);                                          \
+} while(0)
+
+#define CURRENT_STATE() p_state
+#define UPDATE_STATE(V) p_state = (enum state) (V);
+#define RETURN(V)                                                    \
+do {                                                                 \
+  parser->nread = nread;                                             \
+  parser->state = CURRENT_STATE();                                   \
+  return (V);                                                        \
+} while (0);
+#define REEXECUTE()                                                  \
+  goto reexecute;                                                    \
+
+
+#ifdef __GNUC__
+# define LIKELY(X) __builtin_expect(!!(X), 1)
+# define UNLIKELY(X) __builtin_expect(!!(X), 0)
+#else
+# define LIKELY(X) (X)
+# define UNLIKELY(X) (X)
+#endif
+
+
+/* Run the notify callback FOR, returning ER if it fails */
+#define CALLBACK_NOTIFY_(FOR, ER)                                    \
+do {                                                                 \
+  assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \
+                                                                     \
+  if (LIKELY(settings->on_##FOR)) {                                  \
+    parser->state = CURRENT_STATE();                                 \
+    if (UNLIKELY(0 != settings->on_##FOR(parser))) {                 \
+      SET_ERRNO(HPE_CB_##FOR);                                       \
+    }                                                                \
+    UPDATE_STATE(parser->state);                                     \
+                                                                     \
+    /* We either errored above or got paused; get out */             \
+    if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) {             \
+      return (ER);                                                   \
+    }                                                                \
+  }                                                                  \
+} while (0)
+
+/* Run the notify callback FOR and consume the current byte */
+#define CALLBACK_NOTIFY(FOR)            CALLBACK_NOTIFY_(FOR, p - data + 1)
+
+/* Run the notify callback FOR and don't consume the current byte */
+#define CALLBACK_NOTIFY_NOADVANCE(FOR)  CALLBACK_NOTIFY_(FOR, p - data)
+
+/* Run data callback FOR with LEN bytes, returning ER if it fails */
+#define CALLBACK_DATA_(FOR, LEN, ER)                                 \
+do {                                                                 \
+  assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \
+                                                                     \
+  if (FOR##_mark) {                                                  \
+    if (LIKELY(settings->on_##FOR)) {                                \
+      parser->state = CURRENT_STATE();                               \
+      if (UNLIKELY(0 !=                                              \
+                   settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \
+        SET_ERRNO(HPE_CB_##FOR);                                     \
+      }                                                              \
+      UPDATE_STATE(parser->state);                                   \
+                                                                     \
+      /* We either errored above or got paused; get out */           \
+      if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) {           \
+        return (ER);                                                 \
+      }                                                              \
+    }                                                                \
+    FOR##_mark = NULL;                                               \
+  }                                                                  \
+} while (0)
+
+/* Run the data callback FOR and consume the current byte */
+#define CALLBACK_DATA(FOR)                                           \
+    CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
+
+/* Run the data callback FOR and don't consume the current byte */
+#define CALLBACK_DATA_NOADVANCE(FOR)                                 \
+    CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)
+
+/* Set the mark FOR; non-destructive if mark is already set */
+#define MARK(FOR)                                                    \
+do {                                                                 \
+  if (!FOR##_mark) {                                                 \
+    FOR##_mark = p;                                                  \
+  }                                                                  \
+} while (0)
+
+/* Don't allow the total size of the HTTP headers (including the status
+ * line) to exceed max_header_size.  This check is here to protect
+ * embedders against denial-of-service attacks where the attacker feeds
+ * us a never-ending header that the embedder keeps buffering.
+ *
+ * This check is arguably the responsibility of embedders but we're doing
+ * it on the embedder's behalf because most won't bother and this way we
+ * make the web a little safer.  max_header_size is still far bigger
+ * than any reasonable request or response so this should never affect
+ * day-to-day operation.
+ */
+#define COUNT_HEADER_SIZE(V)                                         \
+do {                                                                 \
+  nread += (uint32_t)(V);                                            \
+  if (UNLIKELY(nread > max_header_size)) {                           \
+    SET_ERRNO(HPE_HEADER_OVERFLOW);                                  \
+    goto error;                                                      \
+  }                                                                  \
+} while (0)
+
+
+#define PROXY_CONNECTION "proxy-connection"
+#define CONNECTION "connection"
+#define CONTENT_LENGTH "content-length"
+#define TRANSFER_ENCODING "transfer-encoding"
+#define UPGRADE "upgrade"
+#define CHUNKED "chunked"
+#define KEEP_ALIVE "keep-alive"
+#define CLOSE "close"
+
+
+static const char *method_strings[] =
+  {
+#define XX(num, name, string) #string,
+  HTTP_METHOD_MAP(XX)
+#undef XX
+  };
+
+
+/* Tokens as defined by rfc 2616. Also lowercases them.
+ *        token       = 1*<any CHAR except CTLs or separators>
+ *     separators     = "(" | ")" | "<" | ">" | "@"
+ *                    | "," | ";" | ":" | "\" | <">
+ *                    | "/" | "[" | "]" | "?" | "="
+ *                    | "{" | "}" | SP | HT
+ */
+static const char tokens[256] = {
+/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
+       ' ',     '!',      0,      '#',     '$',     '%',     '&',    '\'',
+/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
+        0,       0,      '*',     '+',      0,      '-',     '.',      0,
+/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
+       '0',     '1',     '2',     '3',     '4',     '5',     '6',     '7',
+/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
+       '8',     '9',      0,       0,       0,       0,       0,       0,
+/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
+        0,      'a',     'b',     'c',     'd',     'e',     'f',     'g',
+/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
+       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
+/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
+       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
+/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
+       'x',     'y',     'z',      0,       0,       0,      '^',     '_',
+/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
+       '`',     'a',     'b',     'c',     'd',     'e',     'f',     'g',
+/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
+       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
+/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
+       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
+/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
+       'x',     'y',     'z',      0,      '|',      0,      '~',       0 };
+
+
+static const int8_t unhex[256] =
+  {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
+  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  };
+
+
+#if HTTP_PARSER_STRICT
+# define T(v) 0
+#else
+# define T(v) v
+#endif
+
+
+static const uint8_t normal_url_char[32] = {
+/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
+        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
+/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
+        0    | T(2)   |   0    |   0    | T(16)  |   0    |   0    |   0,
+/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
+        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
+/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
+        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
+/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
+        0    |   2    |   4    |   0    |   16   |   32   |   64   |  128,
+/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |   0,
+/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |   0, };
+
+#undef T
+
+enum state
+  { s_dead = 1 /* important that this is > 0 */
+
+  , s_start_req_or_res
+  , s_res_or_resp_H
+  , s_start_res
+  , s_res_H
+  , s_res_HT
+  , s_res_HTT
+  , s_res_HTTP
+  , s_res_http_major
+  , s_res_http_dot
+  , s_res_http_minor
+  , s_res_http_end
+  , s_res_first_status_code
+  , s_res_status_code
+  , s_res_status_start
+  , s_res_status
+  , s_res_line_almost_done
+
+  , s_start_req
+
+  , s_req_method
+  , s_req_spaces_before_url
+  , s_req_schema
+  , s_req_schema_slash
+  , s_req_schema_slash_slash
+  , s_req_server_start
+  , s_req_server
+  , s_req_server_with_at
+  , s_req_path
+  , s_req_query_string_start
+  , s_req_query_string
+  , s_req_fragment_start
+  , s_req_fragment
+  , s_req_http_start
+  , s_req_http_H
+  , s_req_http_HT
+  , s_req_http_HTT
+  , s_req_http_HTTP
+  , s_req_http_I
+  , s_req_http_IC
+  , s_req_http_major
+  , s_req_http_dot
+  , s_req_http_minor
+  , s_req_http_end
+  , s_req_line_almost_done
+
+  , s_header_field_start
+  , s_header_field
+  , s_header_value_discard_ws
+  , s_header_value_discard_ws_almost_done
+  , s_header_value_discard_lws
+  , s_header_value_start
+  , s_header_value
+  , s_header_value_lws
+
+  , s_header_almost_done
+
+  , s_chunk_size_start
+  , s_chunk_size
+  , s_chunk_parameters
+  , s_chunk_size_almost_done
+
+  , s_headers_almost_done
+  , s_headers_done
+
+  /* Important: 's_headers_done' must be the last 'header' state. All
+   * states beyond this must be 'body' states. It is used for overflow
+   * checking. See the PARSING_HEADER() macro.
+   */
+
+  , s_chunk_data
+  , s_chunk_data_almost_done
+  , s_chunk_data_done
+
+  , s_body_identity
+  , s_body_identity_eof
+
+  , s_message_done
+  };
+
+
+#define PARSING_HEADER(state) (state <= s_headers_done)
+
+
+enum header_states
+  { h_general = 0
+  , h_C
+  , h_CO
+  , h_CON
+
+  , h_matching_connection
+  , h_matching_proxy_connection
+  , h_matching_content_length
+  , h_matching_transfer_encoding
+  , h_matching_upgrade
+
+  , h_connection
+  , h_content_length
+  , h_content_length_num
+  , h_content_length_ws
+  , h_transfer_encoding
+  , h_upgrade
+
+  , h_matching_transfer_encoding_chunked
+  , h_matching_connection_token_start
+  , h_matching_connection_keep_alive
+  , h_matching_connection_close
+  , h_matching_connection_upgrade
+  , h_matching_connection_token
+
+  , h_transfer_encoding_chunked
+  , h_connection_keep_alive
+  , h_connection_close
+  , h_connection_upgrade
+  };
+
+enum http_host_state
+  {
+    s_http_host_dead = 1
+  , s_http_userinfo_start
+  , s_http_userinfo
+  , s_http_host_start
+  , s_http_host_v6_start
+  , s_http_host
+  , s_http_host_v6
+  , s_http_host_v6_end
+  , s_http_host_v6_zone_start
+  , s_http_host_v6_zone
+  , s_http_host_port_start
+  , s_http_host_port
+};
+
+/* Macros for character classes; depends on strict-mode  */
+#define CR                  '\r'
+#define LF                  '\n'
+#define LOWER(c)            (unsigned char)(c | 0x20)
+#define IS_ALPHA(c)         (LOWER(c) >= 'a' && LOWER(c) <= 'z')
+#define IS_NUM(c)           ((c) >= '0' && (c) <= '9')
+#define IS_ALPHANUM(c)      (IS_ALPHA(c) || IS_NUM(c))
+#define IS_HEX(c)           (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
+#define IS_MARK(c)          ((c) == '-' || (c) == '_' || (c) == '.' || \
+  (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
+  (c) == ')')
+#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
+  (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
+  (c) == '$' || (c) == ',')
+
+#define STRICT_TOKEN(c)     ((c == ' ') ? 0 : tokens[(unsigned char)c])
+
+#if HTTP_PARSER_STRICT
+#define TOKEN(c)            STRICT_TOKEN(c)
+#define IS_URL_CHAR(c)      (BIT_AT(normal_url_char, (unsigned char)c))
+#define IS_HOST_CHAR(c)     (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
+#else
+#define TOKEN(c)            tokens[(unsigned char)c]
+#define IS_URL_CHAR(c)                                                         \
+  (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
+#define IS_HOST_CHAR(c)                                                        \
+  (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
+#endif
+
+/**
+ * Verify that a char is a valid visible (printable) US-ASCII
+ * character or %x80-FF
+ **/
+#define IS_HEADER_CHAR(ch)                                                     \
+  (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127))
+
+#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
+
+
+#if HTTP_PARSER_STRICT
+# define STRICT_CHECK(cond)                                          \
+do {                                                                 \
+  if (cond) {                                                        \
+    SET_ERRNO(HPE_STRICT);                                           \
+    goto error;                                                      \
+  }                                                                  \
+} while (0)
+# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
+#else
+# define STRICT_CHECK(cond)
+# define NEW_MESSAGE() start_state
+#endif
+
+
+/* Map errno values to strings for human-readable output */
+#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s },
+static struct {
+  const char *name;
+  const char *description;
+} http_strerror_tab[] = {
+  HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)
+};
+#undef HTTP_STRERROR_GEN
+
+int http_message_needs_eof(const http_parser *parser);
+
+/* Our URL parser.
+ *
+ * This is designed to be shared by http_parser_execute() for URL validation,
+ * hence it has a state transition + byte-for-byte interface. In addition, it
+ * is meant to be embedded in http_parser_parse_url(), which does the dirty
+ * work of turning state transitions URL components for its API.
+ *
+ * This function should only be invoked with non-space characters. It is
+ * assumed that the caller cares about (and can detect) the transition between
+ * URL and non-URL states by looking for these.
+ */
+static enum state
+parse_url_char(enum state s, const char ch)
+{
+  if (ch == ' ' || ch == '\r' || ch == '\n') {
+    return s_dead;
+  }
+
+#if HTTP_PARSER_STRICT
+  if (ch == '\t' || ch == '\f') {
+    return s_dead;
+  }
+#endif
+
+  switch (s) {
+    case s_req_spaces_before_url:
+      /* Proxied requests are followed by scheme of an absolute URI (alpha).
+       * All methods except CONNECT are followed by '/' or '*'.
+       */
+
+      if (ch == '/' || ch == '*') {
+        return s_req_path;
+      }
+
+      if (IS_ALPHA(ch)) {
+        return s_req_schema;
+      }
+
+      break;
+
+    case s_req_schema:
+      if (IS_ALPHA(ch)) {
+        return s;
+      }
+
+      if (ch == ':') {
+        return s_req_schema_slash;
+      }
+
+      break;
+
+    case s_req_schema_slash:
+      if (ch == '/') {
+        return s_req_schema_slash_slash;
+      }
+
+      break;
+
+    case s_req_schema_slash_slash:
+      if (ch == '/') {
+        return s_req_server_start;
+      }
+
+      break;
+
+    case s_req_server_with_at:
+      if (ch == '@') {
+        return s_dead;
+      }
+
+    /* fall through */
+    case s_req_server_start:
+    case s_req_server:
+      if (ch == '/') {
+        return s_req_path;
+      }
+
+      if (ch == '?') {
+        return s_req_query_string_start;
+      }
+
+      if (ch == '@') {
+        return s_req_server_with_at;
+      }
+
+      if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
+        return s_req_server;
+      }
+
+      break;
+
+    case s_req_path:
+      if (IS_URL_CHAR(ch)) {
+        return s;
+      }
+
+      switch (ch) {
+        case '?':
+          return s_req_query_string_start;
+
+        case '#':
+          return s_req_fragment_start;
+      }
+
+      break;
+
+    case s_req_query_string_start:
+    case s_req_query_string:
+      if (IS_URL_CHAR(ch)) {
+        return s_req_query_string;
+      }
+
+      switch (ch) {
+        case '?':
+          /* allow extra '?' in query string */
+          return s_req_query_string;
+
+        case '#':
+          return s_req_fragment_start;
+      }
+
+      break;
+
+    case s_req_fragment_start:
+      if (IS_URL_CHAR(ch)) {
+        return s_req_fragment;
+      }
+
+      switch (ch) {
+        case '?':
+          return s_req_fragment;
+
+        case '#':
+          return s;
+      }
+
+      break;
+
+    case s_req_fragment:
+      if (IS_URL_CHAR(ch)) {
+        return s;
+      }
+
+      switch (ch) {
+        case '?':
+        case '#':
+          return s;
+      }
+
+      break;
+
+    default:
+      break;
+  }
+
+  /* We should never fall out of the switch above unless there's an error */
+  return s_dead;
+}
+
+size_t http_parser_execute (http_parser *parser,
+                            const http_parser_settings *settings,
+                            const char *data,
+                            size_t len)
+{
+  char c, ch;
+  int8_t unhex_val;
+  const char *p = data;
+  const char *header_field_mark = 0;
+  const char *header_value_mark = 0;
+  const char *url_mark = 0;
+  const char *body_mark = 0;
+  const char *status_mark = 0;
+  enum state p_state = (enum state) parser->state;
+  const unsigned int lenient = parser->lenient_http_headers;
+  uint32_t nread = parser->nread;
+
+  /* We're in an error state. Don't bother doing anything. */
+  if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+    return 0;
+  }
+
+  if (len == 0) {
+    switch (CURRENT_STATE()) {
+      case s_body_identity_eof:
+        /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
+         * we got paused.
+         */
+        CALLBACK_NOTIFY_NOADVANCE(message_complete);
+        return 0;
+
+      case s_dead:
+      case s_start_req_or_res:
+      case s_start_res:
+      case s_start_req:
+        return 0;
+
+      default:
+        SET_ERRNO(HPE_INVALID_EOF_STATE);
+        return 1;
+    }
+  }
+
+
+  if (CURRENT_STATE() == s_header_field)
+    header_field_mark = data;
+  if (CURRENT_STATE() == s_header_value)
+    header_value_mark = data;
+  switch (CURRENT_STATE()) {
+  case s_req_path:
+  case s_req_schema:
+  case s_req_schema_slash:
+  case s_req_schema_slash_slash:
+  case s_req_server_start:
+  case s_req_server:
+  case s_req_server_with_at:
+  case s_req_query_string_start:
+  case s_req_query_string:
+  case s_req_fragment_start:
+  case s_req_fragment:
+    url_mark = data;
+    break;
+  case s_res_status:
+    status_mark = data;
+    break;
+  default:
+    break;
+  }
+
+  for (p=data; p != data + len; p++) {
+    ch = *p;
+
+    if (PARSING_HEADER(CURRENT_STATE()))
+      COUNT_HEADER_SIZE(1);
+
+reexecute:
+    switch (CURRENT_STATE()) {
+
+      case s_dead:
+        /* this state is used after a 'Connection: close' message
+         * the parser will error out if it reads another message
+         */
+        if (LIKELY(ch == CR || ch == LF))
+          break;
+
+        SET_ERRNO(HPE_CLOSED_CONNECTION);
+        goto error;
+
+      case s_start_req_or_res:
+      {
+        if (ch == CR || ch == LF)
+          break;
+        parser->flags = 0;
+        parser->content_length = ULLONG_MAX;
+
+        if (ch == 'H') {
+          UPDATE_STATE(s_res_or_resp_H);
+
+          CALLBACK_NOTIFY(message_begin);
+        } else {
+          parser->type = HTTP_REQUEST;
+          UPDATE_STATE(s_start_req);
+          REEXECUTE();
+        }
+
+        break;
+      }
+
+      case s_res_or_resp_H:
+        if (ch == 'T') {
+          parser->type = HTTP_RESPONSE;
+          UPDATE_STATE(s_res_HT);
+        } else {
+          if (UNLIKELY(ch != 'E')) {
+            SET_ERRNO(HPE_INVALID_CONSTANT);
+            goto error;
+          }
+
+          parser->type = HTTP_REQUEST;
+          parser->method = HTTP_HEAD;
+          parser->index = 2;
+          UPDATE_STATE(s_req_method);
+        }
+        break;
+
+      case s_start_res:
+      {
+        if (ch == CR || ch == LF)
+          break;
+        parser->flags = 0;
+        parser->content_length = ULLONG_MAX;
+
+        if (ch == 'H') {
+          UPDATE_STATE(s_res_H);
+        } else {
+          SET_ERRNO(HPE_INVALID_CONSTANT);
+          goto error;
+        }
+
+        CALLBACK_NOTIFY(message_begin);
+        break;
+      }
+
+      case s_res_H:
+        STRICT_CHECK(ch != 'T');
+        UPDATE_STATE(s_res_HT);
+        break;
+
+      case s_res_HT:
+        STRICT_CHECK(ch != 'T');
+        UPDATE_STATE(s_res_HTT);
+        break;
+
+      case s_res_HTT:
+        STRICT_CHECK(ch != 'P');
+        UPDATE_STATE(s_res_HTTP);
+        break;
+
+      case s_res_HTTP:
+        STRICT_CHECK(ch != '/');
+        UPDATE_STATE(s_res_http_major);
+        break;
+
+      case s_res_http_major:
+        if (UNLIKELY(!IS_NUM(ch))) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_major = ch - '0';
+        UPDATE_STATE(s_res_http_dot);
+        break;
+
+      case s_res_http_dot:
+      {
+        if (UNLIKELY(ch != '.')) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        UPDATE_STATE(s_res_http_minor);
+        break;
+      }
+
+      case s_res_http_minor:
+        if (UNLIKELY(!IS_NUM(ch))) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_minor = ch - '0';
+        UPDATE_STATE(s_res_http_end);
+        break;
+
+      case s_res_http_end:
+      {
+        if (UNLIKELY(ch != ' ')) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        UPDATE_STATE(s_res_first_status_code);
+        break;
+      }
+
+      case s_res_first_status_code:
+      {
+        if (!IS_NUM(ch)) {
+          if (ch == ' ') {
+            break;
+          }
+
+          SET_ERRNO(HPE_INVALID_STATUS);
+          goto error;
+        }
+        parser->status_code = ch - '0';
+        UPDATE_STATE(s_res_status_code);
+        break;
+      }
+
+      case s_res_status_code:
+      {
+        if (!IS_NUM(ch)) {
+          switch (ch) {
+            case ' ':
+              UPDATE_STATE(s_res_status_start);
+              break;
+            case CR:
+            case LF:
+              UPDATE_STATE(s_res_status_start);
+              REEXECUTE();
+              break;
+            default:
+              SET_ERRNO(HPE_INVALID_STATUS);
+              goto error;
+          }
+          break;
+        }
+
+        parser->status_code *= 10;
+        parser->status_code += ch - '0';
+
+        if (UNLIKELY(parser->status_code > 999)) {
+          SET_ERRNO(HPE_INVALID_STATUS);
+          goto error;
+        }
+
+        break;
+      }
+
+      case s_res_status_start:
+      {
+        MARK(status);
+        UPDATE_STATE(s_res_status);
+        parser->index = 0;
+
+        if (ch == CR || ch == LF)
+          REEXECUTE();
+
+        break;
+      }
+
+      case s_res_status:
+        if (ch == CR) {
+          UPDATE_STATE(s_res_line_almost_done);
+          CALLBACK_DATA(status);
+          break;
+        }
+
+        if (ch == LF) {
+          UPDATE_STATE(s_header_field_start);
+          CALLBACK_DATA(status);
+          break;
+        }
+
+        break;
+
+      case s_res_line_almost_done:
+        STRICT_CHECK(ch != LF);
+        UPDATE_STATE(s_header_field_start);
+        break;
+
+      case s_start_req:
+      {
+        if (ch == CR || ch == LF)
+          break;
+        parser->flags = 0;
+        parser->content_length = ULLONG_MAX;
+
+        if (UNLIKELY(!IS_ALPHA(ch))) {
+          SET_ERRNO(HPE_INVALID_METHOD);
+          goto error;
+        }
+
+        parser->method = (enum http_method) 0;
+        parser->index = 1;
+        switch (ch) {
+          case 'A': parser->method = HTTP_ACL; break;
+          case 'B': parser->method = HTTP_BIND; break;
+          case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
+          case 'D': parser->method = HTTP_DELETE; break;
+          case 'G': parser->method = HTTP_GET; break;
+          case 'H': parser->method = HTTP_HEAD; break;
+          case 'L': parser->method = HTTP_LOCK; /* or LINK */ break;
+          case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break;
+          case 'N': parser->method = HTTP_NOTIFY; break;
+          case 'O': parser->method = HTTP_OPTIONS; break;
+          case 'P': parser->method = HTTP_POST;
+            /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
+            break;
+          case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break;
+          case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH, SOURCE */ break;
+          case 'T': parser->method = HTTP_TRACE; break;
+          case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break;
+          default:
+            SET_ERRNO(HPE_INVALID_METHOD);
+            goto error;
+        }
+        UPDATE_STATE(s_req_method);
+
+        CALLBACK_NOTIFY(message_begin);
+
+        break;
+      }
+
+      case s_req_method:
+      {
+        const char *matcher;
+        if (UNLIKELY(ch == '\0')) {
+          SET_ERRNO(HPE_INVALID_METHOD);
+          goto error;
+        }
+
+        matcher = method_strings[parser->method];
+        if (ch == ' ' && matcher[parser->index] == '\0') {
+          UPDATE_STATE(s_req_spaces_before_url);
+        } else if (ch == matcher[parser->index]) {
+          ; /* nada */
+        } else if ((ch >= 'A' && ch <= 'Z') || ch == '-') {
+
+          switch (parser->method << 16 | parser->index << 8 | ch) {
+#define XX(meth, pos, ch, new_meth) \
+            case (HTTP_##meth << 16 | pos << 8 | ch): \
+              parser->method = HTTP_##new_meth; break;
+
+            XX(POST,      1, 'U', PUT)
+            XX(POST,      1, 'A', PATCH)
+            XX(POST,      1, 'R', PROPFIND)
+            XX(PUT,       2, 'R', PURGE)
+            XX(CONNECT,   1, 'H', CHECKOUT)
+            XX(CONNECT,   2, 'P', COPY)
+            XX(MKCOL,     1, 'O', MOVE)
+            XX(MKCOL,     1, 'E', MERGE)
+            XX(MKCOL,     1, '-', MSEARCH)
+            XX(MKCOL,     2, 'A', MKACTIVITY)
+            XX(MKCOL,     3, 'A', MKCALENDAR)
+            XX(SUBSCRIBE, 1, 'E', SEARCH)
+            XX(SUBSCRIBE, 1, 'O', SOURCE)
+            XX(REPORT,    2, 'B', REBIND)
+            XX(PROPFIND,  4, 'P', PROPPATCH)
+            XX(LOCK,      1, 'I', LINK)
+            XX(UNLOCK,    2, 'S', UNSUBSCRIBE)
+            XX(UNLOCK,    2, 'B', UNBIND)
+            XX(UNLOCK,    3, 'I', UNLINK)
+#undef XX
+            default:
+              SET_ERRNO(HPE_INVALID_METHOD);
+              goto error;
+          }
+        } else {
+          SET_ERRNO(HPE_INVALID_METHOD);
+          goto error;
+        }
+
+        ++parser->index;
+        break;
+      }
+
+      case s_req_spaces_before_url:
+      {
+        if (ch == ' ') break;
+
+        MARK(url);
+        if (parser->method == HTTP_CONNECT) {
+          UPDATE_STATE(s_req_server_start);
+        }
+
+        UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+        if (UNLIKELY(CURRENT_STATE() == s_dead)) {
+          SET_ERRNO(HPE_INVALID_URL);
+          goto error;
+        }
+
+        break;
+      }
+
+      case s_req_schema:
+      case s_req_schema_slash:
+      case s_req_schema_slash_slash:
+      case s_req_server_start:
+      {
+        switch (ch) {
+          /* No whitespace allowed here */
+          case ' ':
+          case CR:
+          case LF:
+            SET_ERRNO(HPE_INVALID_URL);
+            goto error;
+          default:
+            UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+            if (UNLIKELY(CURRENT_STATE() == s_dead)) {
+              SET_ERRNO(HPE_INVALID_URL);
+              goto error;
+            }
+        }
+
+        break;
+      }
+
+      case s_req_server:
+      case s_req_server_with_at:
+      case s_req_path:
+      case s_req_query_string_start:
+      case s_req_query_string:
+      case s_req_fragment_start:
+      case s_req_fragment:
+      {
+        switch (ch) {
+          case ' ':
+            UPDATE_STATE(s_req_http_start);
+            CALLBACK_DATA(url);
+            break;
+          case CR:
+          case LF:
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            UPDATE_STATE((ch == CR) ?
+              s_req_line_almost_done :
+              s_header_field_start);
+            CALLBACK_DATA(url);
+            break;
+          default:
+            UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+            if (UNLIKELY(CURRENT_STATE() == s_dead)) {
+              SET_ERRNO(HPE_INVALID_URL);
+              goto error;
+            }
+        }
+        break;
+      }
+
+      case s_req_http_start:
+        switch (ch) {
+          case ' ':
+            break;
+          case 'H':
+            UPDATE_STATE(s_req_http_H);
+            break;
+          case 'I':
+            if (parser->method == HTTP_SOURCE) {
+              UPDATE_STATE(s_req_http_I);
+              break;
+            }
+            /* fall through */
+          default:
+            SET_ERRNO(HPE_INVALID_CONSTANT);
+            goto error;
+        }
+        break;
+
+      case s_req_http_H:
+        STRICT_CHECK(ch != 'T');
+        UPDATE_STATE(s_req_http_HT);
+        break;
+
+      case s_req_http_HT:
+        STRICT_CHECK(ch != 'T');
+        UPDATE_STATE(s_req_http_HTT);
+        break;
+
+      case s_req_http_HTT:
+        STRICT_CHECK(ch != 'P');
+        UPDATE_STATE(s_req_http_HTTP);
+        break;
+
+      case s_req_http_I:
+        STRICT_CHECK(ch != 'C');
+        UPDATE_STATE(s_req_http_IC);
+        break;
+
+      case s_req_http_IC:
+        STRICT_CHECK(ch != 'E');
+        UPDATE_STATE(s_req_http_HTTP);  /* Treat "ICE" as "HTTP". */
+        break;
+
+      case s_req_http_HTTP:
+        STRICT_CHECK(ch != '/');
+        UPDATE_STATE(s_req_http_major);
+        break;
+
+      case s_req_http_major:
+        if (UNLIKELY(!IS_NUM(ch))) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_major = ch - '0';
+        UPDATE_STATE(s_req_http_dot);
+        break;
+
+      case s_req_http_dot:
+      {
+        if (UNLIKELY(ch != '.')) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        UPDATE_STATE(s_req_http_minor);
+        break;
+      }
+
+      case s_req_http_minor:
+        if (UNLIKELY(!IS_NUM(ch))) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_minor = ch - '0';
+        UPDATE_STATE(s_req_http_end);
+        break;
+
+      case s_req_http_end:
+      {
+        if (ch == CR) {
+          UPDATE_STATE(s_req_line_almost_done);
+          break;
+        }
+
+        if (ch == LF) {
+          UPDATE_STATE(s_header_field_start);
+          break;
+        }
+
+        SET_ERRNO(HPE_INVALID_VERSION);
+        goto error;
+        break;
+      }
+
+      /* end of request line */
+      case s_req_line_almost_done:
+      {
+        if (UNLIKELY(ch != LF)) {
+          SET_ERRNO(HPE_LF_EXPECTED);
+          goto error;
+        }
+
+        UPDATE_STATE(s_header_field_start);
+        break;
+      }
+
+      case s_header_field_start:
+      {
+        if (ch == CR) {
+          UPDATE_STATE(s_headers_almost_done);
+          break;
+        }
+
+        if (ch == LF) {
+          /* they might be just sending \n instead of \r\n so this would be
+           * the second \n to denote the end of headers*/
+          UPDATE_STATE(s_headers_almost_done);
+          REEXECUTE();
+        }
+
+        c = TOKEN(ch);
+
+        if (UNLIKELY(!c)) {
+          SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+          goto error;
+        }
+
+        MARK(header_field);
+
+        parser->index = 0;
+        UPDATE_STATE(s_header_field);
+
+        switch (c) {
+          case 'c':
+            parser->header_state = h_C;
+            break;
+
+          case 'p':
+            parser->header_state = h_matching_proxy_connection;
+            break;
+
+          case 't':
+            parser->header_state = h_matching_transfer_encoding;
+            break;
+
+          case 'u':
+            parser->header_state = h_matching_upgrade;
+            break;
+
+          default:
+            parser->header_state = h_general;
+            break;
+        }
+        break;
+      }
+
+      case s_header_field:
+      {
+        const char* start = p;
+        for (; p != data + len; p++) {
+          ch = *p;
+          c = TOKEN(ch);
+
+          if (!c)
+            break;
+
+          switch (parser->header_state) {
+            case h_general: {
+              size_t limit = data + len - p;
+              limit = MIN(limit, max_header_size);
+              while (p+1 < data + limit && TOKEN(p[1])) {
+                p++;
+              }
+              break;
+            }
+
+            case h_C:
+              parser->index++;
+              parser->header_state = (c == 'o' ? h_CO : h_general);
+              break;
+
+            case h_CO:
+              parser->index++;
+              parser->header_state = (c == 'n' ? h_CON : h_general);
+              break;
+
+            case h_CON:
+              parser->index++;
+              switch (c) {
+                case 'n':
+                  parser->header_state = h_matching_connection;
+                  break;
+                case 't':
+                  parser->header_state = h_matching_content_length;
+                  break;
+                default:
+                  parser->header_state = h_general;
+                  break;
+              }
+              break;
+
+            /* connection */
+
+            case h_matching_connection:
+              parser->index++;
+              if (parser->index > sizeof(CONNECTION)-1
+                  || c != CONNECTION[parser->index]) {
+                parser->header_state = h_general;
+              } else if (parser->index == sizeof(CONNECTION)-2) {
+                parser->header_state = h_connection;
+              }
+              break;
+
+            /* proxy-connection */
+
+            case h_matching_proxy_connection:
+              parser->index++;
+              if (parser->index > sizeof(PROXY_CONNECTION)-1
+                  || c != PROXY_CONNECTION[parser->index]) {
+                parser->header_state = h_general;
+              } else if (parser->index == sizeof(PROXY_CONNECTION)-2) {
+                parser->header_state = h_connection;
+              }
+              break;
+
+            /* content-length */
+
+            case h_matching_content_length:
+              parser->index++;
+              if (parser->index > sizeof(CONTENT_LENGTH)-1
+                  || c != CONTENT_LENGTH[parser->index]) {
+                parser->header_state = h_general;
+              } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
+                parser->header_state = h_content_length;
+              }
+              break;
+
+            /* transfer-encoding */
+
+            case h_matching_transfer_encoding:
+              parser->index++;
+              if (parser->index > sizeof(TRANSFER_ENCODING)-1
+                  || c != TRANSFER_ENCODING[parser->index]) {
+                parser->header_state = h_general;
+              } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {
+                parser->header_state = h_transfer_encoding;
+              }
+              break;
+
+            /* upgrade */
+
+            case h_matching_upgrade:
+              parser->index++;
+              if (parser->index > sizeof(UPGRADE)-1
+                  || c != UPGRADE[parser->index]) {
+                parser->header_state = h_general;
+              } else if (parser->index == sizeof(UPGRADE)-2) {
+                parser->header_state = h_upgrade;
+              }
+              break;
+
+            case h_connection:
+            case h_content_length:
+            case h_transfer_encoding:
+            case h_upgrade:
+              if (ch != ' ') parser->header_state = h_general;
+              break;
+
+            default:
+              assert(0 && "Unknown header_state");
+              break;
+          }
+        }
+
+        if (p == data + len) {
+          --p;
+          COUNT_HEADER_SIZE(p - start);
+          break;
+        }
+
+        COUNT_HEADER_SIZE(p - start);
+
+        if (ch == ':') {
+          UPDATE_STATE(s_header_value_discard_ws);
+          CALLBACK_DATA(header_field);
+          break;
+        }
+
+        SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+        goto error;
+      }
+
+      case s_header_value_discard_ws:
+        if (ch == ' ' || ch == '\t') break;
+
+        if (ch == CR) {
+          UPDATE_STATE(s_header_value_discard_ws_almost_done);
+          break;
+        }
+
+        if (ch == LF) {
+          UPDATE_STATE(s_header_value_discard_lws);
+          break;
+        }
+
+        /* fall through */
+
+      case s_header_value_start:
+      {
+        MARK(header_value);
+
+        UPDATE_STATE(s_header_value);
+        parser->index = 0;
+
+        c = LOWER(ch);
+
+        switch (parser->header_state) {
+          case h_upgrade:
+            parser->flags |= F_UPGRADE;
+            parser->header_state = h_general;
+            break;
+
+          case h_transfer_encoding:
+            /* looking for 'Transfer-Encoding: chunked' */
+            if ('c' == c) {
+              parser->header_state = h_matching_transfer_encoding_chunked;
+            } else {
+              parser->header_state = h_general;
+            }
+            break;
+
+          case h_content_length:
+            if (UNLIKELY(!IS_NUM(ch))) {
+              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+              goto error;
+            }
+
+            if (parser->flags & F_CONTENTLENGTH) {
+              SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
+              goto error;
+            }
+
+            parser->flags |= F_CONTENTLENGTH;
+            parser->content_length = ch - '0';
+            parser->header_state = h_content_length_num;
+            break;
+
+          /* when obsolete line folding is encountered for content length
+           * continue to the s_header_value state */
+          case h_content_length_ws:
+            break;
+
+          case h_connection:
+            /* looking for 'Connection: keep-alive' */
+            if (c == 'k') {
+              parser->header_state = h_matching_connection_keep_alive;
+            /* looking for 'Connection: close' */
+            } else if (c == 'c') {
+              parser->header_state = h_matching_connection_close;
+            } else if (c == 'u') {
+              parser->header_state = h_matching_connection_upgrade;
+            } else {
+              parser->header_state = h_matching_connection_token;
+            }
+            break;
+
+          /* Multi-value `Connection` header */
+          case h_matching_connection_token_start:
+            break;
+
+          default:
+            parser->header_state = h_general;
+            break;
+        }
+        break;
+      }
+
+      case s_header_value:
+      {
+        const char* start = p;
+        enum header_states h_state = (enum header_states) parser->header_state;
+        for (; p != data + len; p++) {
+          ch = *p;
+          if (ch == CR) {
+            UPDATE_STATE(s_header_almost_done);
+            parser->header_state = h_state;
+            CALLBACK_DATA(header_value);
+            break;
+          }
+
+          if (ch == LF) {
+            UPDATE_STATE(s_header_almost_done);
+            COUNT_HEADER_SIZE(p - start);
+            parser->header_state = h_state;
+            CALLBACK_DATA_NOADVANCE(header_value);
+            REEXECUTE();
+          }
+
+          if (!lenient && !IS_HEADER_CHAR(ch)) {
+            SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+            goto error;
+          }
+
+          c = LOWER(ch);
+
+          switch (h_state) {
+            case h_general:
+              {
+                const char* limit = p + MIN(data + len - p, max_header_size);
+
+                for (; p != limit; p++) {
+                  ch = *p;
+                  if (ch == CR || ch == LF) {
+                    --p;
+                    break;
+                  }
+                  if (!lenient && !IS_HEADER_CHAR(ch)) {
+                    SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+                    goto error;
+                  }
+                }
+                if (p == data + len)
+                  --p;
+                break;
+              }
+
+            case h_connection:
+            case h_transfer_encoding:
+              assert(0 && "Shouldn't get here.");
+              break;
+
+            case h_content_length:
+              if (ch == ' ') break;
+              h_state = h_content_length_num;
+              /* fall through */
+
+            case h_content_length_num:
+            {
+              uint64_t t;
+
+              if (ch == ' ') {
+                h_state = h_content_length_ws;
+                break;
+              }
+
+              if (UNLIKELY(!IS_NUM(ch))) {
+                SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+                parser->header_state = h_state;
+                goto error;
+              }
+
+              t = parser->content_length;
+              t *= 10;
+              t += ch - '0';
+
+              /* Overflow? Test against a conservative limit for simplicity. */
+              if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) {
+                SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+                parser->header_state = h_state;
+                goto error;
+              }
+
+              parser->content_length = t;
+              break;
+            }
+
+            case h_content_length_ws:
+              if (ch == ' ') break;
+              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+              parser->header_state = h_state;
+              goto error;
+
+            /* Transfer-Encoding: chunked */
+            case h_matching_transfer_encoding_chunked:
+              parser->index++;
+              if (parser->index > sizeof(CHUNKED)-1
+                  || c != CHUNKED[parser->index]) {
+                h_state = h_general;
+              } else if (parser->index == sizeof(CHUNKED)-2) {
+                h_state = h_transfer_encoding_chunked;
+              }
+              break;
+
+            case h_matching_connection_token_start:
+              /* looking for 'Connection: keep-alive' */
+              if (c == 'k') {
+                h_state = h_matching_connection_keep_alive;
+              /* looking for 'Connection: close' */
+              } else if (c == 'c') {
+                h_state = h_matching_connection_close;
+              } else if (c == 'u') {
+                h_state = h_matching_connection_upgrade;
+              } else if (STRICT_TOKEN(c)) {
+                h_state = h_matching_connection_token;
+              } else if (c == ' ' || c == '\t') {
+                /* Skip lws */
+              } else {
+                h_state = h_general;
+              }
+              break;
+
+            /* looking for 'Connection: keep-alive' */
+            case h_matching_connection_keep_alive:
+              parser->index++;
+              if (parser->index > sizeof(KEEP_ALIVE)-1
+                  || c != KEEP_ALIVE[parser->index]) {
+                h_state = h_matching_connection_token;
+              } else if (parser->index == sizeof(KEEP_ALIVE)-2) {
+                h_state = h_connection_keep_alive;
+              }
+              break;
+
+            /* looking for 'Connection: close' */
+            case h_matching_connection_close:
+              parser->index++;
+              if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {
+                h_state = h_matching_connection_token;
+              } else if (parser->index == sizeof(CLOSE)-2) {
+                h_state = h_connection_close;
+              }
+              break;
+
+            /* looking for 'Connection: upgrade' */
+            case h_matching_connection_upgrade:
+              parser->index++;
+              if (parser->index > sizeof(UPGRADE) - 1 ||
+                  c != UPGRADE[parser->index]) {
+                h_state = h_matching_connection_token;
+              } else if (parser->index == sizeof(UPGRADE)-2) {
+                h_state = h_connection_upgrade;
+              }
+              break;
+
+            case h_matching_connection_token:
+              if (ch == ',') {
+                h_state = h_matching_connection_token_start;
+                parser->index = 0;
+              }
+              break;
+
+            case h_transfer_encoding_chunked:
+              if (ch != ' ') h_state = h_general;
+              break;
+
+            case h_connection_keep_alive:
+            case h_connection_close:
+            case h_connection_upgrade:
+              if (ch == ',') {
+                if (h_state == h_connection_keep_alive) {
+                  parser->flags |= F_CONNECTION_KEEP_ALIVE;
+                } else if (h_state == h_connection_close) {
+                  parser->flags |= F_CONNECTION_CLOSE;
+                } else if (h_state == h_connection_upgrade) {
+                  parser->flags |= F_CONNECTION_UPGRADE;
+                }
+                h_state = h_matching_connection_token_start;
+                parser->index = 0;
+              } else if (ch != ' ') {
+                h_state = h_matching_connection_token;
+              }
+              break;
+
+            default:
+              UPDATE_STATE(s_header_value);
+              h_state = h_general;
+              break;
+          }
+        }
+        parser->header_state = h_state;
+
+        if (p == data + len)
+          --p;
+
+        COUNT_HEADER_SIZE(p - start);
+        break;
+      }
+
+      case s_header_almost_done:
+      {
+        if (UNLIKELY(ch != LF)) {
+          SET_ERRNO(HPE_LF_EXPECTED);
+          goto error;
+        }
+
+        UPDATE_STATE(s_header_value_lws);
+        break;
+      }
+
+      case s_header_value_lws:
+      {
+        if (ch == ' ' || ch == '\t') {
+          if (parser->header_state == h_content_length_num) {
+              /* treat obsolete line folding as space */
+              parser->header_state = h_content_length_ws;
+          }
+          UPDATE_STATE(s_header_value_start);
+          REEXECUTE();
+        }
+
+        /* finished the header */
+        switch (parser->header_state) {
+          case h_connection_keep_alive:
+            parser->flags |= F_CONNECTION_KEEP_ALIVE;
+            break;
+          case h_connection_close:
+            parser->flags |= F_CONNECTION_CLOSE;
+            break;
+          case h_transfer_encoding_chunked:
+            parser->flags |= F_CHUNKED;
+            break;
+          case h_connection_upgrade:
+            parser->flags |= F_CONNECTION_UPGRADE;
+            break;
+          default:
+            break;
+        }
+
+        UPDATE_STATE(s_header_field_start);
+        REEXECUTE();
+      }
+
+      case s_header_value_discard_ws_almost_done:
+      {
+        STRICT_CHECK(ch != LF);
+        UPDATE_STATE(s_header_value_discard_lws);
+        break;
+      }
+
+      case s_header_value_discard_lws:
+      {
+        if (ch == ' ' || ch == '\t') {
+          UPDATE_STATE(s_header_value_discard_ws);
+          break;
+        } else {
+          switch (parser->header_state) {
+            case h_connection_keep_alive:
+              parser->flags |= F_CONNECTION_KEEP_ALIVE;
+              break;
+            case h_connection_close:
+              parser->flags |= F_CONNECTION_CLOSE;
+              break;
+            case h_connection_upgrade:
+              parser->flags |= F_CONNECTION_UPGRADE;
+              break;
+            case h_transfer_encoding_chunked:
+              parser->flags |= F_CHUNKED;
+              break;
+            case h_content_length:
+              /* do not allow empty content length */
+              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+              goto error;
+              break;
+            default:
+              break;
+          }
+
+          /* header value was empty */
+          MARK(header_value);
+          UPDATE_STATE(s_header_field_start);
+          CALLBACK_DATA_NOADVANCE(header_value);
+          REEXECUTE();
+        }
+      }
+
+      case s_headers_almost_done:
+      {
+        STRICT_CHECK(ch != LF);
+
+        if (parser->flags & F_TRAILING) {
+          /* End of a chunked request */
+          UPDATE_STATE(s_message_done);
+          CALLBACK_NOTIFY_NOADVANCE(chunk_complete);
+          REEXECUTE();
+        }
+
+        /* Cannot use chunked encoding and a content-length header together
+           per the HTTP specification. */
+        if ((parser->flags & F_CHUNKED) &&
+            (parser->flags & F_CONTENTLENGTH)) {
+          SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
+          goto error;
+        }
+
+        UPDATE_STATE(s_headers_done);
+
+        /* Set this here so that on_headers_complete() callbacks can see it */
+        if ((parser->flags & F_UPGRADE) &&
+            (parser->flags & F_CONNECTION_UPGRADE)) {
+          /* For responses, "Upgrade: foo" and "Connection: upgrade" are
+           * mandatory only when it is a 101 Switching Protocols response,
+           * otherwise it is purely informational, to announce support.
+           */
+          parser->upgrade =
+              (parser->type == HTTP_REQUEST || parser->status_code == 101);
+        } else {
+          parser->upgrade = (parser->method == HTTP_CONNECT);
+        }
+
+        /* Here we call the headers_complete callback. This is somewhat
+         * different than other callbacks because if the user returns 1, we
+         * will interpret that as saying that this message has no body. This
+         * is needed for the annoying case of recieving a response to a HEAD
+         * request.
+         *
+         * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
+         * we have to simulate it by handling a change in errno below.
+         */
+        if (settings->on_headers_complete) {
+          switch (settings->on_headers_complete(parser)) {
+            case 0:
+              break;
+
+            case 2:
+              parser->upgrade = 1;
+
+              /* fall through */
+            case 1:
+              parser->flags |= F_SKIPBODY;
+              break;
+
+            default:
+              SET_ERRNO(HPE_CB_headers_complete);
+              RETURN(p - data); /* Error */
+          }
+        }
+
+        if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+          RETURN(p - data);
+        }
+
+        REEXECUTE();
+      }
+
+      case s_headers_done:
+      {
+        int hasBody;
+        STRICT_CHECK(ch != LF);
+
+        parser->nread = 0;
+        nread = 0;
+
+        hasBody = parser->flags & F_CHUNKED ||
+          (parser->content_length > 0 && parser->content_length != ULLONG_MAX);
+        if (parser->upgrade && (parser->method == HTTP_CONNECT ||
+                                (parser->flags & F_SKIPBODY) || !hasBody)) {
+          /* Exit, the rest of the message is in a different protocol. */
+          UPDATE_STATE(NEW_MESSAGE());
+          CALLBACK_NOTIFY(message_complete);
+          RETURN((p - data) + 1);
+        }
+
+        if (parser->flags & F_SKIPBODY) {
+          UPDATE_STATE(NEW_MESSAGE());
+          CALLBACK_NOTIFY(message_complete);
+        } else if (parser->flags & F_CHUNKED) {
+          /* chunked encoding - ignore Content-Length header */
+          UPDATE_STATE(s_chunk_size_start);
+        } else {
+          if (parser->content_length == 0) {
+            /* Content-Length header given but zero: Content-Length: 0\r\n */
+            UPDATE_STATE(NEW_MESSAGE());
+            CALLBACK_NOTIFY(message_complete);
+          } else if (parser->content_length != ULLONG_MAX) {
+            /* Content-Length header given and non-zero */
+            UPDATE_STATE(s_body_identity);
+          } else {
+            if (!http_message_needs_eof(parser)) {
+              /* Assume content-length 0 - read the next */
+              UPDATE_STATE(NEW_MESSAGE());
+              CALLBACK_NOTIFY(message_complete);
+            } else {
+              /* Read body until EOF */
+              UPDATE_STATE(s_body_identity_eof);
+            }
+          }
+        }
+
+        break;
+      }
+
+      case s_body_identity:
+      {
+        uint64_t to_read = MIN(parser->content_length,
+                               (uint64_t) ((data + len) - p));
+
+        assert(parser->content_length != 0
+            && parser->content_length != ULLONG_MAX);
+
+        /* The difference between advancing content_length and p is because
+         * the latter will automaticaly advance on the next loop iteration.
+         * Further, if content_length ends up at 0, we want to see the last
+         * byte again for our message complete callback.
+         */
+        MARK(body);
+        parser->content_length -= to_read;
+        p += to_read - 1;
+
+        if (parser->content_length == 0) {
+          UPDATE_STATE(s_message_done);
+
+          /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
+           *
+           * The alternative to doing this is to wait for the next byte to
+           * trigger the data callback, just as in every other case. The
+           * problem with this is that this makes it difficult for the test
+           * harness to distinguish between complete-on-EOF and
+           * complete-on-length. It's not clear that this distinction is
+           * important for applications, but let's keep it for now.
+           */
+          CALLBACK_DATA_(body, p - body_mark + 1, p - data);
+          REEXECUTE();
+        }
+
+        break;
+      }
+
+      /* read until EOF */
+      case s_body_identity_eof:
+        MARK(body);
+        p = data + len - 1;
+
+        break;
+
+      case s_message_done:
+        UPDATE_STATE(NEW_MESSAGE());
+        CALLBACK_NOTIFY(message_complete);
+        if (parser->upgrade) {
+          /* Exit, the rest of the message is in a different protocol. */
+          RETURN((p - data) + 1);
+        }
+        break;
+
+      case s_chunk_size_start:
+      {
+        assert(nread == 1);
+        assert(parser->flags & F_CHUNKED);
+
+        unhex_val = unhex[(unsigned char)ch];
+        if (UNLIKELY(unhex_val == -1)) {
+          SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
+          goto error;
+        }
+
+        parser->content_length = unhex_val;
+        UPDATE_STATE(s_chunk_size);
+        break;
+      }
+
+      case s_chunk_size:
+      {
+        uint64_t t;
+
+        assert(parser->flags & F_CHUNKED);
+
+        if (ch == CR) {
+          UPDATE_STATE(s_chunk_size_almost_done);
+          break;
+        }
+
+        unhex_val = unhex[(unsigned char)ch];
+
+        if (unhex_val == -1) {
+          if (ch == ';' || ch == ' ') {
+            UPDATE_STATE(s_chunk_parameters);
+            break;
+          }
+
+          SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
+          goto error;
+        }
+
+        t = parser->content_length;
+        t *= 16;
+        t += unhex_val;
+
+        /* Overflow? Test against a conservative limit for simplicity. */
+        if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) {
+          SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+          goto error;
+        }
+
+        parser->content_length = t;
+        break;
+      }
+
+      case s_chunk_parameters:
+      {
+        assert(parser->flags & F_CHUNKED);
+        /* just ignore this shit. TODO check for overflow */
+        if (ch == CR) {
+          UPDATE_STATE(s_chunk_size_almost_done);
+          break;
+        }
+        break;
+      }
+
+      case s_chunk_size_almost_done:
+      {
+        assert(parser->flags & F_CHUNKED);
+        STRICT_CHECK(ch != LF);
+
+        parser->nread = 0;
+        nread = 0;
+
+        if (parser->content_length == 0) {
+          parser->flags |= F_TRAILING;
+          UPDATE_STATE(s_header_field_start);
+        } else {
+          UPDATE_STATE(s_chunk_data);
+        }
+        CALLBACK_NOTIFY(chunk_header);
+        break;
+      }
+
+      case s_chunk_data:
+      {
+        uint64_t to_read = MIN(parser->content_length,
+                               (uint64_t) ((data + len) - p));
+
+        assert(parser->flags & F_CHUNKED);
+        assert(parser->content_length != 0
+            && parser->content_length != ULLONG_MAX);
+
+        /* See the explanation in s_body_identity for why the content
+         * length and data pointers are managed this way.
+         */
+        MARK(body);
+        parser->content_length -= to_read;
+        p += to_read - 1;
+
+        if (parser->content_length == 0) {
+          UPDATE_STATE(s_chunk_data_almost_done);
+        }
+
+        break;
+      }
+
+      case s_chunk_data_almost_done:
+        assert(parser->flags & F_CHUNKED);
+        assert(parser->content_length == 0);
+        STRICT_CHECK(ch != CR);
+        UPDATE_STATE(s_chunk_data_done);
+        CALLBACK_DATA(body);
+        break;
+
+      case s_chunk_data_done:
+        assert(parser->flags & F_CHUNKED);
+        STRICT_CHECK(ch != LF);
+        parser->nread = 0;
+        nread = 0;
+        UPDATE_STATE(s_chunk_size_start);
+        CALLBACK_NOTIFY(chunk_complete);
+        break;
+
+      default:
+        assert(0 && "unhandled state");
+        SET_ERRNO(HPE_INVALID_INTERNAL_STATE);
+        goto error;
+    }
+  }
+
+  /* Run callbacks for any marks that we have leftover after we ran out of
+   * bytes. There should be at most one of these set, so it's OK to invoke
+   * them in series (unset marks will not result in callbacks).
+   *
+   * We use the NOADVANCE() variety of callbacks here because 'p' has already
+   * overflowed 'data' and this allows us to correct for the off-by-one that
+   * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'
+   * value that's in-bounds).
+   */
+
+  assert(((header_field_mark ? 1 : 0) +
+          (header_value_mark ? 1 : 0) +
+          (url_mark ? 1 : 0)  +
+          (body_mark ? 1 : 0) +
+          (status_mark ? 1 : 0)) <= 1);
+
+  CALLBACK_DATA_NOADVANCE(header_field);
+  CALLBACK_DATA_NOADVANCE(header_value);
+  CALLBACK_DATA_NOADVANCE(url);
+  CALLBACK_DATA_NOADVANCE(body);
+  CALLBACK_DATA_NOADVANCE(status);
+
+  RETURN(len);
+
+error:
+  if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {
+    SET_ERRNO(HPE_UNKNOWN);
+  }
+
+  RETURN(p - data);
+}
+
+
+/* Does the parser need to see an EOF to find the end of the message? */
+int
+http_message_needs_eof (const http_parser *parser)
+{
+  if (parser->type == HTTP_REQUEST) {
+    return 0;
+  }
+
+  /* See RFC 2616 section 4.4 */
+  if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */
+      parser->status_code == 204 ||     /* No Content */
+      parser->status_code == 304 ||     /* Not Modified */
+      parser->flags & F_SKIPBODY) {     /* response to a HEAD request */
+    return 0;
+  }
+
+  if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {
+    return 0;
+  }
+
+  return 1;
+}
+
+
+int
+http_should_keep_alive (const http_parser *parser)
+{
+  if (parser->http_major > 0 && parser->http_minor > 0) {
+    /* HTTP/1.1 */
+    if (parser->flags & F_CONNECTION_CLOSE) {
+      return 0;
+    }
+  } else {
+    /* HTTP/1.0 or earlier */
+    if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
+      return 0;
+    }
+  }
+
+  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)
+{
+  void *data = parser->data; /* preserve application data */
+  memset(parser, 0, sizeof(*parser));
+  parser->data = data;
+  parser->type = t;
+  parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
+  parser->http_errno = HPE_OK;
+}
+
+void
+http_parser_settings_init(http_parser_settings *settings)
+{
+  memset(settings, 0, sizeof(*settings));
+}
+
+const char *
+http_errno_name(enum http_errno err) {
+  assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab));
+  return http_strerror_tab[err].name;
+}
+
+const char *
+http_errno_description(enum http_errno err) {
+  assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab));
+  return http_strerror_tab[err].description;
+}
+
+static enum http_host_state
+http_parse_host_char(enum http_host_state s, const char ch) {
+  switch(s) {
+    case s_http_userinfo:
+    case s_http_userinfo_start:
+      if (ch == '@') {
+        return s_http_host_start;
+      }
+
+      if (IS_USERINFO_CHAR(ch)) {
+        return s_http_userinfo;
+      }
+      break;
+
+    case s_http_host_start:
+      if (ch == '[') {
+        return s_http_host_v6_start;
+      }
+
+      if (IS_HOST_CHAR(ch)) {
+        return s_http_host;
+      }
+
+      break;
+
+    case s_http_host:
+      if (IS_HOST_CHAR(ch)) {
+        return s_http_host;
+      }
+
+    /* fall through */
+    case s_http_host_v6_end:
+      if (ch == ':') {
+        return s_http_host_port_start;
+      }
+
+      break;
+
+    case s_http_host_v6:
+      if (ch == ']') {
+        return s_http_host_v6_end;
+      }
+
+    /* fall through */
+    case s_http_host_v6_start:
+      if (IS_HEX(ch) || ch == ':' || ch == '.') {
+        return s_http_host_v6;
+      }
+
+      if (s == s_http_host_v6 && ch == '%') {
+        return s_http_host_v6_zone_start;
+      }
+      break;
+
+    case s_http_host_v6_zone:
+      if (ch == ']') {
+        return s_http_host_v6_end;
+      }
+
+    /* fall through */
+    case s_http_host_v6_zone_start:
+      /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */
+      if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' ||
+          ch == '~') {
+        return s_http_host_v6_zone;
+      }
+      break;
+
+    case s_http_host_port:
+    case s_http_host_port_start:
+      if (IS_NUM(ch)) {
+        return s_http_host_port;
+      }
+
+      break;
+
+    default:
+      break;
+  }
+  return s_http_host_dead;
+}
+
+static int
+http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
+  enum http_host_state s;
+
+  const char *p;
+  size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
+
+  assert(u->field_set & (1 << UF_HOST));
+
+  u->field_data[UF_HOST].len = 0;
+
+  s = found_at ? s_http_userinfo_start : s_http_host_start;
+
+  for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
+    enum http_host_state new_s = http_parse_host_char(s, *p);
+
+    if (new_s == s_http_host_dead) {
+      return 1;
+    }
+
+    switch(new_s) {
+      case s_http_host:
+        if (s != s_http_host) {
+          u->field_data[UF_HOST].off = (uint16_t)(p - buf);
+        }
+        u->field_data[UF_HOST].len++;
+        break;
+
+      case s_http_host_v6:
+        if (s != s_http_host_v6) {
+          u->field_data[UF_HOST].off = (uint16_t)(p - buf);
+        }
+        u->field_data[UF_HOST].len++;
+        break;
+
+      case s_http_host_v6_zone_start:
+      case s_http_host_v6_zone:
+        u->field_data[UF_HOST].len++;
+        break;
+
+      case s_http_host_port:
+        if (s != s_http_host_port) {
+          u->field_data[UF_PORT].off = (uint16_t)(p - buf);
+          u->field_data[UF_PORT].len = 0;
+          u->field_set |= (1 << UF_PORT);
+        }
+        u->field_data[UF_PORT].len++;
+        break;
+
+      case s_http_userinfo:
+        if (s != s_http_userinfo) {
+          u->field_data[UF_USERINFO].off = (uint16_t)(p - buf);
+          u->field_data[UF_USERINFO].len = 0;
+          u->field_set |= (1 << UF_USERINFO);
+        }
+        u->field_data[UF_USERINFO].len++;
+        break;
+
+      default:
+        break;
+    }
+    s = new_s;
+  }
+
+  /* Make sure we don't end somewhere unexpected */
+  switch (s) {
+    case s_http_host_start:
+    case s_http_host_v6_start:
+    case s_http_host_v6:
+    case s_http_host_v6_zone_start:
+    case s_http_host_v6_zone:
+    case s_http_host_port_start:
+    case s_http_userinfo:
+    case s_http_userinfo_start:
+      return 1;
+    default:
+      break;
+  }
+
+  return 0;
+}
+
+void
+http_parser_url_init(struct http_parser_url *u) {
+  memset(u, 0, sizeof(*u));
+}
+
+int
+http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
+                      struct http_parser_url *u)
+{
+  enum state s;
+  const char *p;
+  enum http_parser_url_fields uf, old_uf;
+  int found_at = 0;
+
+  if (buflen == 0) {
+    return 1;
+  }
+
+  u->port = u->field_set = 0;
+  s = is_connect ? s_req_server_start : s_req_spaces_before_url;
+  old_uf = UF_MAX;
+
+  for (p = buf; p < buf + buflen; p++) {
+    s = parse_url_char(s, *p);
+
+    /* Figure out the next field that we're operating on */
+    switch (s) {
+      case s_dead:
+        return 1;
+
+      /* Skip delimeters */
+      case s_req_schema_slash:
+      case s_req_schema_slash_slash:
+      case s_req_server_start:
+      case s_req_query_string_start:
+      case s_req_fragment_start:
+        continue;
+
+      case s_req_schema:
+        uf = UF_SCHEMA;
+        break;
+
+      case s_req_server_with_at:
+        found_at = 1;
+
+      /* fall through */
+      case s_req_server:
+        uf = UF_HOST;
+        break;
+
+      case s_req_path:
+        uf = UF_PATH;
+        break;
+
+      case s_req_query_string:
+        uf = UF_QUERY;
+        break;
+
+      case s_req_fragment:
+        uf = UF_FRAGMENT;
+        break;
+
+      default:
+        assert(!"Unexpected state");
+        return 1;
+    }
+
+    /* Nothing's changed; soldier on */
+    if (uf == old_uf) {
+      u->field_data[uf].len++;
+      continue;
+    }
+
+    u->field_data[uf].off = (uint16_t)(p - buf);
+    u->field_data[uf].len = 1;
+
+    u->field_set |= (1 << uf);
+    old_uf = uf;
+  }
+
+  /* host must be present if there is a schema */
+  /* parsing http:///toto will fail */
+  if ((u->field_set & (1 << UF_SCHEMA)) &&
+      (u->field_set & (1 << UF_HOST)) == 0) {
+    return 1;
+  }
+
+  if (u->field_set & (1 << UF_HOST)) {
+    if (http_parse_host(buf, u, found_at) != 0) {
+      return 1;
+    }
+  }
+
+  /* CONNECT requests can only contain "hostname:port" */
+  if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
+    return 1;
+  }
+
+  if (u->field_set & (1 << UF_PORT)) {
+    uint16_t off;
+    uint16_t len;
+    const char* p;
+    const char* end;
+    unsigned long v;
+
+    off = u->field_data[UF_PORT].off;
+    len = u->field_data[UF_PORT].len;
+    end = buf + off + len;
+
+    /* NOTE: The characters are already validated and are in the [0-9] range */
+    assert(off + len <= buflen && "Port number overflow");
+    v = 0;
+    for (p = buf + off; p < end; p++) {
+      v *= 10;
+      v += *p - '0';
+
+      /* Ports have a max value of 2^16 */
+      if (v > 0xffff) {
+        return 1;
+      }
+    }
+
+    u->port = (uint16_t) v;
+  }
+
+  return 0;
+}
+
+void
+http_parser_pause(http_parser *parser, int paused) {
+  /* Users should only be pausing/unpausing a parser that is not in an error
+   * state. In non-debug builds, there's not much that we can do about this
+   * other than ignore it.
+   */
+  if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||
+      HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {
+    uint32_t nread = parser->nread; /* used by the SET_ERRNO macro */
+    SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);
+  } else {
+    assert(0 && "Attempting to pause parser in error state");
+  }
+}
+
+int
+http_body_is_final(const struct http_parser *parser) {
+    return parser->state == s_message_done;
+}
+
+unsigned long
+http_parser_version(void) {
+  return HTTP_PARSER_VERSION_MAJOR * 0x10000 |
+         HTTP_PARSER_VERSION_MINOR * 0x00100 |
+         HTTP_PARSER_VERSION_PATCH * 0x00001;
+}
+
+void
+http_parser_set_max_header_size(uint32_t size) {
+  max_header_size = size;
+}

+ 439 - 0
http/http_parser.h

@@ -0,0 +1,439 @@
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef http_parser_h
+#define http_parser_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Also update SONAME in the Makefile whenever you change these. */
+#define HTTP_PARSER_VERSION_MAJOR 2
+#define HTTP_PARSER_VERSION_MINOR 9
+#define HTTP_PARSER_VERSION_PATCH 1
+
+#include <stddef.h>
+#if defined(_WIN32) && !defined(__MINGW32__) && \
+  (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
+#include <BaseTsd.h>
+typedef __int8 int8_t;
+typedef unsigned __int8 uint8_t;
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+#else
+#include <stdint.h>
+#endif
+
+/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
+ * faster
+ */
+#ifndef HTTP_PARSER_STRICT
+# define HTTP_PARSER_STRICT 1
+#endif
+
+/* Maximium header size allowed. If the macro is not defined
+ * before including this header then the default is used. To
+ * change the maximum header size, define the macro in the build
+ * environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
+ * the effective limit on the size of the header, define the macro
+ * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
+ */
+#ifndef HTTP_MAX_HEADER_SIZE
+# define HTTP_MAX_HEADER_SIZE (80*1024)
+#endif
+
+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.
+ *
+ * The one exception is on_headers_complete. In a HTTP_RESPONSE parser
+ * returning '1' from on_headers_complete will tell the parser that it
+ * should not expect a body. This is used when receiving a response to a
+ * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
+ * chunked' headers that indicate the presence of a body.
+ *
+ * Returning `2` from on_headers_complete will tell parser that it should not
+ * expect neither a body nor any futher responses on this connection. This is
+ * useful for handling responses to a CONNECT request which may not contain
+ * `Upgrade` or `Connection: upgrade` headers.
+ *
+ * http_data_cb does not return data chunks. It will be called arbitrarily
+ * many times for each string. E.G. you might get 10 callbacks for "on_url"
+ * each providing just a few characters more data.
+ */
+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
+  , F_CONNECTION_KEEP_ALIVE = 1 << 1
+  , F_CONNECTION_CLOSE      = 1 << 2
+  , F_CONNECTION_UPGRADE    = 1 << 3
+  , F_TRAILING              = 1 << 4
+  , F_UPGRADE               = 1 << 5
+  , F_SKIPBODY              = 1 << 6
+  , F_CONTENTLENGTH         = 1 << 7
+  };
+
+
+/* Map for errno-related constants
+ *
+ * The provided argument should be a macro that takes 2 arguments.
+ */
+#define HTTP_ERRNO_MAP(XX)                                           \
+  /* No error */                                                     \
+  XX(OK, "success")                                                  \
+                                                                     \
+  /* Callback-related errors */                                      \
+  XX(CB_message_begin, "the on_message_begin callback failed")       \
+  XX(CB_url, "the on_url callback failed")                           \
+  XX(CB_header_field, "the on_header_field callback failed")         \
+  XX(CB_header_value, "the on_header_value callback failed")         \
+  XX(CB_headers_complete, "the on_headers_complete callback failed") \
+  XX(CB_body, "the on_body callback failed")                         \
+  XX(CB_message_complete, "the on_message_complete callback failed") \
+  XX(CB_status, "the on_status callback failed")                     \
+  XX(CB_chunk_header, "the on_chunk_header callback failed")         \
+  XX(CB_chunk_complete, "the on_chunk_complete callback failed")     \
+                                                                     \
+  /* Parsing-related errors */                                       \
+  XX(INVALID_EOF_STATE, "stream ended at an unexpected time")        \
+  XX(HEADER_OVERFLOW,                                                \
+     "too many header bytes seen; overflow detected")                \
+  XX(CLOSED_CONNECTION,                                              \
+     "data received after completed connection: close message")      \
+  XX(INVALID_VERSION, "invalid HTTP version")                        \
+  XX(INVALID_STATUS, "invalid HTTP status code")                     \
+  XX(INVALID_METHOD, "invalid HTTP method")                          \
+  XX(INVALID_URL, "invalid URL")                                     \
+  XX(INVALID_HOST, "invalid host")                                   \
+  XX(INVALID_PORT, "invalid port")                                   \
+  XX(INVALID_PATH, "invalid path")                                   \
+  XX(INVALID_QUERY_STRING, "invalid query string")                   \
+  XX(INVALID_FRAGMENT, "invalid fragment")                           \
+  XX(LF_EXPECTED, "LF character expected")                           \
+  XX(INVALID_HEADER_TOKEN, "invalid character in header")            \
+  XX(INVALID_CONTENT_LENGTH,                                         \
+     "invalid character in content-length header")                   \
+  XX(UNEXPECTED_CONTENT_LENGTH,                                      \
+     "unexpected content-length header")                             \
+  XX(INVALID_CHUNK_SIZE,                                             \
+     "invalid character in chunk size header")                       \
+  XX(INVALID_CONSTANT, "invalid constant string")                    \
+  XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
+  XX(STRICT, "strict mode assertion failed")                         \
+  XX(PAUSED, "parser is paused")                                     \
+  XX(UNKNOWN, "an unknown error occurred")
+
+
+/* Define HPE_* values for each errno value above */
+#define HTTP_ERRNO_GEN(n, s) HPE_##n,
+enum http_errno {
+  HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
+};
+#undef HTTP_ERRNO_GEN
+
+
+/* Get an http_errno value from an http_parser */
+#define HTTP_PARSER_ERRNO(p)            ((enum http_errno) (p)->http_errno)
+
+
+struct http_parser {
+  /** PRIVATE **/
+  unsigned int type : 2;         /* enum http_parser_type */
+  unsigned int flags : 8;        /* F_* values from 'flags' enum; semi-public */
+  unsigned int state : 7;        /* enum state from http_parser.c */
+  unsigned int header_state : 7; /* enum header_state from http_parser.c */
+  unsigned int index : 7;        /* index into current matcher */
+  unsigned int lenient_http_headers : 1;
+
+  uint32_t nread;          /* # bytes read in various scenarios */
+  uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
+
+  /** READ-ONLY **/
+  unsigned short http_major;
+  unsigned short http_minor;
+  unsigned int status_code : 16; /* responses only */
+  unsigned int method : 8;       /* requests only */
+  unsigned int http_errno : 7;
+
+  /* 1 = Upgrade header was present and the parser has exited because of that.
+   * 0 = No upgrade header present.
+   * Should be checked when http_parser_execute() returns in addition to
+   * error checking.
+   */
+  unsigned int upgrade : 1;
+
+  /** PUBLIC **/
+  void *data; /* A pointer to get hook to the "connection" or "socket" object */
+};
+
+
+struct http_parser_settings {
+  http_cb      on_message_begin;
+  http_data_cb on_url;
+  http_data_cb on_status;
+  http_data_cb on_header_field;
+  http_data_cb on_header_value;
+  http_cb      on_headers_complete;
+  http_data_cb on_body;
+  http_cb      on_message_complete;
+  /* When on_chunk_header is called, the current chunk length is stored
+   * in parser->content_length.
+   */
+  http_cb      on_chunk_header;
+  http_cb      on_chunk_complete;
+};
+
+
+enum http_parser_url_fields
+  { UF_SCHEMA           = 0
+  , UF_HOST             = 1
+  , UF_PORT             = 2
+  , UF_PATH             = 3
+  , UF_QUERY            = 4
+  , UF_FRAGMENT         = 5
+  , UF_USERINFO         = 6
+  , UF_MAX              = 7
+  };
+
+
+/* Result structure for http_parser_parse_url().
+ *
+ * Callers should index into field_data[] with UF_* values iff field_set
+ * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
+ * because we probably have padding left over), we convert any port to
+ * a uint16_t.
+ */
+struct http_parser_url {
+  uint16_t field_set;           /* Bitmask of (1 << UF_*) values */
+  uint16_t port;                /* Converted UF_PORT string */
+
+  struct {
+    uint16_t off;               /* Offset into buffer in which field starts */
+    uint16_t len;               /* Length of run in buffer */
+  } field_data[UF_MAX];
+};
+
+
+/* Returns the library version. Bits 16-23 contain the major version number,
+ * bits 8-15 the minor version number and bits 0-7 the patch level.
+ * Usage example:
+ *
+ *   unsigned long version = http_parser_version();
+ *   unsigned major = (version >> 16) & 255;
+ *   unsigned minor = (version >> 8) & 255;
+ *   unsigned patch = version & 255;
+ *   printf("http_parser v%u.%u.%u\n", major, minor, patch);
+ */
+unsigned long http_parser_version(void);
+
+void http_parser_init(http_parser *parser, enum http_parser_type type);
+
+
+/* Initialize http_parser_settings members to 0
+ */
+void http_parser_settings_init(http_parser_settings *settings);
+
+
+/* Executes the parser. Returns number of parsed bytes. Sets
+ * `parser->http_errno` on error. */
+size_t http_parser_execute(http_parser *parser,
+                           const http_parser_settings *settings,
+                           const char *data,
+                           size_t len);
+
+
+/* If http_should_keep_alive() in the on_headers_complete or
+ * on_message_complete callback returns 0, then this should be
+ * the last message on the connection.
+ * If you are the server, respond with the "Connection: close" header.
+ * If you are the client, close the connection.
+ */
+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);
+
+/* Return a string description of the given error */
+const char *http_errno_description(enum http_errno err);
+
+/* Initialize all http_parser_url members to 0 */
+void http_parser_url_init(struct http_parser_url *u);
+
+/* Parse a URL; return nonzero on failure */
+int http_parser_parse_url(const char *buf, size_t buflen,
+                          int is_connect,
+                          struct http_parser_url *u);
+
+/* Pause or un-pause the parser; a nonzero value pauses */
+void http_parser_pause(http_parser *parser, int paused);
+
+/* Checks if this is the final chunk of the body. */
+int http_body_is_final(const http_parser *parser);
+
+/* Change the maximum header size provided at compile time. */
+void http_parser_set_max_header_size(uint32_t size);
+
+#ifdef __cplusplus
+}
+#endif
+#endif

+ 254 - 0
http/httpd.cpp.demo

@@ -0,0 +1,254 @@
+#include "h.h"
+#include "hmain.h"
+#include "HttpServer.h"
+#include "api_test.h"
+#include "httpd_conf.h"
+
+httpd_conf_ctx_t g_conf_ctx;
+HttpService g_http_service;
+
+static void print_version();
+static void print_help();
+
+static int  parse_confile(const char* confile);
+
+// short options
+static const char options[] = "hvc:ts:dp:";
+// long options
+static const option_t long_options[] = {
+    {'h', "help",       NO_ARGUMENT},
+    {'v', "version",    NO_ARGUMENT},
+    {'c', "confile",    REQUIRED_ARGUMENT},
+    {'t', "test",       NO_ARGUMENT},
+    {'s', "signal",     REQUIRED_ARGUMENT},
+    {'d', "daemon",     NO_ARGUMENT},
+    {'p', "port",       REQUIRED_ARGUMENT}
+};
+static const char detail_options[] = R"(
+  -h|--help                 Print this information
+  -v|--version              Print version
+  -c|--confile <confile>    Set configure file, default etc/{program}.conf
+  -t|--test                 Test Configure file and exit
+  -s|--signal <signal>      Send <signal> to process,
+                            <signal>=[start,stop,restart,status,reload]
+  -d|--daemon               Daemonize
+  -p|--port <port>          Set listen port
+)";
+
+void print_version() {
+    printf("%s version %s\n", g_main_ctx.program_name, get_compile_version());
+}
+
+void print_help() {
+    printf("Usage: %s [%s]\n", g_main_ctx.program_name, options);
+    printf("Options:\n%s\n", detail_options);
+}
+
+int parse_confile(const char* confile) {
+    int ret = g_conf_ctx.parser->LoadFromFile(confile);
+    if (ret != 0) {
+        printf("Load confile [%s] failed: %d\n", confile, ret);
+        exit(-40);
+    }
+
+    // logfile
+    string str = g_conf_ctx.parser->GetValue("logfile");
+    if (!str.empty()) {
+        strncpy(g_main_ctx.logfile, str.c_str(), sizeof(g_main_ctx.logfile));
+    }
+    hlog_set_file(g_main_ctx.logfile);
+    // loglevel
+    const char* szLoglevel = g_conf_ctx.parser->GetValue("loglevel").c_str();
+    if (stricmp(szLoglevel, "VERBOSE") == 0) {
+        g_conf_ctx.loglevel = LOG_LEVEL_VERBOSE;
+    } else if (stricmp(szLoglevel, "DEBUG") == 0) {
+        g_conf_ctx.loglevel = LOG_LEVEL_DEBUG;
+    } else if (stricmp(szLoglevel, "INFO") == 0) {
+        g_conf_ctx.loglevel = LOG_LEVEL_INFO;
+    } else if (stricmp(szLoglevel, "WARN") == 0) {
+        g_conf_ctx.loglevel = LOG_LEVEL_WARN;
+    } else if (stricmp(szLoglevel, "ERROR") == 0) {
+        g_conf_ctx.loglevel = LOG_LEVEL_ERROR;
+    } else if (stricmp(szLoglevel, "FATAL") == 0) {
+        g_conf_ctx.loglevel = LOG_LEVEL_FATAL;
+    } else if (stricmp(szLoglevel, "SILENT") == 0) {
+        g_conf_ctx.loglevel = LOG_LEVEL_SILENT;
+    } else {
+        g_conf_ctx.loglevel = LOG_LEVEL_VERBOSE;
+    }
+    hlog_set_level(g_conf_ctx.loglevel);
+    // log_remain_days
+    str = g_conf_ctx.parser->GetValue("log_remain_days");
+    if (!str.empty()) {
+        hlog_set_remain_days(atoi(str.c_str()));
+    }
+    hlogi("%s version: %s", g_main_ctx.program_name, get_compile_version());
+
+    // worker_processes
+    int worker_processes = 0;
+    str = g_conf_ctx.parser->GetValue("worker_processes");
+    if (str.size() != 0) {
+        if (strcmp(str.c_str(), "auto") == 0) {
+            worker_processes = get_ncpu();
+            hlogd("worker_processes=ncpu=%d", worker_processes);
+        }
+        else {
+            worker_processes = atoi(str.c_str());
+        }
+    }
+    g_conf_ctx.worker_processes = LIMIT(0, worker_processes, MAXNUM_WORKER_PROCESSES);
+
+    // port
+    int port = 0;
+    const char* szPort = get_arg("p");
+    if (szPort) {
+        port = atoi(szPort);
+    }
+    if (port == 0) {
+        port = atoi(g_conf_ctx.parser->GetValue("port").c_str());
+    }
+    if (port == 0) {
+        printf("Please config listen port!\n");
+        exit(-10);
+    }
+    g_conf_ctx.port = port;
+
+    // http server
+    // base_url
+    str = g_conf_ctx.parser->GetValue("base_url");
+    if (str.size() != 0) {
+        g_http_service.base_url = str;
+    }
+    // document_root
+    str = g_conf_ctx.parser->GetValue("document_root");
+    if (str.size() != 0) {
+        g_http_service.document_root = str;
+    }
+    // home_page
+    str = g_conf_ctx.parser->GetValue("home_page");
+    if (str.size() != 0) {
+        g_http_service.home_page = str;
+    }
+    // error_page
+    str = g_conf_ctx.parser->GetValue("error_page");
+    if (str.size() != 0) {
+        g_http_service.error_page = str;
+    }
+    // file_stat_interval
+    str = g_conf_ctx.parser->GetValue("file_stat_interval");
+    if (str.size() != 0) {
+        g_conf_ctx.file_stat_interval = MAX(10, atoi(str.c_str()));
+    }
+    // file_cached_time
+    str = g_conf_ctx.parser->GetValue("file_cached_time");
+    if (str.size() != 0) {
+        g_conf_ctx.file_cached_time = MAX(60, atoi(str.c_str()));
+    }
+
+    return 0;
+}
+
+static void on_reload(void* userdata) {
+    hlogi("reload confile [%s]", g_main_ctx.confile);
+    parse_confile(g_main_ctx.confile);
+}
+
+int main(int argc, char** argv) {
+    // g_main_ctx
+    main_ctx_init(argc, argv);
+    //int ret = parse_opt(argc, argv, options);
+    int ret = parse_opt_long(argc, argv, long_options, ARRAY_SIZE(long_options));
+    if (ret != 0) {
+        print_help();
+        exit(ret);
+    }
+
+    /*
+    printf("---------------arg------------------------------\n");
+    printf("%s\n", g_main_ctx.cmdline);
+    for (auto& pair : g_main_ctx.arg_kv) {
+        printf("%s=%s\n", pair.first.c_str(), pair.second.c_str());
+    }
+    for (auto& item : g_main_ctx.arg_list) {
+        printf("%s\n", item.c_str());
+    }
+    printf("================================================\n");
+    */
+
+    /*
+    printf("---------------env------------------------------\n");
+    for (auto& pair : g_main_ctx.env_kv) {
+        printf("%s=%s\n", pair.first.c_str(), pair.second.c_str());
+    }
+    printf("================================================\n");
+    */
+
+    // help
+    if (get_arg("h")) {
+        print_help();
+        exit(0);
+    }
+
+    // version
+    if (get_arg("v")) {
+        print_version();
+        exit(0);
+    }
+
+    // g_conf_ctx
+    conf_ctx_init(&g_conf_ctx);
+    const char* confile = get_arg("c");
+    if (confile) {
+        strncpy(g_main_ctx.confile, confile, sizeof(g_main_ctx.confile));
+    }
+    parse_confile(g_main_ctx.confile);
+
+    // test
+    if (get_arg("t")) {
+        printf("Test confile [%s] OK!\n", g_main_ctx.confile);
+        exit(0);
+    }
+
+    // signal
+    signal_init(on_reload);
+    const char* signal = get_arg("s");
+    if (signal) {
+        handle_signal(signal);
+    }
+
+#ifdef OS_UNIX
+    // daemon
+    if (get_arg("d")) {
+        // nochdir, noclose
+        int ret = daemon(1, 1);
+        if (ret != 0) {
+            printf("daemon error: %d\n", ret);
+            exit(-10);
+        }
+        // parent process exit after daemon, so pid changed.
+        g_main_ctx.pid = getpid();
+    }
+#endif
+
+    // pidfile
+    create_pidfile();
+
+    // HttpServer
+    HttpServer srv;
+    ret = srv.SetListenPort(g_conf_ctx.port);
+    if (ret < 0) {
+        return ret;
+    }
+    srv.SetWorkerProcesses(g_conf_ctx.worker_processes);
+    //g_http_service.AddApi("query", HTTP_GET, http_api_query);
+    g_http_service.preprocessor = http_api_preprocessor;
+    g_http_service.postprocessor = http_api_postprocessor;
+#define XXX(path, method, handler) \
+    g_http_service.AddApi(path, HTTP_##method, handler);
+    HTTP_API_MAP(XXX)
+#undef XXX
+    srv.RegisterService(&g_http_service);
+    srv.Run();
+
+    return 0;
+}

+ 27 - 0
http/httpd_conf.h

@@ -0,0 +1,27 @@
+#ifndef HTTPD_CONF_H_
+#define HTTPD_CONF_H_
+
+#include "h.h"
+#include "iniparser.h"
+
+typedef struct httpd_conf_ctx_s {
+    IniParser* parser;
+    int loglevel;
+    int worker_processes;
+    int port;
+    int file_stat_interval;
+    int file_cached_time;
+} httpd_conf_ctx_t;
+
+extern httpd_conf_ctx_t g_conf_ctx;
+
+inline void conf_ctx_init(httpd_conf_ctx_t* ctx) {
+    ctx->parser = new IniParser;
+    ctx->loglevel = LOG_LEVEL_DEBUG;
+    ctx->worker_processes = 0;
+    ctx->port = 0;
+    ctx->file_stat_interval = 10;
+    ctx->file_cached_time   = 60;
+}
+
+#endif

+ 306 - 0
http/multipart_parser.c

@@ -0,0 +1,306 @@
+/* Based on node-formidable by Felix Geisendörfer 
+ * Igor Afonov - afonov@gmail.com - 2012
+ * MIT License - http://www.opensource.org/licenses/mit-license.php
+ */
+
+#include "multipart_parser.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+static void multipart_log(const char * format, ...)
+{
+#ifdef DEBUG_MULTIPART
+    va_list args;
+    va_start(args, format);
+
+    fprintf(stderr, "[HTTP_MULTIPART_PARSER] %s:%d: ", __FILE__, __LINE__);
+    vfprintf(stderr, format, args);
+    fprintf(stderr, "\n");
+#endif
+}
+
+#define NOTIFY_CB(FOR)                                                 \
+do {                                                                   \
+  if (p->settings->on_##FOR) {                                         \
+    if (p->settings->on_##FOR(p) != 0) {                               \
+      return i;                                                        \
+    }                                                                  \
+  }                                                                    \
+} while (0)
+
+#define EMIT_DATA_CB(FOR, ptr, len)                                    \
+do {                                                                   \
+  if (p->settings->on_##FOR) {                                         \
+    if (p->settings->on_##FOR(p, ptr, len) != 0) {                     \
+      return i;                                                        \
+    }                                                                  \
+  }                                                                    \
+} while (0)
+
+
+#define LF 10
+#define CR 13
+
+struct multipart_parser {
+  void * data;
+
+  size_t index;
+  size_t boundary_length;
+
+  unsigned char state;
+
+  const multipart_parser_settings* settings;
+
+  char* lookbehind;
+  char multipart_boundary[1];
+};
+
+enum state {
+  s_uninitialized = 1,
+  s_start,
+  s_start_boundary,
+  s_header_field_start,
+  s_header_field,
+  s_headers_almost_done,
+  s_header_value_start,
+  s_header_value,
+  s_header_value_almost_done,
+  s_part_data_start,
+  s_part_data,
+  s_part_data_almost_boundary,
+  s_part_data_boundary,
+  s_part_data_almost_end,
+  s_part_data_end,
+  s_part_data_final_hyphen,
+  s_end
+};
+
+multipart_parser* multipart_parser_init
+    (const char *boundary, const multipart_parser_settings* settings) {
+
+  multipart_parser* p = (multipart_parser*)malloc(sizeof(multipart_parser) +
+                               strlen(boundary) +
+                               strlen(boundary) + 9);
+
+  strcpy(p->multipart_boundary, boundary);
+  p->boundary_length = strlen(boundary);
+
+  p->lookbehind = (p->multipart_boundary + p->boundary_length + 1);
+
+  p->index = 0;
+  p->state = s_start;
+  p->settings = settings;
+
+  return p;
+}
+
+void multipart_parser_free(multipart_parser* p) {
+  free(p);
+}
+
+void multipart_parser_set_data(multipart_parser *p, void *data) {
+    p->data = data;
+}
+
+void *multipart_parser_get_data(multipart_parser *p) {
+    return p->data;
+}
+
+size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len) {
+  size_t i = 0;
+  size_t mark = 0;
+  char c, cl;
+  int is_last = 0;
+
+  while(i < len) {
+    c = buf[i];
+    is_last = (i == (len - 1));
+    switch (p->state) {
+      case s_start:
+        multipart_log("s_start");
+        p->index = 0;
+        p->state = s_start_boundary;
+
+      /* fallthrough */
+      case s_start_boundary:
+        multipart_log("s_start_boundary");
+        if (p->index == p->boundary_length) {
+          if (c != CR) {
+            return i;
+          }
+          p->index++;
+          break;
+        } else if (p->index == (p->boundary_length + 1)) {
+          if (c != LF) {
+            return i;
+          }
+          p->index = 0;
+          NOTIFY_CB(part_data_begin);
+          p->state = s_header_field_start;
+          break;
+        }
+        if (c != p->multipart_boundary[p->index]) {
+          return i;
+        }
+        p->index++;
+        break;
+
+      case s_header_field_start:
+        multipart_log("s_header_field_start");
+        mark = i;
+        p->state = s_header_field;
+
+      /* fallthrough */
+      case s_header_field:
+        multipart_log("s_header_field");
+        if (c == CR) {
+          p->state = s_headers_almost_done;
+          break;
+        }
+
+        if (c == ':') {
+          EMIT_DATA_CB(header_field, buf + mark, i - mark);
+          p->state = s_header_value_start;
+          break;
+        }
+
+        cl = tolower(c);
+        if ((c != '-') && (cl < 'a' || cl > 'z')) {
+          multipart_log("invalid character in header name");
+          return i;
+        }
+        if (is_last)
+            EMIT_DATA_CB(header_field, buf + mark, (i - mark) + 1);
+        break;
+
+      case s_headers_almost_done:
+        multipart_log("s_headers_almost_done");
+        if (c != LF) {
+          return i;
+        }
+
+        p->state = s_part_data_start;
+        break;
+
+      case s_header_value_start:
+        multipart_log("s_header_value_start");
+        if (c == ' ') {
+          break;
+        }
+
+        mark = i;
+        p->state = s_header_value;
+
+      /* fallthrough */
+      case s_header_value:
+        multipart_log("s_header_value");
+        if (c == CR) {
+          EMIT_DATA_CB(header_value, buf + mark, i - mark);
+          p->state = s_header_value_almost_done;
+          break;
+        }
+        if (is_last)
+            EMIT_DATA_CB(header_value, buf + mark, (i - mark) + 1);
+        break;
+
+      case s_header_value_almost_done:
+        multipart_log("s_header_value_almost_done");
+        if (c != LF) {
+          return i;
+        }
+        p->state = s_header_field_start;
+        break;
+
+      case s_part_data_start:
+        multipart_log("s_part_data_start");
+        NOTIFY_CB(headers_complete);
+        mark = i;
+        p->state = s_part_data;
+
+      /* fallthrough */
+      case s_part_data:
+        multipart_log("s_part_data");
+        if (c == CR) {
+            EMIT_DATA_CB(part_data, buf + mark, i - mark);
+            mark = i;
+            p->state = s_part_data_almost_boundary;
+            p->lookbehind[0] = CR;
+            break;
+        }
+        if (is_last)
+            EMIT_DATA_CB(part_data, buf + mark, (i - mark) + 1);
+        break;
+
+      case s_part_data_almost_boundary:
+        multipart_log("s_part_data_almost_boundary");
+        if (c == LF) {
+            p->state = s_part_data_boundary;
+            p->lookbehind[1] = LF;
+            p->index = 0;
+            break;
+        }
+        EMIT_DATA_CB(part_data, p->lookbehind, 1);
+        p->state = s_part_data;
+        mark = i --;
+        break;
+
+      case s_part_data_boundary:
+        multipart_log("s_part_data_boundary");
+        if (p->multipart_boundary[p->index] != c) {
+          EMIT_DATA_CB(part_data, p->lookbehind, 2 + p->index);
+          p->state = s_part_data;
+          mark = i --;
+          break;
+        }
+        p->lookbehind[2 + p->index] = c;
+        if ((++ p->index) == p->boundary_length) {
+            NOTIFY_CB(part_data_end);
+            p->state = s_part_data_almost_end;
+        }
+        break;
+
+      case s_part_data_almost_end:
+        multipart_log("s_part_data_almost_end");
+        if (c == '-') {
+            p->state = s_part_data_final_hyphen;
+            break;
+        }
+        if (c == CR) {
+            p->state = s_part_data_end;
+            break;
+        }
+        return i;
+
+      case s_part_data_final_hyphen:
+        multipart_log("s_part_data_final_hyphen");
+        if (c == '-') {
+            NOTIFY_CB(body_end);
+            p->state = s_end;
+            break;
+        }
+        return i;
+
+      case s_part_data_end:
+        multipart_log("s_part_data_end");
+        if (c == LF) {
+            p->state = s_header_field_start;
+            NOTIFY_CB(part_data_begin);
+            break;
+        }
+        return i;
+
+      case s_end:
+        multipart_log("s_end: %02X", (int) c);
+        break;
+
+      default:
+        multipart_log("Multipart parser unrecoverable error");
+        return 0;
+    }
+    ++ i;
+  }
+
+  return len;
+}

+ 48 - 0
http/multipart_parser.h

@@ -0,0 +1,48 @@
+/* Based on node-formidable by Felix Geisendörfer 
+ * Igor Afonov - afonov@gmail.com - 2012
+ * MIT License - http://www.opensource.org/licenses/mit-license.php
+ */
+#ifndef _multipart_parser_h
+#define _multipart_parser_h
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stdlib.h>
+#include <ctype.h>
+
+typedef struct multipart_parser multipart_parser;
+typedef struct multipart_parser_settings multipart_parser_settings;
+typedef struct multipart_parser_state multipart_parser_state;
+
+typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length);
+typedef int (*multipart_notify_cb) (multipart_parser*);
+
+struct multipart_parser_settings {
+  multipart_data_cb on_header_field;
+  multipart_data_cb on_header_value;
+  multipart_data_cb on_part_data;
+
+  multipart_notify_cb on_part_data_begin;
+  multipart_notify_cb on_headers_complete;
+  multipart_notify_cb on_part_data_end;
+  multipart_notify_cb on_body_end;
+};
+
+multipart_parser* multipart_parser_init
+    (const char *boundary, const multipart_parser_settings* settings);
+
+void multipart_parser_free(multipart_parser* p);
+
+size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len);
+
+void multipart_parser_set_data(multipart_parser* p, void* data);
+void * multipart_parser_get_data(multipart_parser* p);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif

+ 325 - 0
http/webbench.c.demo

@@ -0,0 +1,325 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h> // for gethostbyname
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <signal.h>
+
+#include <getopt.h>
+#include <unistd.h>
+
+int Connect(const char* host, int port) {
+    struct sockaddr_in addr;
+    addr.sin_family = AF_INET;
+    in_addr_t inaddr = inet_addr(host);
+    if (inaddr != INADDR_NONE) {
+        addr.sin_addr.s_addr = inaddr;
+    }
+    else {
+        struct hostent* phe = gethostbyname(host);
+        if (phe == NULL) {
+            return -1;
+        }
+        memcpy(&addr.sin_addr, phe->h_addr_list[0], phe->h_length);
+    }
+    addr.sin_port = htons(port);
+    int sock = socket(AF_INET, SOCK_STREAM, 0);
+    if (sock < 0) {
+        perror("socket");
+        return -2;
+    }
+    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) <  0) {
+        perror("connect");
+        return -3;
+    }
+    return sock;
+}
+
+#define METHOD_GET      0
+#define METHOD_HEAD     1
+#define METHOD_OPTIONS  2
+#define METHOD_TRACE    3
+
+#define VERSION         "webbench/1.19.3.15"
+
+volatile int timerexpired = 0; // for timer
+int time    = 30;
+int clients = 1;
+char host[64] = {0};
+int  port   = 80;
+char* proxy_host = NULL;
+int proxy_port = 80;
+int method  = METHOD_GET;
+int http    = 1; // 1=HTTP/1.1 0=HTTP/1.0
+const char* url = NULL;
+
+#define REQUEST_SIZE    2048
+char request[REQUEST_SIZE] = {0};
+char buf[1460] = {0};
+
+int mypipe[2]; // IPC
+
+static const char options[] = "?hV01t:p:c:";
+
+static const struct option long_options[] = {
+    {"help", no_argument, NULL, 'h'},
+    {"version", no_argument, NULL, 'V'},
+    {"time", required_argument, NULL, 't'},
+    {"proxy", required_argument, NULL, 'p'},
+    {"clients", required_argument, NULL, 'c'},
+    {"http10", no_argument, NULL, '0'},
+    {"http11", no_argument, NULL, '1'},
+    {"get", no_argument, &method, METHOD_GET},
+    {"head", no_argument, &method, METHOD_HEAD},
+    {"options", no_argument, &method, METHOD_OPTIONS},
+    {"trace", no_argument, &method, METHOD_TRACE},
+    {NULL, 0, NULL, 0}
+};
+
+void print_usage() {
+    printf("Usage: webbench [%s] URL\n", options);
+    puts("\n\
+Options:\n\
+  -?|-h|--help              Print this information.\n\
+  -V|--version              Print version.\n\
+  -0|--http10               Use HTTP/1.0 protocol.\n\
+  -1|--http11               Use HTTP/1.1 protocol.\n\
+  -t|--time <sec>           Run benchmark for <sec> seconds. Default 30.\n\
+  -p|--proxy <server:port>  Use proxy server for request.\n\
+  -c|--clients <n>          Run <n> HTTP clients. Default one.\n\
+  --get                     Use GET request method.\n\
+  --head                    Use HEAD request method.\n\
+  --options                 Use OPTIONS request method.\n\
+  --trace                   Use TRACE request method.\n\
+    ");
+}
+
+int parse_cmdline(int argc, char** argv) {
+    int opt = 0;
+    int opt_idx = 0;
+    while ((opt=getopt_long(argc, argv, options, long_options, &opt_idx)) != EOF) {
+        switch (opt) {
+        case '?':
+        case 'h': print_usage(); exit(1);
+        case 'V': puts(VERSION); exit(1);
+        case '0': http = 0; break;
+        case '1': http = 1; break;
+        case 't': time = atoi(optarg); break;
+        case 'c': clients = atoi(optarg); break;
+        case 'p':
+            {
+                // host:port
+                char* pos = strrchr(optarg, ':');
+                proxy_host = optarg;
+                if (pos == NULL) break;
+                if (pos == optarg ||
+                    pos == optarg + strlen(optarg) - 1) {
+                    printf("Error option --proxy\n");
+                    return -2;
+                }
+                *pos = '\0';
+                proxy_port = atoi(pos+1);
+            }
+            break;
+        }
+    }
+
+    if (optind == argc) {
+        printf("Missing URL\n");
+        return -2;
+    }
+
+    url = argv[optind];
+
+    return 0;
+}
+
+static void alarm_handler(int singal) {
+    timerexpired = 1;
+}
+
+int main(int argc, char** argv) {
+    if (argc == 1) {
+        print_usage();
+        return 2;
+    }
+
+    int ret = parse_cmdline(argc, argv);
+    if (ret != 0) {
+        return ret;
+    }
+
+    printf("%d clients, running %d sec\n", clients, time);
+
+    // domain port url
+    const char* req_url = "/";
+    if (proxy_host) {
+        strncpy(host, proxy_host, sizeof(host));
+        port = proxy_port;
+    } else {
+        // http://host:port/path
+        const char* pos1 = strstr(url, "http://");
+        if (pos1 == NULL) {
+            pos1 = url;
+        } else {
+            pos1 += strlen("http://");
+        }
+        const char* pos2 = strchr(pos1, '/');
+        if (pos2 == NULL) {
+            pos2 = url + strlen(url);
+        } else {
+            req_url = pos2;
+        }
+        int len = pos2 - pos1;
+        char* server = (char*)malloc(len+1);
+        memcpy(server, pos1, len);
+        server[len] = '\0';
+        char* pos3 = strrchr(server, ':');
+        if (pos3 == NULL) {
+            port = 80;
+        } else {
+            *pos3 = '\0';
+            port = atoi(pos3+1);
+        }
+        strncpy(host, server, sizeof(host));
+        free(server);
+    }
+    printf("server %s:%d\n", host, port);
+
+    // test connect
+    int sock = Connect(host, port);
+    if (sock < 0) {
+        printf("Connect failed!\n");
+        return -20;
+    } else {
+        printf("Connect test OK!\n");
+        close(sock);
+    }
+
+    // build request
+    switch (method) {
+    case METHOD_GET:
+    default:
+        strcpy(request, "GET");
+        break;
+    case METHOD_HEAD:
+        strcpy(request, "HEAD");
+        break;
+    case METHOD_OPTIONS:
+        strcpy(request, "OPTIONS");
+        break;
+    case METHOD_TRACE:
+        strcpy(request, "TRACE");
+        break;
+    }
+
+    strcat(request, " ");
+    strcat(request, req_url);
+    strcat(request, " ");
+    if (http == 0) {
+        strcat(request, "HTTP/1.0");
+    } else if (http == 1) {
+        strcat(request, "HTTP/1.1");
+    }
+    strcat(request, "\r\n");
+    strcat(request, "User-Agent: webbench/1.18.3.15\r\n");
+    strcat(request, "Cache-Control: no-cache\r\n");
+    strcat(request, "Connection: close\r\n");
+    strcat(request, "\r\n");
+    printf("%s", request);
+
+    // IPC
+    if (pipe(mypipe) < 0) {
+        perror("pipe");
+        exit(20);
+    }
+
+    // fork childs
+    pid_t pid = 0;
+    FILE* fp = NULL;
+    int succeed = 0, failed = 0, bytes = 0;
+    int childs = clients;
+    for (int i = 0; i < childs; ++i) {
+        pid = fork();
+        if (pid < 0) {
+            perror("fork");
+            exit(-10);
+        }
+
+        if (pid == 0) {
+            // child
+            //printf("child[%d] start\n", getpid());
+            signal(SIGALRM, alarm_handler);
+            alarm(time);
+            int sock = -1;
+loop:
+            while (1) {
+                if (timerexpired) break;
+                sock = Connect(host, port);
+                if (sock <= 0) {
+                    ++failed;
+                    continue;
+                }
+                int len = strlen(request);
+                int wrbytes = write(sock, request, len);
+                //printf("write %d bytes\n", wrbytes);
+                if (wrbytes != len) {
+                    ++failed;
+                    sock = -1;
+                    continue;
+                }
+                while (1) {
+                    if (timerexpired) break;
+                    int rdbytes = read(sock, buf, sizeof(buf));
+                    //printf("read %d bytes\n", rdbytes);
+                    if (rdbytes < 0) {
+                        ++failed;
+                        sock = -1;
+                        goto loop;
+                    }
+                    if (rdbytes == 0) break;
+                    bytes += rdbytes;
+                }
+                close(sock);
+                ++succeed;
+            }
+
+            fp = fdopen(mypipe[1], "w");
+            if (fp == NULL) {
+                perror("fdopen");
+                return 30;
+            }
+            fprintf(fp, "%d %d %d\n", succeed, failed, bytes);
+            fclose(fp);
+            //printf("child[%d] end\n", getpid());
+            return 0;
+        }
+    }
+
+    fp = fdopen(mypipe[0], "r");
+    if (fp == NULL) {
+        perror("fdopen");
+        return 30;
+    }
+    while (1) {
+        int i,j,k;
+        fscanf(fp, "%d %d %d", &i, &j, &k);
+        succeed += i;
+        failed += j;
+        bytes += k;
+        if (--childs==0) break;
+    }
+    fclose(fp);
+    printf("recv %d bytes/sec, %d succeed, %d failed\n",
+            (int)(bytes)/time,
+            succeed,
+            failed);
+
+    return 0;
+}

+ 301 - 0
utils/md5.c

@@ -0,0 +1,301 @@
+
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+ */
+
+#include "md5.h"
+
+/* Constants for MD5Transform routine.
+ */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform(UINT4 [4], unsigned char [64]);
+static void Encode(unsigned char *, UINT4 *, unsigned int);
+static void Decode(UINT4 *, unsigned char *, unsigned int);
+static void MD5_memcpy(POINTER, POINTER, unsigned int);
+static void MD5_memset(POINTER, int, unsigned int);
+
+static unsigned char PADDING[64] = {
+  0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+  }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+  }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+  }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+  }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context.
+ */
+void MD5Init(MD5_CTX *context)  /* context */
+{
+  context->count[0] = context->count[1] = 0;
+  /* Load magic initialization constants.*/
+  context->state[0] = 0x67452301;
+  context->state[1] = 0xefcdab89;
+  context->state[2] = 0x98badcfe;
+  context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+  operation, processing another message block, and updating the
+  context.
+ */
+void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputLen)
+{
+  unsigned int i, index, partLen;
+
+  /* Compute number of bytes mod 64 */
+  index = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+  /* Update number of bits */
+  if ((context->count[0] += ((UINT4)inputLen << 3)) < ((UINT4)inputLen << 3))
+    context->count[1]++;
+  context->count[1] += ((UINT4)inputLen >> 29);
+
+  partLen = 64 - index;
+
+  /* Transform as many times as possible.*/
+  if (inputLen >= partLen) {
+    MD5_memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen);
+    MD5Transform (context->state, context->buffer);
+
+    for (i = partLen; i + 63 < inputLen; i += 64)
+      MD5Transform(context->state, &input[i]);
+
+    index = 0;
+  } else
+    i = 0;
+
+  /* Buffer remaining input */
+  MD5_memcpy((POINTER)&context->buffer[index], (POINTER)&input[i],
+  inputLen-i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+  the message digest and zeroizing the context.
+ */
+void MD5Final (unsigned char digest[16], MD5_CTX *context)
+{
+  unsigned char bits[8];
+  unsigned int index, padLen;
+
+  /* Save number of bits */
+  Encode (bits, context->count, 8);
+
+  /* Pad out to 56 mod 64. */
+  index = (unsigned int)((context->count[0] >> 3) & 0x3f);
+  padLen = (index < 56) ? (56 - index) : (120 - index);
+  MD5Update (context, PADDING, padLen);
+
+  /* Append length (before padding) */
+  MD5Update (context, bits, 8);
+  /* Store state in digest */
+  Encode (digest, context->state, 16);
+
+  /* Zeroize sensitive information. */
+  MD5_memset ((POINTER)context, 0, sizeof (*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block.
+ */
+static void MD5Transform(UINT4 state[4], unsigned char block[64])
+{
+  UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+  Decode (x, block, 64);
+
+  /* Round 1 */
+  FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+  FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+  FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+  FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+  FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+  FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+  FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+  FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+  FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+  FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+  FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+  FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+  FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+  FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+  FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+  FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+  GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+  GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+  GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+  GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+  GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+  GG (d, a, b, c, x[10], S22,  0x2441453); /* 22 */
+  GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+  GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+  GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+  GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+  GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+  GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+  GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+  GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+  GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+  GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+  /* Round 3 */
+  HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+  HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+  HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+  HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+  HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+  HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+  HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+  HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+  HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+  HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+  HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+  HH (b, c, d, a, x[ 6], S34,  0x4881d05); /* 44 */
+  HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+  HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+  HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+  HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+  /* Round 4 */
+  II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+  II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+  II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+  II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+  II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+  II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+  II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+  II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+  II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+  II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+  II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+  II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+  II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+  II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+  II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+  II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+  state[0] += a;
+  state[1] += b;
+  state[2] += c;
+  state[3] += d;
+
+  /* Zeroize sensitive information. */
+  MD5_memset ((POINTER)x, 0, sizeof (x));
+}
+
+/* Encodes input (UINT4) into output (unsigned char). Assumes len is
+  a multiple of 4.
+ */
+static void Encode(unsigned char *output, UINT4 *input, unsigned int len)
+{
+  unsigned int i, j;
+
+  for (i = 0, j = 0; j < len; i++, j += 4) {
+    output[j] = (unsigned char)(input[i] & 0xff);
+    output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+    output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+    output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+  }
+}
+
+/* Decodes input (unsigned char) into output (UINT4). Assumes len is
+  a multiple of 4.
+ */
+static void Decode(UINT4 *output, unsigned char *input, unsigned int len)
+{
+  unsigned int i, j;
+
+  for (i = 0, j = 0; j < len; i++, j += 4)
+    output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
+      (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
+}
+
+/* Note: Replace "for loop" with standard memcpy if possible.
+ */
+static void MD5_memcpy(POINTER output, POINTER input, unsigned int len)
+{
+  unsigned int i;
+
+  for (i = 0; i < len; i++)
+ output[i] = input[i];
+}
+
+/* Note: Replace "for loop" with standard memset if possible.
+ */
+static void MD5_memset(POINTER output, int value, unsigned int len)
+{
+  unsigned int i;
+
+  for (i = 0; i < len; i++)
+    ((char *)output)[i] = (char)value;
+}

+ 51 - 0
utils/md5.h

@@ -0,0 +1,51 @@
+/* MD5.H - header file for MD5C.C
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+ */
+
+/* POINTER defines a generic pointer type */
+typedef unsigned char *POINTER;
+
+/* UINT2 defines a two byte word */
+typedef unsigned short int UINT2;
+
+/* UINT4 defines a four byte word */
+typedef unsigned long int UINT4;
+
+/* MD5 context. */
+typedef struct {
+  UINT4 state[4];                                   /* state (ABCD) */
+  UINT4 count[2];        /* number of bits, modulo 2^64 (lsb first) */
+  unsigned char buffer[64];                         /* input buffer */
+} MD5_CTX;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void MD5Init(MD5_CTX *);
+void MD5Update(MD5_CTX *, unsigned char *, unsigned int);
+void MD5Final(unsigned char [16], MD5_CTX *);
+
+#ifdef __cplusplus
+}
+#endif