hewei.it пре 4 година
родитељ
комит
b25ed82f8c
51 измењених фајлова са 479 додато и 153 уклоњено
  1. 45 0
      .github/workflows/CI.yml
  2. 54 0
      .github/workflows/benchmark.yml
  3. 1 3
      .travis.yml
  4. 1 1
      CMakeLists.txt
  5. 15 4
      README-CN.md
  6. 15 4
      README.md
  7. 1 1
      base/hbase.h
  8. 11 11
      base/hsocket.h
  9. 16 3
      base/hssl.c
  10. 1 0
      base/hssl.h
  11. 4 4
      base/htime.h
  12. 2 2
      base/hversion.h
  13. 2 2
      cpputil/hstring.h
  14. 14 2
      docs/API.md
  15. 2 2
      etc/httpd.conf
  16. 37 0
      etc/nginx.conf
  17. 18 0
      event/hloop.c
  18. 2 1
      event/hloop.h
  19. 15 29
      event/nio.c
  20. 33 1
      evpp/Channel.h
  21. 2 2
      evpp/Event.h
  22. 3 2
      evpp/EventLoop.h
  23. 1 1
      evpp/EventLoopThread.h
  24. 1 1
      evpp/EventLoopThreadPool.h
  25. 1 1
      evpp/EventLoopThread_test.cpp
  26. 1 1
      evpp/TcpClient.h
  27. 1 1
      evpp/UdpClient.h
  28. 1 1
      evpp/UdpServer.h
  29. 9 3
      examples/http_server_test.cpp
  30. 13 6
      examples/httpd/handler.h
  31. 2 2
      examples/httpd/router.h
  32. 26 8
      examples/websocket_server_test.cpp
  33. 3 0
      hexport.h
  34. 5 2
      http/Http1Parser.cpp
  35. 16 4
      http/HttpMessage.cpp
  36. 1 1
      http/HttpMessage.h
  37. 9 5
      http/WebSocketChannel.h
  38. 7 3
      http/WebSocketParser.cpp
  39. 10 3
      http/client/AsyncHttpClient.cpp
  40. 9 4
      http/client/AsyncHttpClient.h
  41. 20 0
      http/client/WebSocketClient.cpp
  42. 8 0
      http/client/WebSocketClient.h
  43. 6 4
      http/client/http_client.cpp
  44. 16 11
      http/client/requests.h
  45. 3 3
      http/http_content.cpp
  46. 3 3
      http/http_content.h
  47. 5 3
      http/server/HttpServer.cpp
  48. 1 1
      http/server/HttpServer.h
  49. 3 3
      http/server/HttpService.h
  50. 2 2
      http/websocket_parser.c
  51. 2 2
      http/wsdef.h

+ 45 - 0
.github/workflows/CI.yml

@@ -0,0 +1,45 @@
+name: CI
+
+on:
+  push:
+    paths-ignore:
+      - '**.md'
+
+  pull_request:
+    paths-ignore:
+      - '**.md'
+
+jobs:
+  build-linux:
+    name: build-linux
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: build
+        run: |
+          sudo apt update
+          sudo apt install libssl-dev libnghttp2-dev
+          ./configure --with-openssl --with-nghttp2
+          make libhv examples unittest evpp
+
+  build-macos:
+    name: build-macos
+    runs-on: macos-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: build
+        run: |
+          ./configure
+          make libhv examples unittest evpp
+
+  build-windows:
+    name: build-windows
+    runs-on: windows-2016
+    steps:
+      - uses: actions/checkout@v2
+      - name: build
+        run: |
+          mkdir win64
+          cd win64
+          cmake .. -G "Visual Studio 15 2017 Win64"
+          cmake --build .

+ 54 - 0
.github/workflows/benchmark.yml

@@ -0,0 +1,54 @@
+name: benchmark
+
+on:
+  push:
+    paths-ignore:
+      - '**.md'
+
+  pull_request:
+    paths-ignore:
+      - '**.md'
+
+jobs:
+  benchmark:
+    name: benchmark
+    runs-on: ubuntu-18.04
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: prepare
+        run: |
+          sudo apt update
+          sudo apt install libssl-dev
+          sudo apt install nginx
+
+      - name: make wrk
+        run: |
+          git clone https://github.com/wg/wrk
+          pushd wrk
+          make WITH_OPENSSL=/usr
+          sudo cp wrk /usr/bin
+          popd
+
+      - name: make httpd
+        run: |
+          ./configure
+          make httpd
+
+      - name: build echo-servers
+        run: |
+          bash echo-servers/build.sh
+
+      - name: benchmark echo-servers
+        run: |
+          bash echo-servers/benchmark.sh
+
+      - name: webbench
+        run: |
+          sudo nginx -c /etc/nginx/nginx.conf
+          sudo cp html/index.html /var/www/html/index.html
+          bin/httpd -c etc/httpd.conf -d
+          ps aux | grep nginx
+          ps aux | grep httpd
+          wrk -c 100 -t 2 -d 10s http://127.0.0.1:80/
+          wrk -c 100 -t 2 -d 10s http://127.0.0.1:8080/

+ 1 - 3
.travis.yml

@@ -8,15 +8,13 @@ jobs:
       env: COVERALLS=no
       script:
         - ./configure
-        - make clean
         - make libhv examples unittest evpp
 
     - os: osx
       compiler: clang
       env: COVERALLS=no
-      scrit:
+      script:
         - ./configure
-        - make clean
         - make libhv examples unittest evpp
 
     - os: windows

+ 1 - 1
CMakeLists.txt

@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.6)
 
-project(hv VERSION 1.1.0)
+project(hv VERSION 1.1.1)
 
 option(BUILD_SHARED "build shared library" ON)
 option(BUILD_STATIC "build static library" ON)

+ 15 - 4
README-CN.md

@@ -2,15 +2,26 @@
 
 # libhv
 
-[![Latest Version](https://img.shields.io/github/release/ithewei/libhv.svg)](https://github.com/ithewei/libhv/releases)
-[![Build Status](https://travis-ci.org/ithewei/libhv.svg?branch=master)](https://travis-ci.org/ithewei/libhv)
-[![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20Windows%20%7C%20Mac-blue)](.travis.yml)
+[![platform](https://img.shields.io/badge/platform-linux%20%7C%20windows%20%7C%20macos-blue)](.github/workflows/CI.yml)
+[![CI](https://github.com/ithewei/libhv/workflows/CI/badge.svg?branch=master)](https://github.com/ithewei/libhv/actions/workflows/CI.yml?query=branch%3Amaster)
+[![benchmark](https://github.com/ithewei/libhv/workflows/benchmark/badge.svg?branch=master)](https://github.com/ithewei/libhv/actions/workflows/benchmark.yml?query=branch%3Amaster)
+<br>
+[![release](https://badgen.net/github/release/ithewei/libhv?icon=github)](https://github.com/ithewei/libhv/releases)
+[![stars](https://badgen.net/github/stars/ithewei/libhv?icon=github)](https://github.com/ithewei/libhv/stargazers)
+[![forks](https://badgen.net/github/forks/ithewei/libhv?icon=github)](https://github.com/ithewei/libhv/network/members)
+[![issues](https://badgen.net/github/issues/ithewei/libhv?icon=github)](https://github.com/ithewei/libhv/issues)
+[![PRs](https://badgen.net/github/prs/ithewei/libhv?icon=github)](https://github.com/ithewei/libhv/pulls)
+[![license](https://badgen.net/github/license/ithewei/libhv?icon=github)](LICENSE)
+<br>
+[![gitee](https://badgen.net/badge/mirror/gitee/red)](https://gitee.com/libhv/libhv)
+[![awesome-c](https://badgen.net/badge/icon/awesome-c/pink?icon=awesome&label&color)](https://github.com/oz123/awesome-c)
+[![awesome-cpp](https://badgen.net/badge/icon/awesome-cpp/pink?icon=awesome&label&color)](https://github.com/fffaraz/awesome-cpp)
 
 `libhv`是一个类似于`libevent、libev、libuv`的跨平台网络库,提供了更简单的接口和更丰富的协议。
 
 ## ✨ 特征
 
-- 跨平台(Linux, Windows, Mac, Solaris)
+- 跨平台(Linux, Windows, MacOS, Solaris)
 - 高性能事件循环(网络IO事件、定时器事件、空闲事件)
 - TCP/UDP服务端/客户端/代理
 - SSL/TLS加密通信(WITH_OPENSSL or WITH_MBEDTLS)

+ 15 - 4
README.md

@@ -2,9 +2,20 @@ English | [中文](README-CN.md)
 
 # libhv
 
-[![Latest Version](https://img.shields.io/github/release/ithewei/libhv.svg)](https://github.com/ithewei/libhv/releases)
-[![Build Status](https://travis-ci.org/ithewei/libhv.svg?branch=master)](https://travis-ci.org/ithewei/libhv)
-[![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20Windows%20%7C%20Mac-blue)](.travis.yml)
+[![platform](https://img.shields.io/badge/platform-linux%20%7C%20windows%20%7C%20macos-blue)](.github/workflows/CI.yml)
+[![CI](https://github.com/ithewei/libhv/workflows/CI/badge.svg?branch=master)](https://github.com/ithewei/libhv/actions/workflows/CI.yml?query=branch%3Amaster)
+[![benchmark](https://github.com/ithewei/libhv/workflows/benchmark/badge.svg?branch=master)](https://github.com/ithewei/libhv/actions/workflows/benchmark.yml?query=branch%3Amaster)
+<br>
+[![release](https://badgen.net/github/release/ithewei/libhv?icon=github)](https://github.com/ithewei/libhv/releases)
+[![stars](https://badgen.net/github/stars/ithewei/libhv?icon=github)](https://github.com/ithewei/libhv/stargazers)
+[![forks](https://badgen.net/github/forks/ithewei/libhv?icon=github)](https://github.com/ithewei/libhv/network/members)
+[![issues](https://badgen.net/github/issues/ithewei/libhv?icon=github)](https://github.com/ithewei/libhv/issues)
+[![PRs](https://badgen.net/github/prs/ithewei/libhv?icon=github)](https://github.com/ithewei/libhv/pulls)
+[![license](https://badgen.net/github/license/ithewei/libhv?icon=github)](LICENSE)
+<br>
+[![gitee](https://badgen.net/badge/mirror/gitee/red)](https://gitee.com/libhv/libhv)
+[![awesome-c](https://badgen.net/badge/icon/awesome-c/pink?icon=awesome&label&color)](https://github.com/oz123/awesome-c)
+[![awesome-cpp](https://badgen.net/badge/icon/awesome-cpp/pink?icon=awesome&label&color)](https://github.com/fffaraz/awesome-cpp)
 
 Like `libevent, libev, and libuv`,
 `libhv` provides event-loop with non-blocking IO and timer,
@@ -12,7 +23,7 @@ but simpler api and richer protocols.
 
 ## ✨ Features
 
-- Cross-platform (Linux, Windows, Mac, Solaris)
+- Cross-platform (Linux, Windows, MacOS, Solaris)
 - EventLoop (IO, timer, idle)
 - TCP/UDP client/server/proxy
 - SSL/TLS support: WITH_OPENSSL or WITH_MBEDTLS

+ 1 - 1
base/hbase.h

@@ -43,7 +43,7 @@ HV_EXPORT void  safe_free(void* ptr);
 // 统计内存分配/释放的次数,以此检查是否有内存未释放
 HV_EXPORT long hv_alloc_cnt();
 HV_EXPORT long hv_free_cnt();
-static inline void hv_memcheck() {
+HV_INLINE void hv_memcheck() {
     printf("Memcheck => alloc:%ld free:%ld\n", hv_alloc_cnt(), hv_free_cnt());
 }
 #define HV_MEMCHECK    atexit(hv_memcheck);

+ 11 - 11
base/hsocket.h

@@ -22,7 +22,7 @@
 
 BEGIN_EXTERN_C
 
-static inline int socket_errno() {
+HV_INLINE int socket_errno() {
 #ifdef OS_WIN
     return WSAGetLastError();
 #else
@@ -34,11 +34,11 @@ HV_EXPORT const char* socket_strerror(int err);
 // 屏蔽一些windows和unix下套接字的差异
 #ifdef OS_WIN
 typedef int socklen_t;
-static inline int blocking(int sockfd) {
+HV_INLINE int blocking(int sockfd) {
     unsigned long nb = 0;
     return ioctlsocket(sockfd, FIONBIO, &nb);
 }
-static inline int nonblocking(int sockfd) {
+HV_INLINE int nonblocking(int sockfd) {
     unsigned long nb = 1;
     return ioctlsocket(sockfd, FIONBIO, &nb);
 }
@@ -86,7 +86,7 @@ HV_EXPORT const char* sockaddr_str(sockaddr_u* addr, char* buf, int len);
 //#define INET6_ADDRSTRLEN  46
 #ifdef ENABLE_UDS
 #define SOCKADDR_STRLEN     sizeof(((struct sockaddr_un*)(NULL))->sun_path)
-static inline void sockaddr_set_path(sockaddr_u* addr, const char* path) {
+HV_INLINE void sockaddr_set_path(sockaddr_u* addr, const char* path) {
     addr->sa.sa_family = AF_UNIX;
     strncpy(addr->sun.sun_path, path, sizeof(addr->sun.sun_path));
 }
@@ -94,7 +94,7 @@ static inline void sockaddr_set_path(sockaddr_u* addr, const char* path) {
 #define SOCKADDR_STRLEN     64 // ipv4:port | [ipv6]:port
 #endif
 
-static inline void sockaddr_print(sockaddr_u* addr) {
+HV_INLINE void sockaddr_print(sockaddr_u* addr) {
     char buf[SOCKADDR_STRLEN] = {0};
     sockaddr_str(addr, buf, sizeof(buf));
     puts(buf);
@@ -134,11 +134,11 @@ HV_EXPORT int ConnectUnixTimeout(const char* path, int ms DEFAULT(DEFAULT_CONNEC
 // Just implement Socketpair(AF_INET, SOCK_STREAM, 0, sv);
 HV_EXPORT int Socketpair(int family, int type, int protocol, int sv[2]);
 
-static inline int tcp_nodelay(int sockfd, int on DEFAULT(1)) {
+HV_INLINE int tcp_nodelay(int sockfd, int on DEFAULT(1)) {
     return setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (const char*)&on, sizeof(int));
 }
 
-static inline int tcp_nopush(int sockfd, int on DEFAULT(1)) {
+HV_INLINE int tcp_nopush(int sockfd, int on DEFAULT(1)) {
 #ifdef TCP_NOPUSH
     return setsockopt(sockfd, IPPROTO_TCP, TCP_NOPUSH, (const char*)&on, sizeof(int));
 #elif defined(TCP_CORK)
@@ -148,7 +148,7 @@ static inline int tcp_nopush(int sockfd, int on DEFAULT(1)) {
 #endif
 }
 
-static inline int tcp_keepalive(int sockfd, int on DEFAULT(1), int delay DEFAULT(60)) {
+HV_INLINE int tcp_keepalive(int sockfd, int on DEFAULT(1), int delay DEFAULT(60)) {
     if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&on, sizeof(int)) != 0) {
         return socket_errno();
     }
@@ -165,12 +165,12 @@ static inline int tcp_keepalive(int sockfd, int on DEFAULT(1), int delay DEFAULT
 #endif
 }
 
-static inline int udp_broadcast(int sockfd, int on DEFAULT(1)) {
+HV_INLINE int udp_broadcast(int sockfd, int on DEFAULT(1)) {
     return setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (const char*)&on, sizeof(int));
 }
 
 // send timeout
-static inline int so_sndtimeo(int sockfd, int timeout) {
+HV_INLINE int so_sndtimeo(int sockfd, int timeout) {
 #ifdef OS_WIN
     return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(int));
 #else
@@ -180,7 +180,7 @@ static inline int so_sndtimeo(int sockfd, int timeout) {
 }
 
 // recv timeout
-static inline int so_rcvtimeo(int sockfd, int timeout) {
+HV_INLINE int so_rcvtimeo(int sockfd, int timeout) {
 #ifdef OS_WIN
     return setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(int));
 #else

+ 16 - 3
base/hssl.c

@@ -49,19 +49,29 @@ hssl_ctx_t hssl_ctx_init(hssl_ctx_init_param_t* param) {
 #endif
     if (ctx == NULL) return NULL;
     int mode = SSL_VERIFY_NONE;
+    const char* ca_file = NULL;
+    const char* ca_path = NULL;
     if (param) {
         if (param->ca_file && *param->ca_file) {
-            if (!SSL_CTX_load_verify_locations(ctx, param->ca_file, NULL)) {
-                fprintf(stderr, "ssl ca_file verify failed!\n");
+            ca_file = param->ca_file;
+        }
+        if (param->ca_path && *param->ca_path) {
+            ca_path = param->ca_path;
+        }
+        if (ca_file || ca_path) {
+            if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_path)) {
+                fprintf(stderr, "ssl ca_file/ca_path failed!\n");
                 goto error;
             }
         }
+
         if (param->crt_file && *param->crt_file) {
             if (!SSL_CTX_use_certificate_file(ctx, param->crt_file, SSL_FILETYPE_PEM)) {
                 fprintf(stderr, "ssl crt_file error!\n");
                 goto error;
             }
         }
+
         if (param->key_file && *param->key_file) {
             if (!SSL_CTX_use_PrivateKey_file(ctx, param->key_file, SSL_FILETYPE_PEM)) {
                 fprintf(stderr, "ssl key_file error!\n");
@@ -71,12 +81,15 @@ hssl_ctx_t hssl_ctx_init(hssl_ctx_init_param_t* param) {
                 fprintf(stderr, "ssl key_file check failed!\n");
                 goto error;
             }
-
         }
+
         if (param->verify_peer) {
             mode = SSL_VERIFY_PEER;
         }
     }
+    if (mode == SSL_VERIFY_PEER && !ca_file && !ca_path) {
+        SSL_CTX_set_default_verify_paths(ctx);
+    }
     SSL_CTX_set_verify(ctx, mode, NULL);
     s_ssl_ctx = ctx;
     return ctx;

+ 1 - 0
base/hssl.h

@@ -21,6 +21,7 @@ typedef struct {
     const char* crt_file;
     const char* key_file;
     const char* ca_file;
+    const char* ca_path;
     short       verify_peer;
     short       endpoint; // 0: server 1: client
 } hssl_ctx_init_param_t;

+ 4 - 4
base/htime.h

@@ -10,7 +10,7 @@ BEGIN_EXTERN_C
 #define SECONDS_PER_DAY     86400   // 24*3600
 #define SECONDS_PER_WEEK    604800  // 7*24*3600
 
-#define IS_LEAP_YEAR(year) (((year)%4 == 0 && (year)%100 != 0) || (year)%100 == 0)
+#define IS_LEAP_YEAR(year) (((year)%4 == 0 && (year)%100 != 0) || (year)%400 == 0)
 
 typedef struct datetime_s {
     int year;
@@ -37,7 +37,7 @@ struct timezone {
 };
 
 #include <sys/timeb.h>
-static inline int gettimeofday(struct timeval *tv, struct timezone *tz) {
+HV_INLINE int gettimeofday(struct timeval *tv, struct timezone *tz) {
     struct _timeb tb;
     _ftime(&tb);
     if (tv) {
@@ -53,12 +53,12 @@ static inline int gettimeofday(struct timeval *tv, struct timezone *tz) {
 #endif
 
 HV_EXPORT unsigned int gettick_ms();
-static inline unsigned long long gettimeofday_ms() {
+HV_INLINE unsigned long long gettimeofday_ms() {
     struct timeval tv;
     gettimeofday(&tv, NULL);
     return tv.tv_sec * (unsigned long long)1000 + tv.tv_usec/1000;
 }
-static inline unsigned long long gettimeofday_us() {
+HV_INLINE unsigned long long gettimeofday_us() {
     struct timeval tv;
     gettimeofday(&tv, NULL);
     return tv.tv_sec * (unsigned long long)1000000 + tv.tv_usec;

+ 2 - 2
base/hversion.h

@@ -8,7 +8,7 @@ BEGIN_EXTERN_C
 
 #define HV_VERSION_MAJOR    1
 #define HV_VERSION_MINOR    1
-#define HV_VERSION_PATCH    0
+#define HV_VERSION_PATCH    1
 
 #define HV_VERSION_STRING   STRINGIFY(HV_VERSION_MAJOR) "." \
                             STRINGIFY(HV_VERSION_MINOR) "." \
@@ -17,7 +17,7 @@ BEGIN_EXTERN_C
 #define HV_VERSION_NUMBER   ((HV_VERSION_MAJOR << 16) | (HV_VERSION_MINOR << 8) | HV_VERSION_PATCH)
 
 
-static inline const char* hv_version() {
+HV_INLINE const char* hv_version() {
     return HV_VERSION_STRING;
 }
 

+ 2 - 2
cpputil/hstring.h

@@ -23,14 +23,14 @@ public:
 namespace hv {
 // NOTE: low-version NDK not provide std::to_string
 template<typename T>
-static inline std::string to_string(const T& t) {
+HV_INLINE std::string to_string(const T& t) {
     std::ostringstream oss;
     oss << t;
     return oss.str();
 }
 
 template<typename T>
-static inline T from_string(const std::string& str) {
+HV_INLINE T from_string(const std::string& str) {
     T t;
     std::istringstream iss(str);
     iss >> t;

+ 14 - 2
docs/API.md

@@ -9,13 +9,12 @@
 - BYTE_ORDER: BIG_ENDIAN, LITTLE_ENDIAN
 - stdbool.h: bool, true, false
 - stdint.h: int8_t, int16_t, int32_t, int64_t
-- var
 - hv_sleep, hv_msleep, hv_usleep, hv_delay
 - hv_mkdir
 - stricmp, strcasecmp
 
 ### hexport.h
-- HV_EXPORT
+- HV_EXPORT, HV_INLINE
 - HV_SOURCE, HV_STATICLIB, HV_DYNAMICLIB
 - HV_DEPRECATED
 - HV_UNUSED
@@ -325,6 +324,8 @@
 - hloop_create_tcp_server
 - hloop_create_udp_client
 - hloop_create_udp_server
+- hloop_create_ssl_client
+- hloop_create_ssl_server
 - hloop_new
 - hloop_free
 - hloop_run
@@ -477,6 +478,17 @@
 - http_client_get_header
 - http_client_clear_headers
 
+### requests.h
+- requests::request
+- requests::get
+- requests::post
+- requests::put
+- requests::patch
+- requests::Delete
+- requests::head
+- requests::options
+- requests::async
+
 ### HttpServer.h
 - http_server_run
 - http_server_stop

+ 2 - 2
etc/httpd.conf

@@ -7,12 +7,12 @@ log_remain_days = 3
 log_filesize = 64M
 
 # worker_processes = auto # auto = ncpu
-worker_processes = 4
+worker_processes = auto
 
 # http server
 http_port = 8080
 https_port = 8443
-#base_url = /v1/api
+#base_url = /api/v1
 document_root = html
 home_page = index.html
 #error_page = error.html

+ 37 - 0
etc/nginx.conf

@@ -0,0 +1,37 @@
+# cd libhv
+# sudo nginx -p . -c etc/nginx.conf
+# bin/httpd -c etc/httpd.conf -s restart -d
+# bin/curl -v http://127.0.0.1/api/v1/get
+
+worker_processes    auto;
+
+pid         logs/nginx.pid;
+error_log   logs/error.log;
+
+events {
+    worker_connections  1024;
+}
+
+http {
+    access_log      logs/access.log;
+
+    server {
+        listen      80;
+
+        # static files service
+        location / {
+            root    html;
+            index   index.html;
+        }
+
+        # autoindex service
+        location /downloads/ {
+            autoindex   on;
+        }
+
+        # api service: nginx => libhv
+        location /api/v1/ {
+            proxy_pass  http://127.0.0.1:8080/;
+        }
+    }
+}

+ 18 - 0
event/hloop.c

@@ -810,6 +810,24 @@ int hio_del(hio_t* io, int events) {
     return 0;
 }
 
+static void hio_close_event_cb(hevent_t* ev) {
+    hio_t* io = (hio_t*)ev->userdata;
+    uint32_t id = (uintptr_t)ev->privdata;
+    if (io->id != id) return;
+    hio_close(io);
+}
+
+int hio_close_async(hio_t* io) {
+    hevent_t ev;
+    memset(&ev, 0, sizeof(ev));
+    ev.cb = hio_close_event_cb;
+    ev.userdata = io;
+    ev.privdata = (void*)(uintptr_t)io->id;
+    ev.priority = HEVENT_HIGH_PRIORITY;
+    hloop_post_event(io->loop, &ev);
+    return 0;
+}
+
 hio_t* hread(hloop_t* loop, int fd, void* buf, size_t len, hread_cb read_cb) {
     hio_t* io = hio_get(loop, fd);
     assert(io != NULL);

+ 2 - 1
event/hloop.h

@@ -99,7 +99,7 @@ typedef enum {
 #define HIO_DEFAULT_CONNECT_TIMEOUT     5000    // ms
 #define HIO_DEFAULT_CLOSE_TIMEOUT       60000   // ms
 #define HIO_DEFAULT_KEEPALIVE_TIMEOUT   75000   // ms
-#define HIO_DEFAULT_HEARTBEAT_INTERVAL  30000   // ms
+#define HIO_DEFAULT_HEARTBEAT_INTERVAL  10000   // ms
 
 BEGIN_EXTERN_C
 
@@ -268,6 +268,7 @@ HV_EXPORT int hio_write  (hio_t* io, const void* buf, size_t len);
 // NOTE: hio_close is thread-safe, if called by other thread, hloop_post_event(hio_close_event).
 // hio_del(io, HV_RDWR) => close => hclose_cb
 HV_EXPORT int hio_close  (hio_t* io);
+HV_EXPORT int hio_close_async(hio_t* io);
 
 //------------------high-level apis-------------------------------------------
 // hio_get -> hio_set_readbuf -> hio_setcb_read -> hio_read

+ 15 - 29
event/nio.c

@@ -129,20 +129,20 @@ static void __close_cb(hio_t* io) {
     }
 }
 
-static void ssl_server_handshark(hio_t* io) {
-    printd("ssl server handshark...\n");
+static void ssl_server_handshake(hio_t* io) {
+    printd("ssl server handshake...\n");
     int ret = hssl_accept(io->ssl);
     if (ret == 0) {
-        // handshark finish
+        // handshake finish
         iowatcher_del_event(io->loop, io->fd, HV_READ);
         io->events &= ~HV_READ;
         io->cb = NULL;
-        printd("ssl handshark finished.\n");
+        printd("ssl handshake finished.\n");
         __accept_cb(io);
     }
     else if (ret == HSSL_WANT_READ) {
         if ((io->events & HV_READ) == 0) {
-            hio_add(io, ssl_server_handshark, HV_READ);
+            hio_add(io, ssl_server_handshake, HV_READ);
         }
     }
     else {
@@ -151,20 +151,20 @@ static void ssl_server_handshark(hio_t* io) {
     }
 }
 
-static void ssl_client_handshark(hio_t* io) {
-    printd("ssl client handshark...\n");
+static void ssl_client_handshake(hio_t* io) {
+    printd("ssl client handshake...\n");
     int ret = hssl_connect(io->ssl);
     if (ret == 0) {
-        // handshark finish
+        // handshake finish
         iowatcher_del_event(io->loop, io->fd, HV_READ);
         io->events &= ~HV_READ;
         io->cb = NULL;
-        printd("ssl handshark finished.\n");
+        printd("ssl handshake finished.\n");
         __connect_cb(io);
     }
     else if (ret == HSSL_WANT_READ) {
         if ((io->events & HV_READ) == 0) {
-            hio_add(io, ssl_client_handshark, HV_READ);
+            hio_add(io, ssl_client_handshake, HV_READ);
         }
     }
     else {
@@ -209,10 +209,10 @@ accept:
         }
         hio_enable_ssl(connio);
         connio->ssl = ssl;
-        ssl_server_handshark(connio);
+        ssl_server_handshake(connio);
     }
     else {
-        // NOTE: SSL call accept_cb after handshark finished
+        // NOTE: SSL call accept_cb after handshake finished
         __accept_cb(connio);
     }
 
@@ -245,10 +245,10 @@ static void nio_connect(hio_t* io) {
                 goto connect_failed;
             }
             io->ssl = ssl;
-            ssl_client_handshark(io);
+            ssl_client_handshake(io);
         }
         else {
-            // NOTE: SSL call connect_cb after handshark finished
+            // NOTE: SSL call connect_cb after handshake finished
             __connect_cb(io);
         }
 
@@ -551,24 +551,10 @@ disconnect:
     return nwrite;
 }
 
-static void hio_close_event_cb(hevent_t* ev) {
-    hio_t* io = (hio_t*)ev->userdata;
-    uint32_t id = (uintptr_t)ev->privdata;
-    if (io->id != id) return;
-    hio_close(io);
-}
-
 int hio_close (hio_t* io) {
     if (io->closed) return 0;
     if (hv_gettid() != io->loop->tid) {
-        hevent_t ev;
-        memset(&ev, 0, sizeof(ev));
-        ev.cb = hio_close_event_cb;
-        ev.userdata = io;
-        ev.privdata = (void*)(uintptr_t)io->id;
-        ev.priority = HEVENT_HIGH_PRIORITY;
-        hloop_post_event(io->loop, &ev);
-        return 0;
+        return hio_close_async(io);
     }
     hrecursive_mutex_lock(&io->write_mutex);
     if (!write_queue_empty(&io->write_queue) && io->error == 0 && io->close == 0) {

+ 33 - 1
evpp/Channel.h

@@ -101,8 +101,11 @@ public:
         return write(str.data(), str.size());
     }
 
-    int close() {
+    int close(bool async = false) {
         if (!isOpened()) return 0;
+        if (async) {
+            return hio_close_async(io_);
+        }
         return hio_close(io_);
     }
 
@@ -156,6 +159,7 @@ class SocketChannel : public Channel {
 public:
     // for TcpClient
     std::function<void()>   onconnect;
+    std::function<void()>   heartbeat;
 
     SocketChannel(hio_t* io) : Channel(io) {
     }
@@ -166,9 +170,26 @@ public:
     }
 
     void setConnectTimeout(int timeout_ms) {
+        if (io_ == NULL) return;
         hio_set_connect_timeout(io_, timeout_ms);
     }
 
+    void setCloseTimeout(int timeout_ms) {
+        if (io_ == NULL) return;
+        hio_set_close_timeout(io_, timeout_ms);
+    }
+
+    void setKeepaliveTimeout(int timeout_ms) {
+        if (io_ == NULL) return;
+        hio_set_keepalive_timeout(io_, timeout_ms);
+    }
+
+    void setHeartbeat(int interval_ms, std::function<void()> fn) {
+        if (io_ == NULL) return;
+        heartbeat = std::move(fn);
+        hio_set_heartbeat(io_, interval_ms, send_heartbeat);
+    }
+
     int startConnect(int port, const char* host = "127.0.0.1") {
         sockaddr_u peeraddr;
         memset(&peeraddr, 0, sizeof(peeraddr));
@@ -181,11 +202,13 @@ public:
     }
 
     int startConnect(struct sockaddr* peeraddr) {
+        if (io_ == NULL) return -1;
         hio_set_peeraddr(io_, peeraddr, SOCKADDR_LEN(peeraddr));
         return startConnect();
     }
 
     int startConnect() {
+        if (io_ == NULL) return -1;
         status = CONNECTING;
         hio_setcb_connect(io_, on_connect);
         return hio_connect(io_);
@@ -196,12 +219,14 @@ public:
     }
 
     std::string localaddr() {
+        if (io_ == NULL) return "";
         struct sockaddr* addr = hio_localaddr(io_);
         char buf[SOCKADDR_STRLEN] = {0};
         return SOCKADDR_STR(addr, buf);
     }
 
     std::string peeraddr() {
+        if (io_ == NULL) return "";
         struct sockaddr* addr = hio_peeraddr(io_);
         char buf[SOCKADDR_STRLEN] = {0};
         return SOCKADDR_STR(addr, buf);
@@ -221,6 +246,13 @@ private:
             }
         }
     }
+
+    static void send_heartbeat(hio_t* io) {
+        SocketChannel* channel = (SocketChannel*)hio_context(io);
+        if (channel && channel->heartbeat) {
+            channel->heartbeat();
+        }
+    }
 };
 
 typedef std::shared_ptr<Channel>        ChannelPtr;

+ 2 - 2
evpp/Event.h

@@ -23,7 +23,7 @@ struct Event {
 
     Event(EventCallback cb = NULL) {
         memset(&event, 0, sizeof(hevent_t));
-        this->cb = cb;
+        this->cb = std::move(cb);
     }
 };
 
@@ -34,7 +34,7 @@ struct Timer {
 
     Timer(htimer_t* timer = NULL, TimerCallback cb = NULL, int repeat = INFINITE) {
         this->timer = timer;
-        this->cb = cb;
+        this->cb = std::move(cb);
         this->repeat = repeat;
     }
 };

+ 3 - 2
evpp/EventLoop.h

@@ -125,6 +125,7 @@ public:
     }
 
     bool isInLoopThread() {
+        if (loop_ == NULL) return false;
         return hv_gettid() == hloop_tid(loop_);
     }
 
@@ -133,10 +134,10 @@ public:
     }
 
     void runInLoop(Functor fn) {
-        if (isInLoopThread()) {
+        if (isRunning() && isInLoopThread()) {
             if (fn) fn();
         } else {
-            queueInLoop(fn);
+            queueInLoop(std::move(fn));
         }
     }
 

+ 1 - 1
evpp/EventLoopThread.h

@@ -29,7 +29,7 @@ public:
         join();
     }
 
-    EventLoopPtr loop() {
+    const EventLoopPtr& loop() {
         return loop_;
     }
 

+ 1 - 1
evpp/EventLoopThreadPool.h

@@ -62,7 +62,7 @@ public:
 
         for (int i = 0; i < thread_num_; ++i) {
             EventLoopThreadPtr loop_thread(new EventLoopThread);
-            EventLoopPtr loop = loop_thread->loop();
+            const EventLoopPtr& loop = loop_thread->loop();
             loop_thread->start(false,
                 [this, started_cnt, pre, &loop]() {
                     if (++(*started_cnt) == thread_num_) {

+ 1 - 1
evpp/EventLoopThread_test.cpp

@@ -23,7 +23,7 @@ int main(int argc, char* argv[]) {
     printf("main tid=%ld\n", hv_gettid());
 
     EventLoopThread loop_thread;
-    EventLoopPtr loop = loop_thread.loop();
+    const EventLoopPtr& loop = loop_thread.loop();
 
     // runEvery 1s
     loop->setInterval(1000, std::bind(onTimer, std::placeholders::_1, 100));

+ 1 - 1
evpp/TcpClient.h

@@ -53,7 +53,7 @@ public:
     virtual ~TcpClientTmpl() {
     }
 
-    EventLoopPtr loop() {
+    const EventLoopPtr& loop() {
         return loop_thread.loop();
     }
 

+ 1 - 1
evpp/UdpClient.h

@@ -17,7 +17,7 @@ public:
     virtual ~UdpClient() {
     }
 
-    EventLoopPtr loop() {
+    const EventLoopPtr& loop() {
         return loop_thread.loop();
     }
 

+ 1 - 1
evpp/UdpServer.h

@@ -17,7 +17,7 @@ public:
     virtual ~UdpServer() {
     }
 
-    EventLoopPtr loop() {
+    const EventLoopPtr& loop() {
         return loop_thread.loop();
     }
 

+ 9 - 3
examples/http_server_test.cpp

@@ -12,7 +12,7 @@
  *
  * @build   ./configure --with-openssl && make clean && make
  *
- * @server  bin/http_server_test
+ * @server  bin/http_server_test 8080
  *
  * @client  curl -v http://127.0.0.1:8080/ping
  *          curl -v https://127.0.0.1:8443/ping --insecure
@@ -22,9 +22,15 @@
  */
 #define TEST_HTTPS 0
 
-int main() {
+int main(int argc, char** argv) {
     HV_MEMCHECK;
 
+    int port = 0;
+    if (argc > 1) {
+        port = atoi(argv[1]);
+    }
+    if (port == 0) port = 8080;
+
     HttpService router;
     router.GET("/ping", [](HttpRequest* req, HttpResponse* resp) {
         return resp->String("pong");
@@ -55,7 +61,7 @@ int main() {
 
     http_server_t server;
     server.service = &router;
-    server.port = 8080;
+    server.port = port;
 #if TEST_HTTPS
     server.https_port = 8443;
     hssl_ctx_init_param_t param;

+ 13 - 6
examples/httpd/handler.h

@@ -20,6 +20,14 @@ public:
         req->ParseBody();
         // 响应格式默认为application/json
         resp->content_type = APPLICATION_JSON;
+        // cors
+        resp->headers["Access-Control-Allow-Origin"] = "*";
+        if (req->method == HTTP_OPTIONS) {
+            resp->headers["Access-Control-Allow-Origin"] = req->GetHeader("Origin", "*");
+            resp->headers["Access-Control-Allow-Methods"] = req->GetHeader("Access-Control-Request-Method", "OPTIONS, HEAD, GET, POST, PUT, DELETE, PATCH");
+            resp->headers["Access-Control-Allow-Headers"] = req->GetHeader("Access-Control-Request-Headers", "Content-Type");
+            return HTTP_STATUS_NO_CONTENT;
+        }
 #if 0
         // 前处理中我们可以做一些公共逻辑,如请求统计、请求拦截、API鉴权等,
         // 下面是一段简单的Token头校验代码
@@ -171,11 +179,10 @@ public:
         // RESTful /:field/ => HttpRequest::query_params
         // path=/group/:group_name/user/:user_id
         // restful风格URL里的参数已被解析到query_params数据结构里,可通过GetParam("key")获取
-        // string group_name = req->GetParam("group_name");
-        // string user_id = req->GetParam("user_id");
-        for (auto& param : req->query_params) {
-            resp->Set(param.first.c_str(), param.second);
-        }
+        std::string group_name = req->GetParam("group_name");
+        std::string user_id = req->GetParam("user_id");
+        resp->Set("group_name", group_name);
+        resp->Set("user_id", user_id);
         response_status(resp, 0, "OK");
         return 200;
     }
@@ -209,7 +216,7 @@ public:
         if (req->content_type != MULTIPART_FORM_DATA) {
             return response_status(resp, HTTP_STATUS_BAD_REQUEST);
         }
-        FormData file = req->form["file"];
+        const FormData& file = req->form["file"];
         if (file.content.empty()) {
             return response_status(resp, HTTP_STATUS_BAD_REQUEST);
         }

+ 2 - 2
examples/httpd/router.h

@@ -4,7 +4,7 @@
 #include <future> // import std::async
 
 #include "HttpService.h"
-#include "http_client.h"
+#include "requests.h"
 
 #include "handler.h"
 
@@ -85,7 +85,7 @@ public:
             HttpRequestPtr req2(new HttpRequest);
             req2->url = req->path.substr(1);
             // 异步HTTP客户端请求 + 异步响应
-            http_client_send_async(req2, [writer](const HttpResponsePtr& resp2){
+            requests::async(req2, [writer](const HttpResponsePtr& resp2){
                 writer->Begin();
                 if (resp2 == NULL) {
                     writer->WriteStatus(HTTP_STATUS_NOT_FOUND);

+ 26 - 8
examples/websocket_server_test.cpp

@@ -28,6 +28,22 @@
 
 using namespace hv;
 
+class MyContext {
+public:
+    MyContext() {
+        timerID = INVALID_TIMER_ID;
+    }
+    ~MyContext() {
+    }
+
+    int handleMessage(const std::string& msg) {
+        printf("onmessage: %s\n", msg.c_str());
+        return msg.size();
+    }
+
+    TimerID timerID;
+};
+
 int main(int argc, char** argv) {
     if (argc < 2) {
         printf("Usage: %s port\n", argv[0]);
@@ -35,12 +51,12 @@ int main(int argc, char** argv) {
     }
     int port = atoi(argv[1]);
 
-    TimerID timerID = INVALID_TIMER_ID;
     WebSocketServerCallbacks ws;
-    ws.onopen = [&timerID](const WebSocketChannelPtr& channel, const std::string& url) {
+    ws.onopen = [](const WebSocketChannelPtr& channel, const std::string& url) {
         printf("onopen: GET %s\n", url.c_str());
+        MyContext* ctx = channel->newContext<MyContext>();
         // send(time) every 1s
-        timerID = setInterval(1000, [channel](TimerID id) {
+        ctx->timerID = setInterval(1000, [channel](TimerID id) {
             char str[DATETIME_FMT_BUFLEN] = {0};
             datetime_t dt = datetime_now();
             datetime_fmt(&dt, str);
@@ -48,14 +64,16 @@ int main(int argc, char** argv) {
         });
     };
     ws.onmessage = [](const WebSocketChannelPtr& channel, const std::string& msg) {
-        printf("onmessage: %s\n", msg.c_str());
+        MyContext* ctx = channel->getContext<MyContext>();
+        ctx->handleMessage(msg);
     };
-    ws.onclose = [&timerID](const WebSocketChannelPtr& channel) {
+    ws.onclose = [](const WebSocketChannelPtr& channel) {
         printf("onclose\n");
-        if (timerID != INVALID_TIMER_ID) {
-            killTimer(timerID);
-            timerID = INVALID_TIMER_ID;
+        MyContext* ctx = channel->getContext<MyContext>();
+        if (ctx->timerID != INVALID_TIMER_ID) {
+            killTimer(ctx->timerID);
         }
+        channel->deleteContext<MyContext>();
     };
 
     websocket_server_t server;

+ 3 - 0
hexport.h

@@ -24,6 +24,9 @@
     #define HV_EXPORT
 #endif
 
+// HV_INLINE
+#define HV_INLINE static inline
+
 // HV_DEPRECATED
 // 接口过时声明宏
 #if defined(HV_NO_DEPRECATED)

+ 5 - 2
http/Http1Parser.cpp

@@ -1,5 +1,7 @@
 #include "Http1Parser.h"
 
+#define MAX_CONTENT_LENGTH  (1 << 24)   // 16M
+
 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);
@@ -112,8 +114,9 @@ int on_headers_complete(http_parser* parser) {
     if (iter != hp->parsed->headers.end()) {
         int content_length = atoi(iter->second.c_str());
         hp->parsed->content_length = content_length;
-        if ((!skip_body) && content_length > hp->parsed->body.capacity()) {
-            hp->parsed->body.reserve(content_length);
+        int reserve_length = MIN(content_length + 1, MAX_CONTENT_LENGTH);
+        if ((!skip_body) && reserve_length > hp->parsed->body.capacity()) {
+            hp->parsed->body.reserve(reserve_length);
         }
     }
     hp->state = HP_HEADERS_COMPLETE;

+ 16 - 4
http/HttpMessage.cpp

@@ -91,7 +91,10 @@ std::string HttpMessage::GetString(const char* key, const std::string& defvalue)
     switch (content_type) {
     case APPLICATION_JSON:
     {
-        auto value = json[key];
+        if (!json.is_object()) {
+            return defvalue;
+        }
+        const auto& value = json[key];
         if (value.is_string()) {
             return value;
         }
@@ -135,7 +138,10 @@ std::string HttpMessage::GetString(const char* key, const std::string& defvalue)
 template<>
 HV_EXPORT int64_t HttpMessage::Get(const char* key, int64_t defvalue) {
     if (content_type == APPLICATION_JSON) {
-        auto value = json[key];
+        if (!json.is_object()) {
+            return defvalue;
+        }
+        const auto& value = json[key];
         if (value.is_number()) {
             return value;
         }
@@ -163,7 +169,10 @@ HV_EXPORT int64_t HttpMessage::Get(const char* key, int64_t defvalue) {
 template<>
 HV_EXPORT double HttpMessage::Get(const char* key, double defvalue) {
     if (content_type == APPLICATION_JSON) {
-        auto value = json[key];
+        if (!json.is_object()) {
+            return defvalue;
+        }
+        const auto& value = json[key];
         if (value.is_number()) {
             return value;
         }
@@ -187,7 +196,10 @@ HV_EXPORT double HttpMessage::Get(const char* key, double defvalue) {
 template<>
 HV_EXPORT bool HttpMessage::Get(const char* key, bool defvalue) {
     if (content_type == APPLICATION_JSON) {
-        auto value = json[key];
+        if (!json.is_object()) {
+            return defvalue;
+        }
+        const auto& value = json[key];
         if (value.is_boolean()) {
             return value;
         }

+ 1 - 1
http/HttpMessage.h

@@ -149,7 +149,7 @@ public:
         if (content_type != MULTIPART_FORM_DATA) {
             return HTTP_STATUS_BAD_REQUEST;
         }
-        FormData formdata = form[name];
+        const FormData& formdata = form[name];
         if (formdata.content.empty()) {
             return HTTP_STATUS_BAD_REQUEST;
         }

+ 9 - 5
http/WebSocketChannel.h

@@ -1,6 +1,8 @@
 #ifndef HV_WEBSOCKET_CHANNEL_H_
 #define HV_WEBSOCKET_CHANNEL_H_
 
+#include <mutex>
+
 #include "Channel.h"
 
 #include "wsdef.h"
@@ -31,15 +33,17 @@ public:
             *(int*)mask = rand();
         }
         int frame_size = ws_calc_frame_size(len, has_mask);
-        if (sendbuf.len < frame_size) {
-            sendbuf.resize(ceil2e(frame_size));
+        std::lock_guard<std::mutex> locker(mutex_);
+        if (sendbuf_.len < frame_size) {
+            sendbuf_.resize(ceil2e(frame_size));
         }
-        ws_build_frame(sendbuf.base, buf, len, mask, has_mask, opcode);
-        return write(sendbuf.base, frame_size);
+        ws_build_frame(sendbuf_.base, buf, len, mask, has_mask, opcode);
+        return write(sendbuf_.base, frame_size);
     }
 
 private:
-    Buffer sendbuf;
+    Buffer      sendbuf_;
+    std::mutex  mutex_;
 };
 
 }

+ 7 - 3
http/WebSocketParser.cpp

@@ -1,6 +1,9 @@
 #include "WebSocketParser.h"
 
 #include "websocket_parser.h"
+#include "hdef.h"
+
+#define MAX_PAYLOAD_LENGTH  (1 << 24)   // 16M
 
 static int on_frame_header(websocket_parser* parser) {
     WebSocketParser* wp = (WebSocketParser*)parser->data;
@@ -10,12 +13,13 @@ static int on_frame_header(websocket_parser* parser) {
         wp->opcode = opcode;
     }
     int length = parser->length;
-    if (length && length > wp->message.capacity()) {
-        wp->message.reserve(length);
+    int reserve_length = MIN(length + 1, MAX_PAYLOAD_LENGTH);
+    if (reserve_length > wp->message.capacity()) {
+        wp->message.reserve(reserve_length);
     }
     if (wp->state == WS_FRAME_BEGIN ||
         wp->state == WS_FRAME_END) {
-        wp->message.resize(0);
+        wp->message.clear();
     }
     wp->state = WS_FRAME_HEADER;
     return 0;

+ 10 - 3
http/client/AsyncHttpClient.cpp

@@ -90,9 +90,16 @@ int AsyncHttpClient::doTask(const HttpClientTaskPtr& task) {
         if (iter != conn_pools.end()) {
             iter->second.remove(channel->fd());
         }
-        if (ctx->task && ctx->task->retry_cnt-- > 0) {
-            // try again
-            send(ctx->task);
+        const HttpClientTaskPtr& task = ctx->task;
+        if (task && task->retry_cnt-- > 0) {
+            if (task->retry_delay) {
+                // try again after delay
+                setTimeout(ctx->task->retry_delay, [this, task](TimerID timerID){
+                    doTask(task);
+                });
+            } else {
+                send(task);
+            }
         } else {
             ctx->errorCallback();
         }

+ 9 - 4
http/client/AsyncHttpClient.h

@@ -9,6 +9,9 @@
 #include "HttpMessage.h"
 #include "HttpParser.h"
 
+#define DEFAULT_FAIL_RETRY_COUNT  3
+#define DEFAULT_FAIL_RETRY_DELAY  1000  // ms
+
 // async => keepalive => connect_pool
 
 namespace hv {
@@ -53,8 +56,9 @@ struct HttpClientTask {
     HttpRequestPtr          req;
     HttpResponseCallback    cb;
 
-    uint64_t  start_time;
-    int       retry_cnt;
+    uint64_t    start_time;
+    int         retry_cnt;
+    int         retry_delay;
 };
 typedef std::shared_ptr<HttpClientTask> HttpClientTaskPtr;
 
@@ -111,9 +115,10 @@ public:
     int send(const HttpRequestPtr& req, HttpResponseCallback resp_cb) {
         HttpClientTaskPtr task(new HttpClientTask);
         task->req = req;
-        task->cb = resp_cb;
+        task->cb = std::move(resp_cb);
         task->start_time = hloop_now_hrtime(loop_thread.hloop());
-        task->retry_cnt = 3;
+        task->retry_cnt = DEFAULT_FAIL_RETRY_COUNT;
+        task->retry_delay = DEFAULT_FAIL_RETRY_DELAY;
         return send(task);
     }
 

+ 20 - 0
http/client/WebSocketClient.cpp

@@ -3,12 +3,16 @@
 #include "base64.h"
 #include "hlog.h"
 
+#define DEFAULT_WS_PING_INTERVAL    3000 // ms
+
 namespace hv {
 
 WebSocketClient::WebSocketClient()
     : TcpClientTmpl<WebSocketChannel>()
 {
     state = WS_CLOSED;
+    ping_interval = DEFAULT_WS_PING_INTERVAL;
+    ping_cnt = 0;
 }
 
 WebSocketClient::~WebSocketClient() {
@@ -119,6 +123,7 @@ int WebSocketClient::open(const char* _url) {
                     }
                     case WS_OPCODE_PONG:
                         // printf("recv pong\n");
+                        ping_cnt = 0;
                         break;
                     case WS_OPCODE_TEXT:
                     case WS_OPCODE_BINARY:
@@ -129,6 +134,21 @@ int WebSocketClient::open(const char* _url) {
                     }
                 };
                 state = WS_OPENED;
+                // ping
+                if (ping_interval > 0) {
+                    ping_cnt = 0;
+                    channel->setHeartbeat(ping_interval, [this](){
+                        auto& channel = this->channel;
+                        if (channel == NULL) return;
+                        if (ping_cnt++ == 3) {
+                            hloge("websocket no pong!");
+                            channel->close();
+                            return;
+                        }
+                        // printf("send ping\n");
+                        channel->write(WS_CLIENT_PING_FRAME, WS_CLIENT_MIN_FRAME_SIZE);
+                    });
+                }
                 if (onopen) onopen();
             }
         } else {

+ 8 - 0
http/client/WebSocketClient.h

@@ -32,6 +32,11 @@ public:
     int send(const std::string& msg);
     int send(const char* buf, int len, enum ws_opcode opcode = WS_OPCODE_BINARY);
 
+    // setConnectTimeout / setPingInterval / setReconnect
+    void setPingInterval(int ms) {
+        ping_interval = ms;
+    }
+
 private:
     enum State {
         CONNECTING,
@@ -44,6 +49,9 @@ private:
     HttpRequestPtr      http_req_;
     HttpResponsePtr     http_resp_;
     WebSocketParserPtr  ws_parser_;
+    // ping/pong
+    int                 ping_interval;
+    int                 ping_cnt;
 };
 
 }

+ 6 - 4
http/client/http_client.cpp

@@ -331,7 +331,7 @@ static int __http_client_connect(http_client_t* cli, HttpRequest* req) {
         cli->ssl = hssl_new(ssl_ctx, connfd);
         int ret = hssl_connect(cli->ssl);
         if (ret != 0) {
-            fprintf(stderr, "SSL handshark failed: %d\n", ret);
+            fprintf(stderr, "SSL handshake failed: %d\n", ret);
             hssl_free(cli->ssl);
             cli->ssl = NULL;
             closesocket(connfd);
@@ -438,11 +438,13 @@ const char* http_client_strerror(int errcode) {
 #endif
 
 static int __http_client_send_async(http_client_t* cli, HttpRequestPtr req, HttpResponseCallback resp_cb) {
-    cli->mutex_.lock();
     if (cli->async_client_ == NULL) {
-        cli->async_client_.reset(new hv::AsyncHttpClient);
+        cli->mutex_.lock();
+        if (cli->async_client_ == NULL) {
+            cli->async_client_.reset(new hv::AsyncHttpClient);
+        }
+        cli->mutex_.unlock();
     }
-    cli->mutex_.unlock();
 
     return cli->async_client_->send(req, resp_cb);
 }

+ 16 - 11
http/client/requests.h

@@ -35,19 +35,20 @@ int main() {
 
 namespace requests {
 
-typedef std::shared_ptr<HttpRequest>  Request;
-typedef std::shared_ptr<HttpResponse> Response;
+typedef HttpRequestPtr          Request;
+typedef HttpResponsePtr         Response;
+typedef HttpResponseCallback    ResponseCallback;
 
 static http_headers DefaultHeaders;
 static http_body    NoBody;
 
-Response request(Request req) {
+HV_INLINE Response request(Request req) {
     Response resp(new HttpResponse);
     int ret = http_client_send(req.get(), resp.get());
     return ret ? NULL : resp;
 }
 
-Response request(http_method method, const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
+HV_INLINE Response request(http_method method, const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
     Request req(new HttpRequest);
     req->method = method;
     req->url = url;
@@ -60,35 +61,39 @@ Response request(http_method method, const char* url, const http_body& body = No
     return request(req);
 }
 
-Response get(const char* url, const http_headers& headers = DefaultHeaders) {
+HV_INLINE Response get(const char* url, const http_headers& headers = DefaultHeaders) {
     return request(HTTP_GET, url, NoBody, headers);
 }
 
-Response options(const char* url, const http_headers& headers = DefaultHeaders) {
+HV_INLINE Response options(const char* url, const http_headers& headers = DefaultHeaders) {
     return request(HTTP_OPTIONS, url, NoBody, headers);
 }
 
-Response head(const char* url, const http_headers& headers = DefaultHeaders) {
+HV_INLINE Response head(const char* url, const http_headers& headers = DefaultHeaders) {
     return request(HTTP_HEAD, url, NoBody, headers);
 }
 
-Response post(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
+HV_INLINE Response post(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
     return request(HTTP_POST, url, body, headers);
 }
 
-Response put(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
+HV_INLINE Response put(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
     return request(HTTP_PUT, url, body, headers);
 }
 
-Response patch(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
+HV_INLINE Response patch(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
     return request(HTTP_PATCH, url, body, headers);
 }
 
 // delete is c++ keyword, we have to replace delete with Delete.
-Response Delete(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
+HV_INLINE Response Delete(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
     return request(HTTP_DELETE, url, body, headers);
 }
 
+HV_INLINE int async(Request req, ResponseCallback resp_cb) {
+    return http_client_send_async(req, resp_cb);
+}
+
 }
 
 #endif // HV_REQUESTS_H_

+ 3 - 3
http/http_content.cpp

@@ -4,7 +4,7 @@
 
 #include <string.h>
 
-std::string dump_query_params(QueryParams& query_params) {
+std::string dump_query_params(const QueryParams& query_params) {
     std::string query_string;
     for (auto& pair : query_params) {
         if (query_string.size() != 0) {
@@ -209,7 +209,7 @@ static int on_body_end(multipart_parser* parser) {
     userdata->state = MP_BODY_END;
     return 0;
 }
-int parse_multipart(std::string& str, MultiPart& mp, const char* boundary) {
+int parse_multipart(const std::string& str, MultiPart& mp, const char* boundary) {
     //printf("boundary=%s\n", boundary);
     std::string __boundary("--");
     __boundary += boundary;
@@ -231,7 +231,7 @@ int parse_multipart(std::string& str, MultiPart& mp, const char* boundary) {
     return nparse == str.size() ? 0 : -1;
 }
 
-std::string dump_json(hv::Json& json, int indent) {
+std::string dump_json(const hv::Json& json, int indent) {
     return json.dump(indent);
 }
 

+ 3 - 3
http/http_content.h

@@ -6,7 +6,7 @@
 
 // QueryParams
 typedef hv::KeyValue    QueryParams;
-HV_EXPORT std::string dump_query_params(QueryParams& query_params);
+HV_EXPORT std::string dump_query_params(const QueryParams& query_params);
 HV_EXPORT int         parse_query_params(const char* query_string, QueryParams& query_params);
 
 // NOTE: WITHOUT_HTTP_CONTENT
@@ -49,7 +49,7 @@ struct FormData {
 typedef HV_MAP<std::string, FormData>          MultiPart;
 #define DEFAULT_MULTIPART_BOUNDARY  "----WebKitFormBoundary7MA4YWxkTrZu0gW"
 HV_EXPORT std::string dump_multipart(MultiPart& mp, const char* boundary = DEFAULT_MULTIPART_BOUNDARY);
-HV_EXPORT int         parse_multipart(std::string& str, MultiPart& mp, const char* boundary);
+HV_EXPORT int         parse_multipart(const std::string& str, MultiPart& mp, const char* boundary);
 
 // Json
 // https://github.com/nlohmann/json
@@ -59,7 +59,7 @@ using Json = nlohmann::json;
 // using Json = nlohmann::ordered_json;
 }
 
-HV_EXPORT std::string dump_json(hv::Json& json, int indent = -1);
+HV_EXPORT std::string dump_json(const hv::Json& json, int indent = -1);
 HV_EXPORT int         parse_json(const char* str, hv::Json& json, std::string& errmsg);
 #endif
 

+ 5 - 3
http/server/HttpServer.cpp

@@ -53,7 +53,7 @@ static void websocket_onmessage(int opcode, const std::string& msg, hio_t* io) {
     WebSocketHandler* ws = handler->ws.get();
     switch(opcode) {
     case WS_OPCODE_CLOSE:
-        hio_close(io);
+        hio_close_async(io);
         break;
     case WS_OPCODE_PING:
         // printf("recv ping\n");
@@ -376,12 +376,14 @@ int http_server_stop(http_server_t* server) {
             continue;
         }
         // wait for all loops running
+        bool all_loops_running = true;
         for (auto& loop : privdata->loops) {
             if (loop->status() < hv::Status::kRunning) {
-                continue;
+                all_loops_running = false;
+                break;
             }
         }
-        break;
+        if (all_loops_running) break;
     }
 
     // stop all loops

+ 1 - 1
http/server/HttpServer.h

@@ -44,7 +44,7 @@ typedef struct http_server_s {
 
 int main() {
     HttpService service;
-    service.base_url = "/v1/api";
+    service.base_url = "/api/v1";
     service.GET("/ping", [](HttpRequest* req, HttpResponse* resp) {
         resp->body = "pong";
         return 200;

+ 3 - 3
http/server/HttpService.h

@@ -11,7 +11,7 @@
 #include "HttpMessage.h"
 #include "HttpResponseWriter.h"
 
-#define DEFAULT_BASE_URL        "/v1/api"
+#define DEFAULT_BASE_URL        "/api/v1"
 #define DEFAULT_DOCUMENT_ROOT   "/var/www/html"
 #define DEFAULT_HOME_PAGE       "index.html"
 #define DEFAULT_ERROR_PAGE      "error.html"
@@ -34,8 +34,8 @@ struct http_method_handler {
                         http_async_handler a = NULL)
     {
         method = m;
-        sync_handler = s;
-        async_handler = a;
+        sync_handler = std::move(s);
+        async_handler = std::move(a);
     }
 };
 // method => http_sync_handler

+ 2 - 2
http/websocket_parser.c

@@ -158,8 +158,8 @@ size_t websocket_parser_execute(websocket_parser *parser, const websocket_parser
                     p--;
                 }
                 if(!parser->require) {
-                    NOTIFY_CB(frame_end);
                     SET_STATE(s_start);
+                    NOTIFY_CB(frame_end);
                 }
                 break;
             default:
@@ -246,4 +246,4 @@ size_t websocket_build_frame(char * frame, websocket_flags flags, const char mas
     }
 
     return body_offset + data_len;
-}
+}

+ 2 - 2
http/wsdef.h

@@ -53,7 +53,7 @@ HV_EXPORT int ws_build_frame(
     enum ws_opcode opcode DEFAULT(WS_OPCODE_TEXT),
     bool fin DEFAULT(true));
 
-static inline int ws_client_build_frame(
+HV_INLINE int ws_client_build_frame(
     char* out,
     const char* data,
     int data_len,
@@ -66,7 +66,7 @@ static inline int ws_client_build_frame(
     return ws_build_frame(out, data, data_len, mask, true, opcode, fin);
 }
 
-static inline int ws_server_build_frame(
+HV_INLINE int ws_server_build_frame(
     char* out,
     const char* data,
     int data_len,