This is an automated email from the ASF dual-hosted git repository.

wangdan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pegasus.git


The following commit(s) were added to refs/heads/master by this push:
     new 62f5a59f1 feat(http_client): provide a convenient API http_get that 
performs an http get request on the specified URL (#1866)
62f5a59f1 is described below

commit 62f5a59f1185bab8c20edfe8f1561cb54e566d86
Author: Dan Wang <[email protected]>
AuthorDate: Tue Jan 23 14:38:44 2024 +0800

    feat(http_client): provide a convenient API http_get that performs an http 
get request on the specified URL (#1866)
    
    A simple API is needed for a typical scenario that an HTTP get request
    is performed on a specified URL to fetch some response data.
---
 src/http/http_client.cpp           |  33 ++++-----
 src/http/http_client.h             |  76 +++++++++++++++++++-
 src/http/test/http_client_test.cpp | 141 +++++++++++++++++++++++++++----------
 3 files changed, 193 insertions(+), 57 deletions(-)

diff --git a/src/http/http_client.cpp b/src/http/http_client.cpp
index 1af5ebb4e..612d71a63 100644
--- a/src/http/http_client.cpp
+++ b/src/http/http_client.cpp
@@ -19,7 +19,6 @@
 
 #include <fmt/core.h>
 #include <limits>
-#include <utility>
 
 #include "curl/curl.h"
 #include "utils/error_code.h"
@@ -283,12 +282,9 @@ inline dsn::error_code to_error_code(CURLcode code)
     } while (0)
 
 #define CHECK_IF_CURL_EASY_SETOPT_OK(opt, input, ...)                          
                    \
-    do {                                                                       
                    \
-        CHECK_NOTNULL(_curl, "CURL object has not been allocated");            
                    \
-        CHECK_IF_CURL_EASY_OK(curl_easy_setopt(_curl, CURLOPT_##opt, input),   
                    \
-                              "failed to set " #opt " with " #input ": {}",    
                    \
-                              fmt::format(__VA_ARGS__));                       
                    \
-    } while (0)
+    CHECK_IF_CURL_EASY_OK(curl_easy_setopt(_curl, CURLOPT_##opt, input),       
                    \
+                          "failed to set " #opt " with " #input ": {}",        
                    \
+                          fmt::format(__VA_ARGS__))
 
 dsn::error_s http_client::init()
 {
@@ -375,12 +371,11 @@ size_t http_client::on_response_data(const void *data, 
size_t length)
     return (*_recv_callback)(data, length) ? length : 
std::numeric_limits<size_t>::max();
 }
 
-#define CHECK_IF_CURL_EASY_SETOPT_CURLU_OK()                                   
                    \
-    CHECK_IF_CURL_EASY_SETOPT_OK(                                              
                    \
-        CURLU,                                                                 
                    \
-        _url.curlu(),                                                          
                    \
-        "once CURLOPT_CURLU is supported, curl_easy_setopt should always "     
                    \
-        "return CURLE_OK, see https://curl.se/libcurl/c/CURLOPT_CURLU.html";)
+// Once CURLOPT_CURLU is supported, actually curl_easy_setopt would always 
return CURLE_OK,
+// see https://curl.se/libcurl/c/CURLOPT_CURLU.html";). For the reason why we 
still return
+// ERR_OK, please see the comments for set_url.
+#define RETURN_IF_CURL_EASY_SETOPT_CURLU_NOT_OK()                              
                    \
+    RETURN_IF_CURL_EASY_SETOPT_NOT_OK(CURLU, _url.curlu())
 
 dsn::error_s http_client::set_url(const std::string &new_url)
 {
@@ -394,20 +389,22 @@ dsn::error_s http_client::set_url(const std::string 
&new_url)
     http_url tmp;
     RETURN_NOT_OK(tmp.set_url(new_url.c_str()));
 
-    set_url(std::move(tmp));
+    RETURN_NOT_OK(set_url(std::move(tmp)));
     return dsn::error_s::ok();
 }
 
-void http_client::set_url(const http_url &new_url)
+dsn::error_s http_client::set_url(const http_url &new_url)
 {
     _url = new_url;
-    CHECK_IF_CURL_EASY_SETOPT_CURLU_OK();
+    RETURN_IF_CURL_EASY_SETOPT_CURLU_NOT_OK();
+    return dsn::error_s::ok();
 }
 
-void http_client::set_url(http_url &&new_url)
+dsn::error_s http_client::set_url(http_url &&new_url)
 {
     _url = std::move(new_url);
-    CHECK_IF_CURL_EASY_SETOPT_CURLU_OK();
+    RETURN_IF_CURL_EASY_SETOPT_CURLU_NOT_OK();
+    return dsn::error_s::ok();
 }
 
 dsn::error_s http_client::with_post_method(const std::string &data)
diff --git a/src/http/http_client.h b/src/http/http_client.h
index 6596548e8..7bf953062 100644
--- a/src/http/http_client.h
+++ b/src/http/http_client.h
@@ -25,6 +25,7 @@
 #include <memory>
 #include <string>
 #include <unordered_map>
+#include <utility>
 
 #include "absl/strings/string_view.h"
 #include "http/http_method.h"
@@ -176,8 +177,13 @@ public:
     dsn::error_s set_url(const std::string &new_url);
 
     // Specify the target url by `http_url` class.
-    void set_url(const http_url &new_url);
-    void set_url(http_url &&new_url);
+    //
+    // Currently implementations for both following set_url functions would 
never lead to errors.
+    // However, they could return ERR_OK to allow all of the overloaded 
set_url functions to be
+    // called in the same way, for example, by the function templates, where 
url is specified as
+    // a template parameter.
+    dsn::error_s set_url(const http_url &new_url);
+    dsn::error_s set_url(http_url &&new_url);
 
     // Using post method, with `data` as the payload for post body.
     dsn::error_s with_post_method(const std::string &data);
@@ -249,6 +255,72 @@ private:
     DISALLOW_COPY_AND_ASSIGN(http_client);
 };
 
+// The class that holds the result for an http request.
+class http_result
+{
+public:
+    http_result(dsn::error_s &&err) noexcept
+        : _err(std::move(err)), _status(http_status_code::kInvalidCode)
+    {
+    }
+
+    http_result(http_status_code status, std::string &&response) noexcept
+        : _err(dsn::error_s::ok()), _status(status), _body(std::move(response))
+    {
+    }
+
+    ~http_result() = default;
+
+    http_result(const http_result &) noexcept = default;
+    http_result &operator=(const http_result &) noexcept = default;
+
+    http_result(http_result &&) noexcept = default;
+    http_result &operator=(http_result &&) noexcept = default;
+
+    explicit operator bool() const noexcept { return _err.is_ok(); }
+
+    const dsn::error_s &error() const { return _err; }
+    http_status_code status() const { return _status; }
+    const std::string &body() const { return _body; }
+
+private:
+    dsn::error_s _err;
+    http_status_code _status;
+    std::string _body;
+};
+
+#define RETURN_HTTP_RESULT_IF_NOT_OK(expr)                                     
                    \
+    do {                                                                       
                    \
+        dsn::error_s r(expr);                                                  
                    \
+        if (dsn_unlikely(!r)) {                                                
                    \
+            return http_result(std::move(r));                                  
                    \
+        }                                                                      
                    \
+    } while (0)
+
+// A convenient API that performs an http get request on the specified URL.
+template <typename TUrl>
+http_result http_get(TUrl &&url)
+{
+    http_client client;
+    RETURN_HTTP_RESULT_IF_NOT_OK(client.init());
+
+    // Forward url to the corresponding overloaded function.
+    RETURN_HTTP_RESULT_IF_NOT_OK(client.set_url(std::forward<TUrl>(url)));
+
+    // Use http get as the request method.
+    RETURN_HTTP_RESULT_IF_NOT_OK(client.with_get_method());
+
+    std::string response;
+    RETURN_HTTP_RESULT_IF_NOT_OK(client.exec_method(&response));
+
+    http_status_code status;
+    RETURN_HTTP_RESULT_IF_NOT_OK(client.get_http_status(status));
+
+    return http_result(status, std::move(response));
+}
+
+#undef RETURN_HTTP_RESULT_IF_NOT_OK
+
 } // namespace dsn
 
 USER_DEFINED_STRUCTURE_FORMATTER(::dsn::http_url);
diff --git a/src/http/test/http_client_test.cpp 
b/src/http/test/http_client_test.cpp
index 7a1bea634..ece1a13b8 100644
--- a/src/http/test/http_client_test.cpp
+++ b/src/http/test/http_client_test.cpp
@@ -37,9 +37,9 @@
 #include "utils/strings.h"
 #include "utils/test_macros.h"
 
-// IWYU pragma: no_forward_declare 
dsn::HttpClientMethodTest_ExecMethodByCopyUrlClass_Test
-// IWYU pragma: no_forward_declare 
dsn::HttpClientMethodTest_ExecMethodByMoveUrlClass_Test
 // IWYU pragma: no_forward_declare 
dsn::HttpClientMethodTest_ExecMethodByUrlString_Test
+// IWYU pragma: no_forward_declare 
dsn::HttpClientMethodTest_ExecMethodByCopyUrlObject_Test
+// IWYU pragma: no_forward_declare 
dsn::HttpClientMethodTest_ExecMethodByMoveUrlObject_Test
 namespace dsn {
 
 void check_expected_description_prefix(const std::string 
&expected_description_prefix,
@@ -198,7 +198,7 @@ INSTANTIATE_TEST_SUITE_P(HttpClientTest, HttpSchemeTest, 
testing::ValuesIn(http_
 TEST(HttpUrlTest, Clear)
 {
     http_url url;
-    url.set_url(kTestUrlA.c_str());
+    ASSERT_TRUE(url.set_url(kTestUrlA.c_str()));
 
     std::string str;
     ASSERT_TRUE(url.to_string(str));
@@ -442,56 +442,41 @@ protected:
     http_client _client;
 };
 
-#define TEST_HTTP_CLIENT_EXEC_METHOD(url_setter)                               
                    \
+#define BUILD_URL_STRING(host, port, path)                                     
                    \
+    const auto &url = fmt::format("http://{}:{}{}";, host, port, path)
+
+#define BUILD_URL_OBJECT(host, port, path)                                     
                    \
+    http_url url;                                                              
                    \
+    do {                                                                       
                    \
+        ASSERT_TRUE(url.set_host(host));                                       
                    \
+        ASSERT_TRUE(url.set_port(port));                                       
                    \
+        ASSERT_TRUE(url.set_path(path));                                       
                    \
+    } while (0)
+
+#define TEST_HTTP_CLIENT_EXEC_METHOD(url_builder, ...)                         
                    \
     do {                                                                       
                    \
         const auto &method_case = GetParam();                                  
                    \
-        url_setter(method_case.host, method_case.port, method_case.path);      
                    \
+        url_builder(method_case.host, method_case.port, method_case.path);     
                    \
+        ASSERT_TRUE(_client.set_url(__VA_ARGS__(url)));                        
                    \
         test_mothod(method_case.method,                                        
                    \
                     method_case.post_data,                                     
                    \
                     method_case.expected_http_status,                          
                    \
                     method_case.expected_response);                            
                    \
     } while (0)
 
-// Test setting url by string, where set_url returns an error_s which should 
be checked.
-#define SET_HTTP_CLIENT_BY_URL_STRING(host, port, path)                        
                    \
-    do {                                                                       
                    \
-        const auto &url = fmt::format("http://{}:{}{}";, host, port, path);     
                    \
-        ASSERT_TRUE(_client.set_url(url));                                     
                    \
-    } while (0)
-
 TEST_P(HttpClientMethodTest, ExecMethodByUrlString)
 {
-    TEST_HTTP_CLIENT_EXEC_METHOD(SET_HTTP_CLIENT_BY_URL_STRING);
+    TEST_HTTP_CLIENT_EXEC_METHOD(BUILD_URL_STRING);
 }
 
-// Test setting url by copying url object, where set_url returns nothing.
-#define SET_HTTP_CLIENT_BY_COPY_URL_OBJECT(host, port, path)                   
                    \
-    do {                                                                       
                    \
-        http_url url;                                                          
                    \
-        ASSERT_TRUE(url.set_host(host));                                       
                    \
-        ASSERT_TRUE(url.set_port(port));                                       
                    \
-        ASSERT_TRUE(url.set_path(path));                                       
                    \
-        _client.set_url(url);                                                  
                    \
-    } while (0)
-
-TEST_P(HttpClientMethodTest, ExecMethodByCopyUrlClass)
+TEST_P(HttpClientMethodTest, ExecMethodByCopyUrlObject)
 {
-    TEST_HTTP_CLIENT_EXEC_METHOD(SET_HTTP_CLIENT_BY_COPY_URL_OBJECT);
+    TEST_HTTP_CLIENT_EXEC_METHOD(BUILD_URL_OBJECT);
 }
 
-// Test setting url by moving url object, where set_url returns nothing.
-#define SET_HTTP_CLIENT_BY_MOVE_URL_OBJECT(host, port, path)                   
                    \
-    do {                                                                       
                    \
-        http_url url;                                                          
                    \
-        ASSERT_TRUE(url.set_host(host));                                       
                    \
-        ASSERT_TRUE(url.set_port(port));                                       
                    \
-        ASSERT_TRUE(url.set_path(path));                                       
                    \
-        _client.set_url(std::move(url));                                       
                    \
-    } while (0)
-
-TEST_P(HttpClientMethodTest, ExecMethodByMoveUrlClass)
+TEST_P(HttpClientMethodTest, ExecMethodByMoveUrlObject)
 {
-    TEST_HTTP_CLIENT_EXEC_METHOD(SET_HTTP_CLIENT_BY_MOVE_URL_OBJECT);
+    TEST_HTTP_CLIENT_EXEC_METHOD(BUILD_URL_OBJECT, std::move);
 }
 
 const std::vector<http_client_method_case> http_client_method_tests = {
@@ -529,4 +514,86 @@ INSTANTIATE_TEST_SUITE_P(HttpClientTest,
                          HttpClientMethodTest,
                          testing::ValuesIn(http_client_method_tests));
 
+struct http_get_case
+{
+    const char *host;
+    uint16_t port;
+    const char *path;
+    error_code expected_err_code;
+    std::string expected_description_prefix;
+    http_status_code expected_status;
+    const char *expected_body;
+};
+
+class HttpGetTest : public testing::TestWithParam<http_get_case>
+{
+};
+
+template <typename TUrl>
+void test_http_get(TUrl &&url,
+                   const error_code &expected_err_code,
+                   const std::string &expected_description_prefix,
+                   const http_status_code expected_status,
+                   const char *expected_body)
+{
+    const auto &result = http_get(std::forward<TUrl>(url));
+    EXPECT_EQ(expected_err_code, result.error().code());
+    EXPECT_EQ(expected_status, result.status());
+    EXPECT_STREQ(expected_body, result.body().c_str());
+
+    if (result) {
+        EXPECT_EQ(dsn::ERR_OK, result.error().code());
+    } else {
+        EXPECT_NE(dsn::ERR_OK, result.error().code());
+        
NO_FATALS(check_expected_description_prefix(expected_description_prefix, 
result.error()));
+    }
+}
+
+#define TEST_HTTP_GET(url_builder, ...)                                        
                    \
+    do {                                                                       
                    \
+        const auto &get_case = GetParam();                                     
                    \
+        url_builder(get_case.host, get_case.port, get_case.path);              
                    \
+        test_http_get(__VA_ARGS__(url),                                        
                    \
+                      get_case.expected_err_code,                              
                    \
+                      get_case.expected_description_prefix,                    
                    \
+                      get_case.expected_status,                                
                    \
+                      get_case.expected_body);                                 
                    \
+    } while (0)
+
+TEST_P(HttpGetTest, GetByUrlString) { TEST_HTTP_GET(BUILD_URL_STRING); }
+
+TEST_P(HttpGetTest, GetByCopyUrlObject) { TEST_HTTP_GET(BUILD_URL_OBJECT); }
+
+TEST_P(HttpGetTest, GetByMoveUrlObject) { TEST_HTTP_GET(BUILD_URL_OBJECT, 
std::move); }
+
+const std::vector<http_get_case> http_get_tests = {
+    {
+        "127.0.0.1",
+        20000,
+        "/test/get",
+        dsn::ERR_CURL_FAILED,
+        "ERR_CURL_FAILED: failed to perform http request("
+        "method=GET, url=http://127.0.0.1:20000/test/get): code=7, "
+        "desc=\"Couldn't connect to server\"",
+        http_status_code::kInvalidCode,
+        "",
+    },
+    {"127.0.0.1",
+     20001,
+     "/test/get",
+     dsn::ERR_OK,
+     "",
+     http_status_code::kOk,
+     "you are using GET method"},
+    {"127.0.0.1",
+     20001,
+     "/test/post",
+     dsn::ERR_OK,
+     "",
+     http_status_code::kBadRequest,
+     "please use POST method"},
+};
+
+INSTANTIATE_TEST_SUITE_P(HttpClientTest, HttpGetTest, 
testing::ValuesIn(http_get_tests));
+
 } // namespace dsn


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to