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]