Переглянути джерело

export http client low-level api

ithewei 2 роки тому
батько
коміт
dbcd97ced0
3 змінених файлів з 288 додано та 203 видалено
  1. 3 0
      base/hsocket.h
  2. 260 201
      http/client/http_client.cpp
  3. 25 2
      http/client/http_client.h

+ 3 - 0
base/hsocket.h

@@ -52,6 +52,9 @@ HV_INLINE int nonblocking(int sockfd) {
 #undef  EINPROGRESS
 #define EINPROGRESS WSAEINPROGRESS
 
+#undef  EINTR
+#define EINTR       WSAEINTR
+
 #undef  ENOTSOCK
 #define ENOTSOCK    WSAENOTSOCK
 

+ 260 - 201
http/client/http_client.cpp

@@ -85,8 +85,6 @@ struct http_client_s {
     }
 };
 
-static int __http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* resp);
-
 http_client_t* http_client_new(const char* host, int port, int https) {
     http_client_t* cli = new http_client_t;
     if (host) cli->host = host;
@@ -102,12 +100,6 @@ int http_client_del(http_client_t* cli) {
     return 0;
 }
 
-int http_client_close(http_client_t* cli) {
-    if (cli == NULL) return 0;
-    cli->Close();
-    return 0;
-}
-
 int http_client_set_timeout(http_client_t* cli, int timeout) {
     cli->timeout = timeout;
     return 0;
@@ -169,19 +161,6 @@ int http_client_add_no_proxy(http_client_t* cli, const char* host) {
     return 0;
 }
 
-static int http_client_redirect(HttpRequest* req, HttpResponse* resp) {
-    std::string location = resp->headers["Location"];
-    if (!location.empty()) {
-        hlogi("redirect %s => %s", req->url.c_str(), location.c_str());
-        req->url = location;
-        req->ParseUrl();
-        req->headers["Host"] = req->host;
-        resp->Reset();
-        return http_client_send(req, resp);
-    }
-    return 0;
-}
-
 static int http_client_make_request(http_client_t* cli, HttpRequest* req) {
     if (req->url.empty() || *req->url.c_str() == '/') {
         req->scheme = cli->https ? "https" : "http";
@@ -223,28 +202,219 @@ static int http_client_make_request(http_client_t* cli, HttpRequest* req) {
     return 0;
 }
 
-int http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* resp) {
-    if (!cli || !req || !resp) return ERR_NULL_POINTER;
+int http_client_connect(http_client_t* cli, const char* host, int port, int https, int timeout) {
+    int blocktime = DEFAULT_CONNECT_TIMEOUT;
+    if (timeout > 0) {
+        blocktime = MIN(timeout*1000, blocktime);
+    }
+    int connfd = ConnectTimeout(host, port, blocktime);
+    if (connfd < 0) {
+        fprintf(stderr, "* connect %s:%d failed!\n", host, port);
+        hloge("connect %s:%d failed!", host, port);
+        return connfd;
+    }
+    tcp_nodelay(connfd, 1);
 
-    http_client_make_request(cli, req);
+    if (https && cli->ssl == NULL) {
+        // cli->ssl_ctx > g_ssl_ctx > hssl_ctx_new
+        hssl_ctx_t ssl_ctx = NULL;
+        if (cli->ssl_ctx) {
+            ssl_ctx = cli->ssl_ctx;
+        } else if (g_ssl_ctx) {
+            ssl_ctx = g_ssl_ctx;
+        } else {
+            cli->ssl_ctx = ssl_ctx = hssl_ctx_new(NULL);
+            cli->alloced_ssl_ctx = true;
+        }
+        if (ssl_ctx == NULL) {
+            closesocket(connfd);
+            return NABS(ERR_NEW_SSL_CTX);
+        }
+        cli->ssl = hssl_new(ssl_ctx, connfd);
+        if (cli->ssl == NULL) {
+            closesocket(connfd);
+            return NABS(ERR_NEW_SSL);
+        }
+        if (!is_ipaddr(host)) {
+            hssl_set_sni_hostname(cli->ssl, host);
+        }
+        int ret = hssl_connect(cli->ssl);
+        if (ret != 0) {
+            fprintf(stderr, "* ssl handshake failed: %d\n", ret);
+            hloge("ssl handshake failed: %d", ret);
+            hssl_free(cli->ssl);
+            cli->ssl = NULL;
+            closesocket(connfd);
+            return NABS(ret);
+        }
+    }
 
-    if (req->http_cb) resp->http_cb = std::move(req->http_cb);
+    cli->fd = connfd;
+    return connfd;
+}
 
-    int ret = __http_client_send(cli, req, resp);
-    if (ret != 0) return ret;
+int http_client_close(http_client_t* cli) {
+    if (cli == NULL) return 0;
+    cli->Close();
+    return 0;
+}
 
-    // redirect
-    if (req->redirect && HTTP_STATUS_IS_REDIRECT(resp->status_code)) {
-        return http_client_redirect(req, resp);
+int http_client_send_data(http_client_t* cli, const char* data, int size) {
+    if (!cli || !data || size <= 0) return -1;
+
+    if (cli->ssl) {
+        return hssl_write(cli->ssl, data, size);
+    }
+
+    return send(cli->fd, data, size, 0);
+}
+
+int http_client_recv_data(http_client_t* cli, char* data, int size) {
+    if (!cli || !data || size <= 0) return -1;
+
+    if (cli->ssl) {
+        return hssl_read(cli->ssl, data, size);
+    }
+
+    return recv(cli->fd, data, size, 0);
+}
+
+static int http_client_exec(http_client_t* cli, HttpRequest* req, HttpResponse* resp) {
+    // connect -> send -> recv -> http_parser
+    int err = 0;
+    int timeout = req->timeout;
+    int connfd = cli->fd;
+    bool https = req->IsHttps() && !req->IsProxy();
+    bool keepalive = true;
+
+    time_t start_time = time(NULL);
+    time_t cur_time;
+    int fail_cnt = 0;
+    if (connfd <= 0) {
+connect:
+        connfd = http_client_connect(cli, req->host.c_str(), req->port, https, MIN(req->connect_timeout, req->timeout));
+        if (connfd < 0) {
+            return connfd;
+        }
+    }
+
+    if (cli->parser == NULL) {
+        cli->parser = HttpParserPtr(HttpParser::New(HTTP_CLIENT, (http_version)req->http_major));
+        if (cli->parser == NULL) {
+            hloge("New HttpParser failed!");
+            return ERR_NULL_POINTER;
+        }
+    }
+    cli->parser->SubmitRequest(req);
+    char recvbuf[1024] = {0};
+    int total_nsend, nsend, nrecv;
+    total_nsend = nsend = nrecv = 0;
+send:
+    char* data = NULL;
+    size_t len  = 0;
+    while (cli->parser->GetSendData(&data, &len)) {
+        total_nsend = 0;
+        while (total_nsend < len) {
+            if (timeout > 0) {
+                cur_time = time(NULL);
+                if (cur_time - start_time >= timeout) {
+                    return ERR_TASK_TIMEOUT;
+                }
+                so_sndtimeo(cli->fd, (timeout-(cur_time-start_time)) * 1000);
+            }
+            nsend = http_client_send_data(cli, data + total_nsend, len - total_nsend);
+            if (nsend <= 0) {
+                if (++fail_cnt == 1) {
+                    // maybe keep-alive timeout, try again
+                    cli->Close();
+                    goto connect;
+                }
+                else {
+                    err = socket_errno();
+                    if (err == EINTR) continue;
+                    goto disconnect;
+                }
+            }
+            total_nsend += nsend;
+        }
+    }
+    if (resp == NULL) return 0;
+    cli->parser->InitResponse(resp);
+recv:
+    do {
+        if (timeout > 0) {
+            cur_time = time(NULL);
+            if (cur_time - start_time >= timeout) {
+                return ERR_TASK_TIMEOUT;
+            }
+            so_rcvtimeo(cli->fd, (timeout-(cur_time-start_time)) * 1000);
+        }
+        nrecv = http_client_recv_data(cli, recvbuf, sizeof(recvbuf));
+        if (nrecv <= 0) {
+            if (resp->content_length == 0 && resp->http_major == 1 && resp->http_minor == 0) {
+                // HTTP/1.0, assume close after body
+                goto disconnect;
+            }
+            if (++fail_cnt == 1) {
+                // maybe keep-alive timeout, try again
+                cli->Close();
+                goto connect;
+            }
+            else {
+                err = socket_errno();
+                if (err == EINTR) continue;
+                goto disconnect;
+            }
+        }
+        int nparse = cli->parser->FeedRecvData(recvbuf, nrecv);
+        if (nparse != nrecv) {
+            return ERR_PARSE;
+        }
+    } while(!cli->parser->IsComplete());
+
+    keepalive = req->IsKeepAlive() && resp->IsKeepAlive();
+    if (!keepalive) {
+        cli->Close();
     }
     return 0;
+disconnect:
+    cli->Close();
+    return err;
 }
 
-int http_client_send(HttpRequest* req, HttpResponse* resp) {
-    if (!req || !resp) return ERR_NULL_POINTER;
+int http_client_send_header(http_client_t* cli, HttpRequest* req) {
+    if (!cli || !req) return ERR_NULL_POINTER;
 
-    http_client_t cli;
-    return http_client_send(&cli, req, resp);
+    http_client_make_request(cli, req);
+
+    return http_client_exec(cli, req, NULL);
+}
+
+int http_client_recv_response(http_client_t* cli, HttpResponse* resp) {
+    if (!cli || !resp) return ERR_NULL_POINTER;
+    if (!cli->parser) {
+        hloge("Call http_client_send_header first!");
+        return ERR_NULL_POINTER;
+    }
+
+    char recvbuf[1024] = {0};
+    cli->parser->InitResponse(resp);
+
+    do {
+        int nrecv = http_client_recv_data(cli, recvbuf, sizeof(recvbuf));
+        if (nrecv <= 0) {
+            int err = socket_errno();
+            if (err == EINTR) continue;
+            cli->Close();
+            return err;
+        }
+        int nparse = cli->parser->FeedRecvData(recvbuf, nrecv);
+        if (nparse != nrecv) {
+            return ERR_PARSE;
+        }
+    } while(!cli->parser->IsComplete());
+
+    return 0;
 }
 
 #ifdef WITH_CURL
@@ -302,7 +472,7 @@ static size_t s_body_cb(char* buf, size_t size, size_t cnt, void *userdata) {
     return len;
 }
 
-int __http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* resp) {
+static int http_client_exec_curl(http_client_t* cli, HttpRequest* req, HttpResponse* resp) {
     if (cli->curl == NULL) {
         cli->curl = curl_easy_init();
     }
@@ -434,170 +604,58 @@ int __http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* resp)
 const char* http_client_strerror(int errcode) {
     return curl_easy_strerror((CURLcode)errcode);
 }
-#else
-static int http_client_connect(http_client_t* cli, const char* host, int port, int https, int timeout) {
-    int blocktime = DEFAULT_CONNECT_TIMEOUT;
-    if (timeout > 0) {
-        blocktime = MIN(timeout*1000, blocktime);
-    }
-    int connfd = ConnectTimeout(host, port, blocktime);
-    if (connfd < 0) {
-        fprintf(stderr, "* connect %s:%d failed!\n", host, port);
-        hloge("connect %s:%d failed!", host, port);
-        return connfd;
-    }
-    tcp_nodelay(connfd, 1);
 
-    if (https && cli->ssl == NULL) {
-        // cli->ssl_ctx > g_ssl_ctx > hssl_ctx_new
-        hssl_ctx_t ssl_ctx = NULL;
-        if (cli->ssl_ctx) {
-            ssl_ctx = cli->ssl_ctx;
-        } else if (g_ssl_ctx) {
-            ssl_ctx = g_ssl_ctx;
-        } else {
-            cli->ssl_ctx = ssl_ctx = hssl_ctx_new(NULL);
-            cli->alloced_ssl_ctx = true;
-        }
-        if (ssl_ctx == NULL) {
-            closesocket(connfd);
-            return NABS(ERR_NEW_SSL_CTX);
-        }
-        cli->ssl = hssl_new(ssl_ctx, connfd);
-        if (cli->ssl == NULL) {
-            closesocket(connfd);
-            return NABS(ERR_NEW_SSL);
-        }
-        if (!is_ipaddr(host)) {
-            hssl_set_sni_hostname(cli->ssl, host);
-        }
-        int ret = hssl_connect(cli->ssl);
-        if (ret != 0) {
-            fprintf(stderr, "* ssl handshake failed: %d\n", ret);
-            hloge("ssl handshake failed: %d", ret);
-            hssl_free(cli->ssl);
-            cli->ssl = NULL;
-            closesocket(connfd);
-            return NABS(ret);
-        }
-    }
+#else
 
-    cli->fd = connfd;
-    return connfd;
+const char* http_client_strerror(int errcode) {
+    return socket_strerror(errcode);
 }
 
-int __http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* resp) {
-    // connect -> send -> recv -> http_parser
-    int err = 0;
-    int timeout = req->timeout;
-    int connfd = cli->fd;
-    bool https = req->IsHttps() && !req->IsProxy();
-    bool keepalive = true;
+#endif
 
-    time_t start_time = time(NULL);
-    time_t cur_time;
-    int fail_cnt = 0;
-    if (connfd <= 0) {
-connect:
-        connfd = http_client_connect(cli, req->host.c_str(), req->port, https, MIN(req->connect_timeout, req->timeout));
-        if (connfd < 0) {
-            return connfd;
-        }
+static int http_client_redirect(HttpRequest* req, HttpResponse* resp) {
+    std::string location = resp->headers["Location"];
+    if (!location.empty()) {
+        hlogi("redirect %s => %s", req->url.c_str(), location.c_str());
+        req->url = location;
+        req->ParseUrl();
+        req->headers["Host"] = req->host;
+        resp->Reset();
+        return http_client_send(req, resp);
     }
+    return 0;
+}
 
-    if (cli->parser == NULL) {
-        cli->parser = HttpParserPtr(HttpParser::New(HTTP_CLIENT, (http_version)req->http_major));
-    }
-    cli->parser->SubmitRequest(req);
-    char recvbuf[1024] = {0};
-    int total_nsend, nsend, nrecv;
-    total_nsend = nsend = nrecv = 0;
-send:
-    char* data = NULL;
-    size_t len  = 0;
-    while (cli->parser->GetSendData(&data, &len)) {
-        total_nsend = 0;
-        while (total_nsend < len) {
-            if (timeout > 0) {
-                cur_time = time(NULL);
-                if (cur_time - start_time >= timeout) {
-                    return ERR_TASK_TIMEOUT;
-                }
-                so_sndtimeo(connfd, (timeout-(cur_time-start_time)) * 1000);
-            }
-            if (https) {
-                nsend = hssl_write(cli->ssl, data+total_nsend, len-total_nsend);
-            }
-            else {
-                nsend = send(connfd, data+total_nsend, len-total_nsend, 0);
-            }
-            if (nsend <= 0) {
-                if (++fail_cnt == 1) {
-                    // maybe keep-alive timeout, try again
-                    cli->Close();
-                    goto connect;
-                }
-                else {
-                    err = socket_errno();
-                    goto disconnect;
-                }
-            }
-            total_nsend += nsend;
-        }
-    }
-    cli->parser->InitResponse(resp);
-recv:
-    do {
-        if (timeout > 0) {
-            cur_time = time(NULL);
-            if (cur_time - start_time >= timeout) {
-                return ERR_TASK_TIMEOUT;
-            }
-            so_rcvtimeo(connfd, (timeout-(cur_time-start_time)) * 1000);
-        }
-        if (https) {
-            nrecv = hssl_read(cli->ssl, recvbuf, sizeof(recvbuf));
-        }
-        else {
-            nrecv = recv(connfd, recvbuf, sizeof(recvbuf), 0);
-        }
-        if (nrecv <= 0) {
-            if (resp->content_length == 0 && resp->http_major == 1 && resp->http_minor == 0) {
-                // HTTP/1.0, assume close after body
-                goto disconnect;
-            }
-            if (++fail_cnt == 1) {
-                // maybe keep-alive timeout, try again
-                cli->Close();
-                goto connect;
-            }
-            else {
-                err = socket_errno();
-                goto disconnect;
-            }
-        }
-        int nparse = cli->parser->FeedRecvData(recvbuf, nrecv);
-        if (nparse != nrecv) {
-            return ERR_PARSE;
-        }
-    } while(!cli->parser->IsComplete());
+int http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* resp) {
+    if (!cli || !req || !resp) return ERR_NULL_POINTER;
 
-    keepalive = req->IsKeepAlive() && resp->IsKeepAlive();
-    if (!keepalive) {
-        cli->Close();
+    http_client_make_request(cli, req);
+
+    if (req->http_cb) resp->http_cb = std::move(req->http_cb);
+
+#if WITH_CURL
+    int ret = http_client_exec_curl(cli, req, resp);
+#else
+    int ret = http_client_exec(cli, req, resp);
+#endif
+    if (ret != 0) return ret;
+
+    // redirect
+    if (req->redirect && HTTP_STATUS_IS_REDIRECT(resp->status_code)) {
+        return http_client_redirect(req, resp);
     }
     return 0;
-disconnect:
-    cli->Close();
-    return err;
 }
 
-const char* http_client_strerror(int errcode) {
-    return socket_strerror(errcode);
+int http_client_send(HttpRequest* req, HttpResponse* resp) {
+    if (!req || !resp) return ERR_NULL_POINTER;
+
+    http_client_t cli;
+    return http_client_send(&cli, req, resp);
 }
-#endif
 
-static int __http_client_send_async(http_client_t* cli, HttpRequestPtr req, HttpResponseCallback resp_cb) {
+// below for async
+static int http_client_exec_async(http_client_t* cli, HttpRequestPtr req, HttpResponseCallback resp_cb) {
     if (cli->async_client_ == NULL) {
         cli->mutex_.lock();
         if (cli->async_client_ == NULL) {
@@ -612,29 +670,30 @@ static int __http_client_send_async(http_client_t* cli, HttpRequestPtr req, Http
 int http_client_send_async(http_client_t* cli, HttpRequestPtr req, HttpResponseCallback resp_cb) {
     if (!cli || !req) return ERR_NULL_POINTER;
     http_client_make_request(cli, req.get());
-    return __http_client_send_async(cli, req, std::move(resp_cb));
+    return http_client_exec_async(cli, req, std::move(resp_cb));
 }
 
-static http_client_t* __get_default_async_client();
-static void           __del_default_async_client();
-http_client_t* __get_default_async_client() {
-    static http_client_t* s_default_async_client = NULL;
-    static std::mutex     s_mutex;
-    if (s_default_async_client == NULL) {
+static http_client_t* s_async_http_client = NULL;
+static void hv_destroy_default_async_http_client() {
+    hlogi("destory default http async client");
+    if (s_async_http_client) {
+        http_client_del(s_async_http_client);
+        s_async_http_client = NULL;
+    }
+}
+static http_client_t* hv_default_async_http_client() {
+    static std::mutex s_mutex;
+    if (s_async_http_client == NULL) {
         s_mutex.lock();
-        if (s_default_async_client == NULL) {
+        if (s_async_http_client == NULL) {
             hlogi("create default http async client");
-            s_default_async_client = http_client_new();
+            s_async_http_client = http_client_new();
             // NOTE: I have No better idea
-            atexit(__del_default_async_client);
+            atexit(hv_destroy_default_async_http_client);
         }
         s_mutex.unlock();
     }
-    return s_default_async_client;
-}
-void __del_default_async_client() {
-    hlogi("destory default http async client");
-    http_client_del(__get_default_async_client());
+    return s_async_http_client;
 }
 
 int http_client_send_async(HttpRequestPtr req, HttpResponseCallback resp_cb) {
@@ -644,5 +703,5 @@ int http_client_send_async(HttpRequestPtr req, HttpResponseCallback resp_cb) {
         req->timeout = DEFAULT_HTTP_TIMEOUT;
     }
 
-    return __http_client_send_async(__get_default_async_client(), req, std::move(resp_cb));
+    return http_client_exec_async(hv_default_async_http_client(), req, std::move(resp_cb));
 }

+ 25 - 2
http/client/http_client.h

@@ -30,7 +30,6 @@ int main(int argc, char* argv[]) {
 typedef struct http_client_s http_client_t;
 
 HV_EXPORT http_client_t* http_client_new(const char* host = NULL, int port = DEFAULT_HTTP_PORT, int https = 0);
-HV_EXPORT int http_client_close(http_client_t* cli);
 HV_EXPORT int http_client_del(http_client_t* cli);
 HV_EXPORT const char* http_client_strerror(int errcode);
 
@@ -69,6 +68,15 @@ HV_EXPORT int http_client_send(HttpRequest* req, HttpResponse* resp);
 // http_client_send_async(&default_async_client, ...)
 HV_EXPORT int http_client_send_async(HttpRequestPtr req, HttpResponseCallback resp_cb = NULL);
 
+// low-level api
+// @retval >=0 connfd, <0 error
+HV_EXPORT int http_client_connect(http_client_t* cli, const char* host, int port, int https, int timeout);
+HV_EXPORT int http_client_send_header(http_client_t* cli, HttpRequest* req);
+HV_EXPORT int http_client_send_data(http_client_t* cli, const char* data, int size);
+HV_EXPORT int http_client_recv_data(http_client_t* cli, char* data, int size);
+HV_EXPORT int http_client_recv_response(http_client_t* cli, HttpResponse* resp);
+HV_EXPORT int http_client_close(http_client_t* cli);
+
 namespace hv {
 
 class HttpClient {
@@ -134,7 +142,22 @@ public:
         return http_client_send_async(_client, req, std::move(resp_cb));
     }
 
-    // close
+    // low-level api
+    int connect(const char* host, int port = DEFAULT_HTTP_PORT, int https = 0, int timeout = DEFAULT_HTTP_CONNECT_TIMEOUT) {
+        return http_client_connect(_client, host, port, https, timeout);
+    }
+    int sendHeader(HttpRequest* req) {
+        return http_client_send_header(_client, req);
+    }
+    int sendData(const char* data, int size) {
+        return http_client_send_data(_client, data, size);
+    }
+    int recvData(char* data, int size) {
+        return http_client_recv_data(_client, data, size);
+    }
+    int recvResponse(HttpResponse* resp) {
+        return http_client_recv_response(_client, resp);
+    }
     int close() {
         return http_client_close(_client);
     }