ithewei vor 4 Jahren
Ursprung
Commit
b735854c17

+ 3 - 2
Makefile.vars

@@ -82,7 +82,8 @@ HTTP_CLIENT_HEADERS =   http/client/http_client.h\
 						http/client/axios.h\
 						http/client/WebSocketClient.h\
 
-HTTP_SERVER_HEADERS =   http/server/HttpService.h\
-						http/server/HttpServer.h\
+HTTP_SERVER_HEADERS =   http/server/HttpServer.h\
+						http/server/HttpService.h\
+						http/server/HttpContext.h\
 						http/server/HttpResponseWriter.h\
 						http/server/WebSocketServer.h\

+ 2 - 1
cmake/vars.cmake

@@ -92,8 +92,9 @@ set(HTTP_CLIENT_HEADERS
     http/client/WebSocketClient.h)
 
 set(HTTP_SERVER_HEADERS
-    http/server/HttpService.h
     http/server/HttpServer.h
+    http/server/HttpService.h
+    http/server/HttpContext.h
     http/server/HttpResponseWriter.h
     http/server/WebSocketServer.h
 )

+ 53 - 57
examples/httpd/handler.h

@@ -42,20 +42,20 @@ public:
                 response_status(resp, 10012, "Token wrong");
                 return HTTP_STATUS_UNAUTHORIZED;
             }
-            return 0;
+            return HTTP_STATUS_UNFINISHED;
         }
 #endif
-        return 0;
+        return HTTP_STATUS_UNFINISHED;
     }
 
     static int postprocessor(HttpRequest* req, HttpResponse* resp) {
         // printf("%s\n", resp->Dump(true, true).c_str());
-        return 0;
+        return resp->status_code;
     }
 
     static int errorHandler(const HttpContextPtr& ctx) {
         int error_code = ctx->response->status_code;
-        return response_status(ctx->response.get(), error_code);
+        return response_status(ctx, error_code);
     }
 
     static int largeFileHandler(const HttpContextPtr& ctx) {
@@ -115,47 +115,46 @@ public:
             // 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 0;
+        return HTTP_STATUS_UNFINISHED;
     }
 
-    static int sleep(HttpRequest* req, HttpResponse* resp) {
-        resp->Set("start_ms", gettimeofday_ms());
-        std::string strTime = req->GetParam("t");
+    static int sleep(const HttpContextPtr& ctx) {
+        ctx->set("start_ms", gettimeofday_ms());
+        std::string strTime = ctx->param("t", "1000");
         if (!strTime.empty()) {
             int ms = atoi(strTime.c_str());
             if (ms > 0) {
                 hv_delay(ms);
             }
         }
-        resp->Set("end_ms", gettimeofday_ms());
-        response_status(resp, 0, "OK");
+        ctx->set("end_ms", gettimeofday_ms());
+        response_status(ctx, 0, "OK");
         return 200;
     }
 
-    static void setTimeout(const HttpRequestPtr& req, const HttpResponseWriterPtr& writer) {
-        writer->response->Set("start_ms", gettimeofday_ms());
-        std::string strTime = req->GetParam("t");
+    static int setTimeout(const HttpContextPtr& ctx) {
+        ctx->set("start_ms", gettimeofday_ms());
+        std::string strTime = ctx->param("t", "1000");
         if (!strTime.empty()) {
             int ms = atoi(strTime.c_str());
             if (ms > 0) {
-                hv::setTimeout(ms, [writer](hv::TimerID timerID){
-                    writer->Begin();
-                    HttpResponse* resp = writer->response.get();
-                    resp->Set("end_ms", gettimeofday_ms());
-                    response_status(resp, 0, "OK");
-                    writer->End();
+                hv::setTimeout(ms, [ctx](hv::TimerID timerID){
+                    ctx->set("end_ms", gettimeofday_ms());
+                    response_status(ctx, 0, "OK");
+                    ctx->send();
                 });
             }
         }
+        return HTTP_STATUS_UNFINISHED;
     }
 
-    static int query(HttpRequest* req, HttpResponse* resp) {
+    static int query(const HttpContextPtr& ctx) {
         // scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
         // ?query => HttpRequest::query_params
-        for (auto& param : req->query_params) {
-            resp->Set(param.first.c_str(), param.second);
+        for (auto& param : ctx->params()) {
+            ctx->set(param.first.c_str(), param.second);
         }
-        response_status(resp, 0, "OK");
+        response_status(ctx, 0, "OK");
         return 200;
     }
 
@@ -197,24 +196,6 @@ public:
         return 200;
     }
 
-    static int test(HttpRequest* req, HttpResponse* resp) {
-        // bool b = req->Get<bool>("bool");
-        // int64_t n = req->Get<int64_t>("int");
-        // double f = req->Get<double>("float");
-        bool b = req->GetBool("bool");
-        int64_t n = req->GetInt("int");
-        double f = req->GetFloat("float");
-        string str = req->GetString("string");
-
-        resp->content_type = req->content_type;
-        resp->Set("bool", b);
-        resp->Set("int", n);
-        resp->Set("float", f);
-        resp->Set("string", str);
-        response_status(resp, 0, "OK");
-        return 200;
-    }
-
     static int grpc(HttpRequest* req, HttpResponse* resp) {
         if (req->content_type != APPLICATION_GRPC) {
             return response_status(resp, HTTP_STATUS_BAD_REQUEST);
@@ -228,35 +209,45 @@ public:
         return 200;
     }
 
-    static int restful(HttpRequest* req, HttpResponse* resp) {
+    static int test(const HttpContextPtr& ctx) {
+        ctx->setContentType(ctx->type());
+        ctx->set("bool", ctx->get<bool>("bool"));
+        ctx->set("int", ctx->get<int>("int"));
+        ctx->set("float", ctx->get<float>("float"));
+        ctx->set("string", ctx->get("string"));
+        response_status(ctx, 0, "OK");
+        return 200;
+    }
+
+    static int restful(const HttpContextPtr& ctx) {
         // RESTful /:field/ => HttpRequest::query_params
         // path=/group/:group_name/user/:user_id
-        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");
+        std::string group_name = ctx->param("group_name");
+        std::string user_id = ctx->param("user_id");
+        ctx->set("group_name", group_name);
+        ctx->set("user_id", user_id);
+        response_status(ctx, 0, "OK");
         return 200;
     }
 
-    static int login(HttpRequest* req, HttpResponse* resp) {
-        string username = req->GetString("username");
-        string password = req->GetString("password");
+    static int login(const HttpContextPtr& ctx) {
+        string username = ctx->get("username");
+        string password = ctx->get("password");
         if (username.empty() || password.empty()) {
-            response_status(resp, 10001, "Miss username or password");
+            response_status(ctx, 10001, "Miss username or password");
             return HTTP_STATUS_BAD_REQUEST;
         }
         else if (strcmp(username.c_str(), "admin") != 0) {
-            response_status(resp, 10002, "Username not exist");
+            response_status(ctx, 10002, "Username not exist");
             return HTTP_STATUS_BAD_REQUEST;
         }
         else if (strcmp(password.c_str(), "123456") != 0) {
-            response_status(resp, 10003, "Password wrong");
+            response_status(ctx, 10003, "Password wrong");
             return HTTP_STATUS_BAD_REQUEST;
         }
         else {
-            resp->Set("token", "abcdefg");
-            response_status(resp, 0, "OK");
+            ctx->set("token", "abcdefg");
+            response_status(ctx, 0, "OK");
             return HTTP_STATUS_OK;
         }
     }
@@ -283,10 +274,15 @@ public:
 
 private:
     static int response_status(HttpResponse* resp, int code = 200, const char* message = NULL) {
-        resp->Set("code", code);
         if (message == NULL) message = http_status_str((enum http_status)code);
+        resp->Set("code", code);
         resp->Set("message", message);
-        resp->DumpBody();
+        return code;
+    }
+    static int response_status(const HttpContextPtr& ctx, int code = 200, const char* message = NULL) {
+        if (message == NULL) message = http_status_str((enum http_status)code);
+        ctx->set("code", code);
+        ctx->set("message", message);
         return code;
     }
 };

+ 8 - 8
examples/httpd/router.h

@@ -49,11 +49,12 @@ public:
 
         // curl -v http://ip:port/service
         router.GET("/service", [](const HttpContextPtr& ctx) {
-            ctx->response->json["base_url"] = ctx->service->base_url;
-            ctx->response->json["doucument_root"] = ctx->service->document_root;
-            ctx->response->json["home_page"] = ctx->service->home_page;
-            ctx->response->json["error_page"] = ctx->service->error_page;
-            ctx->response->json["index_of"] = ctx->service->index_of;
+            ctx->setContentType("application/json");
+            ctx->set("base_url", ctx->service->base_url);
+            ctx->set("document_root", ctx->service->document_root);
+            ctx->set("home_page", ctx->service->home_page);
+            ctx->set("error_page", ctx->service->error_page);
+            ctx->set("index_of", ctx->service->index_of);
             return 200;
         });
 
@@ -73,10 +74,9 @@ public:
 
         // curl -v http://ip:port/async
         router.GET("/async", [](const HttpRequestPtr& req, const HttpResponseWriterPtr& writer) {
-            writer->response->headers["X-Request-tid"] = hv::to_string(hv_gettid());
+            writer->WriteHeader("X-Request-tid", hv_gettid());
             std::async([req, writer](){
-                writer->Begin();
-                writer->response->headers["X-Response-tid"] = hv::to_string(hv_gettid());
+                writer->WriteHeader("X-Response-tid", hv_gettid());
                 writer->WriteHeader("Content-Type", "text/plain");
                 writer->WriteBody("This is an async response.\n");
                 writer->End();

+ 10 - 0
http/HttpMessage.cpp

@@ -167,6 +167,11 @@ HV_EXPORT int64_t HttpMessage::Get(const char* key, int64_t defvalue) {
     }
 }
 
+template<>
+HV_EXPORT int HttpMessage::Get(const char* key, int defvalue) {
+    return (int)Get<int64_t>(key, defvalue);
+}
+
 template<>
 HV_EXPORT double HttpMessage::Get(const char* key, double defvalue) {
     if (content_type == APPLICATION_JSON) {
@@ -194,6 +199,11 @@ HV_EXPORT double HttpMessage::Get(const char* key, double defvalue) {
     }
 }
 
+template<>
+HV_EXPORT float HttpMessage::Get(const char* key, float defvalue) {
+    return (float)Get<double>(key, defvalue);
+}
+
 template<>
 HV_EXPORT bool HttpMessage::Get(const char* key, bool defvalue) {
     if (content_type == APPLICATION_JSON) {

+ 1 - 1
http/HttpMessage.h

@@ -94,7 +94,7 @@ public:
     MultiPart           form;       // MULTIPART_FORM_DATA
     hv::KeyValue        kv;         // X_WWW_FORM_URLENCODED
 
-    // T=[bool, int64_t, double]
+    // T=[bool, int, int64_t, float, double]
     template<typename T>
     T Get(const char* key, T defvalue = 0);
 

+ 191 - 0
http/server/HttpContext.h

@@ -0,0 +1,191 @@
+#ifndef HV_HTTP_CONTEXT_H_
+#define HV_HTTP_CONTEXT_H_
+
+#include "hexport.h"
+#include "HttpMessage.h"
+#include "HttpResponseWriter.h"
+
+struct HttpService;
+
+namespace hv {
+
+struct HV_EXPORT HttpContext {
+    HttpService*            service;
+    HttpRequestPtr          request;
+    HttpResponsePtr         response;
+    HttpResponseWriterPtr   writer;
+
+    // HttpRequest aliases
+    // return request->xxx
+    std::string ip() {
+        return request->client_addr.ip;
+    }
+
+    http_method method() {
+        return request->method;
+    }
+
+    std::string url() {
+        return request->url;
+    }
+
+    std::string path() {
+        return request->Path();
+    }
+
+    std::string host() {
+        return request->Host();
+    }
+
+    const http_headers& headers() {
+        return request->headers;
+    }
+
+    std::string header(const char* key, const std::string& defvalue = "") {
+        return request->GetHeader(key, defvalue);
+    }
+
+    const QueryParams& params() {
+        return request->query_params;
+    }
+
+    std::string param(const char* key, const std::string& defvalue = "") {
+        return request->GetParam(key, defvalue);
+    }
+
+    int length() {
+        return request->ContentLength();
+    }
+
+    http_content_type type() {
+        return request->ContentType();
+    }
+
+    bool is(http_content_type content_type) {
+        return request->content_type == content_type;
+    }
+
+    bool is(const char* content_type) {
+        return request->content_type == http_content_type_enum(content_type);
+    }
+
+    std::string& body() {
+        return response->body;
+    }
+
+#ifndef WITHOUT_HTTP_CONTENT
+    const hv::Json& json() {
+        // Content-Type: application/json
+        if (request->content_type == APPLICATION_JSON &&
+            request->json.empty() &&
+            !request->body.empty()) {
+            request->ParseBody();
+        }
+        return request->json;
+    }
+
+    const MultiPart& form() {
+        // Content-Type: multipart/form-data
+        if (request->content_type == MULTIPART_FORM_DATA &&
+            request->form.empty() &&
+            !request->body.empty()) {
+            request->ParseBody();
+        }
+        return request->form;
+    }
+
+    const hv::KeyValue& urlencoded() {
+        // Content-Type: application/x-www-form-urlencoded
+        if (request->content_type == X_WWW_FORM_URLENCODED &&
+            request->kv.empty() &&
+            !request->body.empty()) {
+            request->ParseBody();
+        }
+        return request->kv;
+    }
+
+    // T=[bool, int, int64_t, float, double]
+    template<typename T>
+    T get(const char* key, T defvalue = 0) {
+        return request->Get(key, defvalue);
+    }
+    std::string get(const char* key, const std::string& defvalue = "") {
+        return request->GetString(key, defvalue);
+    }
+#endif
+
+    // HttpResponse aliases
+    // response->xxx = xxx
+    void setStatus(http_status status) {
+        response->status_code = status;
+    }
+
+    void setContentType(http_content_type type) {
+        response->content_type = type;
+    }
+
+    void setContentType(const char* type) {
+        response->content_type = http_content_type_enum(type);
+    }
+
+    void setHeader(const char* key, const std::string& value) {
+        response->headers[key] = value;
+        if (stricmp(key, "Content-Type") == 0) {
+            setContentType(value.c_str());
+        }
+    }
+
+    void setBody(const std::string& body) {
+        response->body = body;
+    }
+
+    // response->sendXxx
+    int send() {
+        writer->End();
+        return response->status_code;
+    }
+
+    int send(const std::string& str, http_content_type type = APPLICATION_JSON) {
+        response->content_type = type;
+        response->body = str;
+        return send();
+    }
+
+    int sendString(const std::string& str) {
+        response->String(str);
+        return send();
+    }
+
+    int sendData(void* data, int len, bool nocopy = true) {
+        response->Data(data, len, nocopy);
+        return send();
+    }
+
+    int sendFile(const char* filepath) {
+        response->File(filepath);
+        return send();
+    }
+
+#ifndef WITHOUT_HTTP_CONTENT
+    // T=[bool, int, int64_t, float, double, string]
+    template<typename T>
+    void set(const char* key, const T& value) {
+        response->Set(key, value);
+    }
+
+    // @see     HttpMessage::Json
+    // @usage   https://github.com/nlohmann/json
+    template<typename T>
+    int sendJson(const T& t) {
+        response->Json(t);
+        return send();
+    }
+#endif
+
+};
+
+} // end namespace hv
+
+typedef std::shared_ptr<hv::HttpContext> HttpContextPtr;
+
+#endif // HV_HTTP_CONTEXT_H_

+ 3 - 6
http/server/HttpHandler.cpp

@@ -66,7 +66,7 @@ postprocessor:
 }
 
 int HttpHandler::customHttpHandler(http_handler& fn) {
-    HttpContextPtr ctx(new HttpContext);
+    HttpContextPtr ctx(new hv::HttpContext);
     ctx->service = service;
     ctx->request = req;
     ctx->response = resp;
@@ -87,9 +87,6 @@ int HttpHandler::defaultRequestHandler() {
     if (sync_handler) {
         // sync api handler
         status_code = sync_handler(req.get(), resp.get());
-        if (status_code != 0) {
-            return status_code;
-        }
     }
     else if (async_handler) {
         // async api handler
@@ -99,8 +96,8 @@ int HttpHandler::defaultRequestHandler() {
     else if (ctx_handler) {
         // HttpContext handler
         status_code = customHttpHandler(ctx_handler);
-        if (status_code != 0) {
-            return status_code;
+        if (writer->state != hv::HttpResponseWriter::SEND_BEGIN) {
+            status_code = 0;
         }
     }
     else if (req->method == HTTP_GET || req->method == HTTP_HEAD) {

+ 6 - 4
http/server/HttpServer.cpp

@@ -255,6 +255,8 @@ static void on_close(hio_t* io) {
 }
 
 static void on_accept(hio_t* io) {
+    http_server_t* server = (http_server_t*)hevent_userdata(io);
+    HttpService* service = server->service;
     /*
     printf("on_accept connfd=%d\n", hio_fd(io));
     char localaddrstr[SOCKADDR_STRLEN] = {0};
@@ -267,18 +269,18 @@ static void on_accept(hio_t* io) {
     hio_setcb_close(io, on_close);
     hio_setcb_read(io, on_recv);
     hio_read(io);
-    hio_set_keepalive_timeout(io, HIO_DEFAULT_KEEPALIVE_TIMEOUT);
+    hio_set_keepalive_timeout(io, service->keepalive_timeout);
+
     // new HttpHandler, delete on_close
     HttpHandler* handler = new HttpHandler;
     // ssl
-    handler->ssl = hio_type(io) == HIO_TYPE_SSL;
+    handler->ssl = hio_is_ssl(io);
     // ip
     sockaddr_ip((sockaddr_u*)hio_peeraddr(io), handler->ip, sizeof(handler->ip));
     // port
     handler->port = sockaddr_port((sockaddr_u*)hio_peeraddr(io));
     // service
-    http_server_t* server = (http_server_t*)hevent_userdata(io);
-    handler->service = server->service;
+    handler->service = service;
     // ws
     handler->ws_service = server->ws;
     // FileCache

+ 10 - 11
http/server/HttpService.h

@@ -10,31 +10,25 @@
 #include "hexport.h"
 #include "HttpMessage.h"
 #include "HttpResponseWriter.h"
+#include "HttpContext.h"
 
 #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"
 #define DEFAULT_INDEXOF_DIR     "/downloads/"
+#define DEFAULT_KEEPALIVE_TIMEOUT   75000   // ms
 
 /*
  * @param[in]  req:  parsed structured http request
  * @param[out] resp: structured http response
- * @return  0:                  handle continue
+ * @return  0:                  handle unfinished
  *          http_status_code:   handle done
  */
+#define HTTP_STATUS_UNFINISHED  0
 typedef std::function<int(HttpRequest* req, HttpResponse* resp)>                            http_sync_handler;
 typedef std::function<void(const HttpRequestPtr& req, const HttpResponseWriterPtr& writer)> http_async_handler;
-
-struct HttpService;
-struct HV_EXPORT HttpContext {
-    HttpService*            service;
-    HttpRequestPtr          request;
-    HttpResponsePtr         response;
-    HttpResponseWriterPtr   writer;
-};
-typedef std::shared_ptr<HttpContext>                    HttpContextPtr;
-typedef std::function<int(const HttpContextPtr& ctx)>   http_handler;
+typedef std::function<int(const HttpContextPtr& ctx)>                                       http_handler;
 
 struct http_method_handler {
     http_method         method;
@@ -79,6 +73,9 @@ struct HV_EXPORT HttpService {
 
     http_handler    errorHandler;
 
+    // options
+    int keepalive_timeout;
+
     HttpService() {
         preprocessor = NULL;
         processor = NULL;
@@ -95,6 +92,8 @@ struct HV_EXPORT HttpService {
         // index_of = DEFAULT_INDEXOF_DIR;
 
         errorHandler = NULL;
+
+        keepalive_timeout = DEFAULT_KEEPALIVE_TIMEOUT;
     }
 
     void AddApi(const char* path, http_method method,