Browse Source

Add http_state_handler for recvLargeFile

ithewei 3 years ago
parent
commit
0dfcffe1a4

+ 4 - 0
cpputil/hthreadpool.h

@@ -52,6 +52,10 @@ public:
     int idleThreadNum() {
         return idle_thread_num;
     }
+    size_t taskNum() {
+        std::lock_guard<std::mutex> locker(task_mutex);
+        return tasks.size();
+    }
     bool isStarted() {
         return status != STOP;
     }

+ 126 - 80
examples/httpd/handler.cpp

@@ -62,84 +62,6 @@ int Handler::errorHandler(const HttpContextPtr& ctx) {
     return response_status(ctx, error_code);
 }
 
-int Handler::largeFileHandler(const HttpContextPtr& ctx) {
-    std::thread([ctx](){
-        ctx->writer->Begin();
-        std::string filepath = ctx->service->document_root + ctx->request->Path();
-        HFile file;
-        if (file.open(filepath.c_str(), "rb") != 0) {
-            ctx->writer->WriteStatus(HTTP_STATUS_NOT_FOUND);
-            ctx->writer->WriteHeader("Content-Type", "text/html");
-            ctx->writer->WriteBody("<center><h1>404 Not Found</h1></center>");
-            ctx->writer->End();
-            return;
-        }
-        http_content_type content_type = CONTENT_TYPE_NONE;
-        const char* suffix = hv_suffixname(filepath.c_str());
-        if (suffix) {
-            content_type = http_content_type_enum_by_suffix(suffix);
-        }
-        if (content_type == CONTENT_TYPE_NONE || content_type == CONTENT_TYPE_UNDEFINED) {
-            content_type = APPLICATION_OCTET_STREAM;
-        }
-        size_t filesize = file.size();
-        ctx->writer->WriteHeader("Content-Type", http_content_type_str(content_type));
-        ctx->writer->WriteHeader("Content-Length", filesize);
-        // ctx->writer->WriteHeader("Transfer-Encoding", "chunked");
-        ctx->writer->EndHeaders();
-
-        char* buf = NULL;
-        int len = 40960; // 40K
-        SAFE_ALLOC(buf, len);
-        size_t total_readbytes = 0;
-        int last_progress = 0;
-        int sleep_ms_per_send = 0;
-        if (ctx->service->limit_rate <= 0) {
-            // unlimited
-        } else {
-            sleep_ms_per_send = len * 1000 / 1024 / ctx->service->limit_rate;
-        }
-        if (sleep_ms_per_send == 0) sleep_ms_per_send = 1;
-        int sleep_ms = sleep_ms_per_send;
-        auto start_time = std::chrono::steady_clock::now();
-        auto end_time = start_time;
-        while (total_readbytes < filesize) {
-            if (!ctx->writer->isConnected()) {
-                break;
-            }
-            if (!ctx->writer->isWriteComplete()) {
-                hv_delay(1);
-                continue;
-            }
-            size_t readbytes = file.read(buf, len);
-            if (readbytes <= 0) {
-                // read file error!
-                ctx->writer->close();
-                break;
-            }
-            int nwrite = ctx->writer->WriteBody(buf, readbytes);
-            if (nwrite < 0) {
-                // disconnected!
-                break;
-            }
-            total_readbytes += readbytes;
-            int cur_progress = total_readbytes * 100 / filesize;
-            if (cur_progress > last_progress) {
-                // printf("<< %s progress: %ld/%ld = %d%%\n",
-                //     ctx->request->path.c_str(), (long)total_readbytes, (long)filesize, (int)cur_progress);
-                last_progress = cur_progress;
-            }
-            end_time += std::chrono::milliseconds(sleep_ms);
-            std::this_thread::sleep_until(end_time);
-        }
-        ctx->writer->End();
-        SAFE_FREE(buf);
-        // auto elapsed_time = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time);
-        // printf("<< %s taked %ds\n", ctx->request->path.c_str(), (int)elapsed_time.count());
-    }).detach();
-    return HTTP_STATUS_UNFINISHED;
-}
-
 int Handler::sleep(const HttpRequestPtr& req, const HttpResponseWriterPtr& writer) {
     writer->WriteHeader("X-Response-tid", hv_gettid());
     unsigned long long start_ms = gettimeofday_ms();
@@ -281,14 +203,138 @@ int Handler::login(const HttpContextPtr& ctx) {
 
 int Handler::upload(const HttpContextPtr& ctx) {
     int status_code = 200;
+    std::string save_path = "html/uploads/";
     if (ctx->is(MULTIPART_FORM_DATA)) {
-        status_code = ctx->request->SaveFormFile("file", "html/uploads/");
+        status_code = ctx->request->SaveFormFile("file", save_path.c_str());
     } else {
-        status_code = ctx->request->SaveFile("html/uploads/upload.txt");
+        std::string filename = ctx->param("filename", "unnamed.txt");
+        std::string filepath = save_path + filename;
+        status_code = ctx->request->SaveFile(filepath.c_str());
     }
     return response_status(ctx, status_code);
 }
 
+int Handler::recvLargeFile(const HttpContextPtr& ctx, http_parser_state state, const char* data, size_t size) {
+    switch (state) {
+    case HP_HEADERS_COMPLETE:
+        {
+            ctx->setContentType(APPLICATION_JSON);
+            std::string save_path = "html/uploads/";
+            std::string filename = ctx->param("filename", "unnamed.txt");
+            std::string filepath = save_path + filename;
+            HFile* file = new HFile;
+            if (file->open(filepath.c_str(), "wb") != 0) {
+                return response_status(ctx, HTTP_STATUS_INTERNAL_SERVER_ERROR);
+            }
+            ctx->userdata = file;
+        }
+    case HP_BODY:
+        {
+            HFile* file = (HFile*)ctx->userdata;
+            if (file && data && size) {
+                if (file->write(data, size) != size) {
+                    return response_status(ctx, HTTP_STATUS_INTERNAL_SERVER_ERROR);
+                }
+            }
+        }
+        break;
+    case HP_MESSAGE_COMPLETE:
+        {
+            HFile* file = (HFile*)ctx->userdata;
+            delete file;
+            return response_status(ctx, HTTP_STATUS_OK);
+        }
+        break;
+    case HP_ERROR:
+        {
+            HFile* file = (HFile*)ctx->userdata;
+            delete file;
+        }
+        break;
+    default:
+        break;
+    }
+    return HTTP_STATUS_UNFINISHED;
+}
+
+int Handler::sendLargeFile(const HttpContextPtr& ctx) {
+    std::thread([ctx](){
+        ctx->writer->Begin();
+        std::string filepath = ctx->service->document_root + ctx->request->Path();
+        HFile file;
+        if (file.open(filepath.c_str(), "rb") != 0) {
+            ctx->writer->WriteStatus(HTTP_STATUS_NOT_FOUND);
+            ctx->writer->WriteHeader("Content-Type", "text/html");
+            ctx->writer->WriteBody("<center><h1>404 Not Found</h1></center>");
+            ctx->writer->End();
+            return;
+        }
+        http_content_type content_type = CONTENT_TYPE_NONE;
+        const char* suffix = hv_suffixname(filepath.c_str());
+        if (suffix) {
+            content_type = http_content_type_enum_by_suffix(suffix);
+        }
+        if (content_type == CONTENT_TYPE_NONE || content_type == CONTENT_TYPE_UNDEFINED) {
+            content_type = APPLICATION_OCTET_STREAM;
+        }
+        size_t filesize = file.size();
+        ctx->writer->WriteHeader("Content-Type", http_content_type_str(content_type));
+        ctx->writer->WriteHeader("Content-Length", filesize);
+        // ctx->writer->WriteHeader("Transfer-Encoding", "chunked");
+        ctx->writer->EndHeaders();
+
+        char* buf = NULL;
+        int len = 40960; // 40K
+        SAFE_ALLOC(buf, len);
+        size_t total_readbytes = 0;
+        int last_progress = 0;
+        int sleep_ms_per_send = 0;
+        if (ctx->service->limit_rate <= 0) {
+            // unlimited
+        } else {
+            sleep_ms_per_send = len * 1000 / 1024 / ctx->service->limit_rate;
+        }
+        if (sleep_ms_per_send == 0) sleep_ms_per_send = 1;
+        int sleep_ms = sleep_ms_per_send;
+        auto start_time = std::chrono::steady_clock::now();
+        auto end_time = start_time;
+        while (total_readbytes < filesize) {
+            if (!ctx->writer->isConnected()) {
+                break;
+            }
+            if (!ctx->writer->isWriteComplete()) {
+                hv_delay(1);
+                continue;
+            }
+            size_t readbytes = file.read(buf, len);
+            if (readbytes <= 0) {
+                // read file error!
+                ctx->writer->close();
+                break;
+            }
+            int nwrite = ctx->writer->WriteBody(buf, readbytes);
+            if (nwrite < 0) {
+                // disconnected!
+                break;
+            }
+            total_readbytes += readbytes;
+            int cur_progress = total_readbytes * 100 / filesize;
+            if (cur_progress > last_progress) {
+                // printf("<< %s progress: %ld/%ld = %d%%\n",
+                //     ctx->request->path.c_str(), (long)total_readbytes, (long)filesize, (int)cur_progress);
+                last_progress = cur_progress;
+            }
+            end_time += std::chrono::milliseconds(sleep_ms);
+            std::this_thread::sleep_until(end_time);
+        }
+        ctx->writer->End();
+        SAFE_FREE(buf);
+        // auto elapsed_time = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time);
+        // printf("<< %s taked %ds\n", ctx->request->path.c_str(), (int)elapsed_time.count());
+    }).detach();
+    return HTTP_STATUS_UNFINISHED;
+}
+
 int Handler::sse(const HttpContextPtr& ctx) {
     ctx->writer->EndHeaders("Content-Type", "text/event-stream");
     // send(message) every 1s

+ 4 - 2
examples/httpd/handler.h

@@ -8,9 +8,7 @@ public:
     // preprocessor => api_handlers => postprocessor
     static int preprocessor(HttpRequest* req, HttpResponse* resp);
     static int postprocessor(HttpRequest* req, HttpResponse* resp);
-
     static int errorHandler(const HttpContextPtr& ctx);
-    static int largeFileHandler(const HttpContextPtr& ctx);
 
     static int sleep(const HttpRequestPtr& req, const HttpResponseWriterPtr& writer);
     static int setTimeout(const HttpContextPtr& ctx);
@@ -29,6 +27,10 @@ public:
     // SSE: Server Send Events
     static int sse(const HttpContextPtr& ctx);
 
+    // LargeFile
+    static int sendLargeFile(const HttpContextPtr& ctx);
+    static int recvLargeFile(const HttpContextPtr& ctx, http_parser_state state, const char* data, size_t size);
+
 private:
     static int response_status(HttpResponse* resp, int code = 200, const char* message = NULL) {
         if (message == NULL) message = http_status_str((enum http_status)code);

+ 4 - 2
examples/httpd/router.cpp

@@ -9,8 +9,8 @@ void Router::Register(hv::HttpService& router) {
     // preprocessor => Handler => postprocessor
     router.preprocessor = Handler::preprocessor;
     router.postprocessor = Handler::postprocessor;
-    // router.largeFileHandler = Handler::largeFileHandler;
     // router.errorHandler = Handler::errorHandler;
+    // router.largeFileHandler = Handler::sendLargeFile;
 
     // curl -v http://ip:port/ping
     router.GET("/ping", [](HttpRequest* req, HttpResponse* resp) {
@@ -130,9 +130,11 @@ void Router::Register(hv::HttpService& router) {
     // curl -v http://ip:port/login -H "Content-Type:application/json" -d '{"username":"admin","password":"123456"}'
     router.POST("/login", Handler::login);
 
-    // curl -v http://ip:port/upload -d '@LICENSE'
+    // curl -v http://ip:port/upload?filename=LICENSE -d '@LICENSE'
     // curl -v http://ip:port/upload -F 'file=@LICENSE'
     router.POST("/upload", Handler::upload);
+    // curl -v http://ip:port/upload/README.md -d '@README.md'
+    router.POST("/upload/{filename}", Handler::recvLargeFile);
 
     // SSE: Server Send Events
     router.GET("/sse", Handler::sse);

+ 5 - 2
getting_started.sh

@@ -66,12 +66,15 @@ cmd="bin/curl -v localhost:8080/json -H 'Content-Type:application/json' -d '{\"u
 cmd="bin/curl -v localhost:8080/form -F 'user=admin' -F 'pswd=123456'" && echo_cmd
      bin/curl -v localhost:8080/form -F 'user=admin' -F 'pswd=123456'
 
-cmd="bin/curl -v localhost:8080/upload -d '@LICENSE'" && echo_cmd
-     bin/curl -v localhost:8080/upload -d '@LICENSE'
+cmd="bin/curl -v localhost:8080/upload?filename=LICENSE -d '@LICENSE'" && echo_cmd
+     bin/curl -v localhost:8080/upload?filename=LICENSE -d '@LICENSE'
 
 cmd="bin/curl -v localhost:8080/upload -F 'file=@LICENSE'" && echo_cmd
      bin/curl -v localhost:8080/upload -F 'file=@LICENSE'
 
+cmd="bin/curl -v localhost:8080/upload/README.md -d '@README.md'" && echo_cmd
+     bin/curl -v localhost:8080/upload/README.md -d '@README.md'
+
 cmd="bin/curl -v localhost:8080/test -H 'Content-Type:application/x-www-form-urlencoded' -d 'bool=1&int=123&float=3.14&string=hello'" && echo_cmd
      bin/curl -v localhost:8080/test -H 'Content-Type:application/x-www-form-urlencoded' -d 'bool=1&int=123&float=3.14&string=hello'
 

+ 2 - 1
http/httpdef.h

@@ -20,7 +20,8 @@ enum http_parser_state {
     HP_CHUNK_HEADER,
     HP_BODY,
     HP_CHUNK_COMPLETE,
-    HP_MESSAGE_COMPLETE
+    HP_MESSAGE_COMPLETE,
+    HP_ERROR
 };
 
 // http_status

+ 1 - 0
http/server/HttpContext.h

@@ -13,6 +13,7 @@ struct HV_EXPORT HttpContext {
     HttpRequestPtr          request;
     HttpResponsePtr         response;
     HttpResponseWriterPtr   writer;
+    void*                   userdata;
 
     // HttpRequest aliases
     // return request->xxx

+ 59 - 20
http/server/HttpHandler.cpp

@@ -16,6 +16,7 @@ HttpHandler::HttpHandler() {
     ssl = false;
     service = NULL;
     ws_service = NULL;
+    api_handler = NULL;
     last_send_ping_time = 0;
     last_recv_pong_time = 0;
 
@@ -49,6 +50,15 @@ bool HttpHandler::Init(int http_version, hio_t* io) {
         writer.reset(new hv::HttpResponseWriter(io, resp));
         writer->status = hv::SocketChannel::CONNECTED;
     }
+    // NOTE: hook http_cb
+    req->http_cb = [this](HttpMessage* msg, http_parser_state state, const char* data, size_t size) {
+        if (state == HP_HEADERS_COMPLETE) {
+            onHeadersComplete();
+        }
+        if (api_handler && api_handler->state_handler) {
+            api_handler->state_handler(getHttpContext(), state, data, size);
+        }
+    };
     return true;
 }
 
@@ -57,6 +67,8 @@ void HttpHandler::Reset() {
     req->Reset();
     resp->Reset();
     parser->InitRequest(req.get());
+    ctx = NULL;
+    api_handler = NULL;
     closeFile();
     if (writer) {
         writer->Begin();
@@ -127,6 +139,17 @@ bool HttpHandler::SwitchWebSocket(hio_t* io) {
     return true;
 }
 
+const HttpContextPtr& HttpHandler::getHttpContext() {
+    if (!ctx) {
+        ctx = std::make_shared<hv::HttpContext>();
+        ctx->service = service;
+        ctx->request = req;
+        ctx->response = resp;
+        ctx->writer = writer;
+    }
+    return ctx;
+}
+
 int HttpHandler::customHttpHandler(const http_handler& handler) {
     return invokeHttpHandler(&handler);
 }
@@ -141,30 +164,48 @@ int HttpHandler::invokeHttpHandler(const http_handler* handler) {
         hv::async(std::bind(handler->async_handler, req, writer));
         status_code = HTTP_STATUS_UNFINISHED;
     } else if (handler->ctx_handler) {
-        HttpContextPtr ctx(new hv::HttpContext);
-        ctx->service = service;
-        ctx->request = req;
-        ctx->response = resp;
-        ctx->writer = writer;
         // NOTE: ctx_handler run on IO thread, you can easily post HttpContextPtr to your consumer thread for processing.
-        status_code = handler->ctx_handler(ctx);
-        if (writer && writer->state != hv::HttpResponseWriter::SEND_BEGIN) {
-            status_code = HTTP_STATUS_UNFINISHED;
-        }
+        status_code = handler->ctx_handler(getHttpContext());
+    } else if (handler->state_handler) {
+        status_code = resp->status_code;
     }
     return status_code;
 }
 
+void HttpHandler::onHeadersComplete() {
+    HttpRequest* pReq = req.get();
+    pReq->scheme = ssl ? "https" : "http";
+    pReq->client_addr.ip = ip;
+    pReq->client_addr.port = port;
+    pReq->ParseUrl();
+
+    if (service->api_handlers.size() != 0) {
+        service->GetApi(pReq, &api_handler);
+    }
+    if (api_handler && api_handler->state_handler) {
+        writer->onclose = [this](){
+            // HP_ERROR
+            if (!parser->IsComplete()) {
+                if (api_handler && api_handler->state_handler) {
+                    api_handler->state_handler(getHttpContext(), HP_ERROR, NULL, 0);
+                }
+            }
+        };
+    } else {
+        // TODO: forward proxy
+        // TODO: reverse proxy
+        // TODO: rewrite
+        // NOTE: not hook http_cb
+        req->http_cb = NULL;
+    }
+}
+
 int HttpHandler::HandleHttpRequest() {
     // preprocessor -> processor -> postprocessor
     int status_code = HTTP_STATUS_OK;
     HttpRequest* pReq = req.get();
     HttpResponse* pResp = resp.get();
 
-    pReq->scheme = ssl ? "https" : "http";
-    pReq->client_addr.ip = ip;
-    pReq->client_addr.port = port;
-    pReq->ParseUrl();
     // NOTE: Not all users want to parse body, we comment it out.
     // pReq->ParseBody();
 
@@ -206,6 +247,9 @@ postprocessor:
         customHttpHandler(service->postprocessor);
     }
 
+    if (writer && writer->state != hv::HttpResponseWriter::SEND_BEGIN) {
+        status_code = 0;
+    }
     if (status_code == 0) {
         state = HANDLE_CONTINUE;
     } else {
@@ -217,14 +261,9 @@ postprocessor:
 
 int HttpHandler::defaultRequestHandler() {
     int status_code = HTTP_STATUS_OK;
-    http_handler* handler = NULL;
-
-    if (service->api_handlers.size() != 0) {
-        service->GetApi(req.get(), &handler);
-    }
 
-    if (handler) {
-        status_code = invokeHttpHandler(handler);
+    if (api_handler) {
+        status_code = invokeHttpHandler(api_handler);
     }
     else if (req->method == HTTP_GET || req->method == HTTP_HEAD) {
         // static handler

+ 5 - 0
http/server/HttpHandler.h

@@ -39,6 +39,8 @@ public:
     HttpResponsePtr         resp;
     HttpResponseWriterPtr   writer;
     HttpParserPtr           parser;
+    HttpContextPtr          ctx;
+    http_handler*           api_handler;
 
     // for sendfile
     FileCache               *files;
@@ -95,6 +97,9 @@ private:
     void closeFile();
     bool isFileOpened();
 
+    const HttpContextPtr& getHttpContext();
+    void onHeadersComplete();
+
     int defaultRequestHandler();
     int defaultStaticHandler();
     int defaultLargeFileHandler();

+ 5 - 0
http/server/HttpResponseWriter.h

@@ -52,6 +52,11 @@ public:
         return 0;
     }
 
+    int WriteCookie(const HttpCookie& cookie) {
+        response->cookies.push_back(cookie);
+        return 0;
+    }
+
     int EndHeaders(const char* key = NULL, const char* value = NULL) {
         if (state != SEND_BEGIN) return -1;
         if (key && value) {

+ 38 - 0
http/server/HttpService.h

@@ -38,20 +38,25 @@ typedef std::function<int(HttpRequest* req, HttpResponse* resp)>
 typedef std::function<void(const HttpRequestPtr& req, const HttpResponseWriterPtr& writer)> http_async_handler;
 // NOTE: http_ctx_handler run on IO thread, you can easily post HttpContextPtr to your consumer thread for processing.
 typedef std::function<int(const HttpContextPtr& ctx)>                                       http_ctx_handler;
+// NOTE: http_state_handler run on IO thread
+typedef std::function<int(const HttpContextPtr& ctx, http_parser_state state, const char* data, size_t size)> http_state_handler;
 
 struct http_handler {
     http_sync_handler   sync_handler;
     http_async_handler  async_handler;
     http_ctx_handler    ctx_handler;
+    http_state_handler  state_handler;
 
     http_handler()  {}
     http_handler(http_sync_handler fn)  : sync_handler(std::move(fn))   {}
     http_handler(http_async_handler fn) : async_handler(std::move(fn))  {}
     http_handler(http_ctx_handler fn)   : ctx_handler(std::move(fn))    {}
+    http_handler(http_state_handler fn) : state_handler(std::move(fn))  {}
     http_handler(const http_handler& rhs)
         : sync_handler(std::move(rhs.sync_handler))
         , async_handler(std::move(rhs.async_handler))
         , ctx_handler(std::move(rhs.ctx_handler))
+        , state_handler(std::move(rhs.state_handler))
     {}
 
     const http_handler& operator=(http_sync_handler fn) {
@@ -66,6 +71,10 @@ struct http_handler {
         ctx_handler = std::move(fn);
         return *this;
     }
+    const http_handler& operator=(http_state_handler fn) {
+        state_handler = std::move(fn);
+        return *this;
+    }
 
     bool isNull() {
         return  sync_handler == NULL &&
@@ -173,6 +182,9 @@ struct HV_EXPORT HttpService {
     void Handle(const char* httpMethod, const char* relativePath, http_ctx_handler handlerFunc) {
         AddApi(relativePath, http_method_enum(httpMethod), http_handler(handlerFunc));
     }
+    void Handle(const char* httpMethod, const char* relativePath, http_state_handler handlerFunc) {
+        AddApi(relativePath, http_method_enum(httpMethod), http_handler(handlerFunc));
+    }
 
     // HEAD
     void HEAD(const char* relativePath, http_sync_handler handlerFunc) {
@@ -184,6 +196,9 @@ struct HV_EXPORT HttpService {
     void HEAD(const char* relativePath, http_ctx_handler handlerFunc) {
         Handle("HEAD", relativePath, handlerFunc);
     }
+    void HEAD(const char* relativePath, http_state_handler handlerFunc) {
+        Handle("HEAD", relativePath, handlerFunc);
+    }
 
     // GET
     void GET(const char* relativePath, http_sync_handler handlerFunc) {
@@ -195,6 +210,9 @@ struct HV_EXPORT HttpService {
     void GET(const char* relativePath, http_ctx_handler handlerFunc) {
         Handle("GET", relativePath, handlerFunc);
     }
+    void GET(const char* relativePath, http_state_handler handlerFunc) {
+        Handle("GET", relativePath, handlerFunc);
+    }
 
     // POST
     void POST(const char* relativePath, http_sync_handler handlerFunc) {
@@ -206,6 +224,9 @@ struct HV_EXPORT HttpService {
     void POST(const char* relativePath, http_ctx_handler handlerFunc) {
         Handle("POST", relativePath, handlerFunc);
     }
+    void POST(const char* relativePath, http_state_handler handlerFunc) {
+        Handle("POST", relativePath, handlerFunc);
+    }
 
     // PUT
     void PUT(const char* relativePath, http_sync_handler handlerFunc) {
@@ -217,6 +238,9 @@ struct HV_EXPORT HttpService {
     void PUT(const char* relativePath, http_ctx_handler handlerFunc) {
         Handle("PUT", relativePath, handlerFunc);
     }
+    void PUT(const char* relativePath, http_state_handler handlerFunc) {
+        Handle("PUT", relativePath, handlerFunc);
+    }
 
     // DELETE
     // NOTE: Windows <winnt.h> #define DELETE as a macro, we have to replace DELETE with Delete.
@@ -229,6 +253,9 @@ struct HV_EXPORT HttpService {
     void Delete(const char* relativePath, http_ctx_handler handlerFunc) {
         Handle("DELETE", relativePath, handlerFunc);
     }
+    void Delete(const char* relativePath, http_state_handler handlerFunc) {
+        Handle("DELETE", relativePath, handlerFunc);
+    }
 
     // PATCH
     void PATCH(const char* relativePath, http_sync_handler handlerFunc) {
@@ -240,6 +267,9 @@ struct HV_EXPORT HttpService {
     void PATCH(const char* relativePath, http_ctx_handler handlerFunc) {
         Handle("PATCH", relativePath, handlerFunc);
     }
+    void PATCH(const char* relativePath, http_state_handler handlerFunc) {
+        Handle("PATCH", relativePath, handlerFunc);
+    }
 
     // Any
     void Any(const char* relativePath, http_sync_handler handlerFunc) {
@@ -266,6 +296,14 @@ struct HV_EXPORT HttpService {
         Handle("DELETE", relativePath, handlerFunc);
         Handle("PATCH", relativePath, handlerFunc);
     }
+    void Any(const char* relativePath, http_state_handler handlerFunc) {
+        Handle("HEAD", relativePath, handlerFunc);
+        Handle("GET", relativePath, handlerFunc);
+        Handle("POST", relativePath, handlerFunc);
+        Handle("PUT", relativePath, handlerFunc);
+        Handle("DELETE", relativePath, handlerFunc);
+        Handle("PATCH", relativePath, handlerFunc);
+    }
 };
 
 }