Skip to content

Commit 5521235

Browse files
committed
add basic client(with cmake modify )
1 parent bc8c8e6 commit 5521235

File tree

3 files changed

+246
-45
lines changed

3 files changed

+246
-45
lines changed

cmake_modules/IcebergThirdpartyToolchain.cmake

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -454,45 +454,6 @@ function(resolve_curl_dependency)
454454
set(HTTP_ONLY
455455
ON
456456
CACHE BOOL "" FORCE)
457-
set(CURL_DISABLE_LDAP
458-
ON
459-
CACHE BOOL "" FORCE)
460-
set(CURL_DISABLE_LDAPS
461-
ON
462-
CACHE BOOL "" FORCE)
463-
set(CURL_DISABLE_RTSP
464-
ON
465-
CACHE BOOL "" FORCE)
466-
set(CURL_DISABLE_FTP
467-
ON
468-
CACHE BOOL "" FORCE)
469-
set(CURL_DISABLE_FILE
470-
ON
471-
CACHE BOOL "" FORCE)
472-
set(CURL_DISABLE_TELNET
473-
ON
474-
CACHE BOOL "" FORCE)
475-
set(CURL_DISABLE_DICT
476-
ON
477-
CACHE BOOL "" FORCE)
478-
set(CURL_DISABLE_TFTP
479-
ON
480-
CACHE BOOL "" FORCE)
481-
set(CURL_DISABLE_GOPHER
482-
ON
483-
CACHE BOOL "" FORCE)
484-
set(CURL_DISABLE_POP3
485-
ON
486-
CACHE BOOL "" FORCE)
487-
set(CURL_DISABLE_IMAP
488-
ON
489-
CACHE BOOL "" FORCE)
490-
set(CURL_DISABLE_SMTP
491-
ON
492-
CACHE BOOL "" FORCE)
493-
set(CURL_DISABLE_SMB
494-
ON
495-
CACHE BOOL "" FORCE)
496457
set(CURL_CA_BUNDLE
497458
"auto"
498459
CACHE STRING "" FORCE)
@@ -502,12 +463,11 @@ function(resolve_curl_dependency)
502463

503464
fetchcontent_declare(CURL
504465
${FC_DECLARE_COMMON_OPTIONS}
505-
GIT_REPOSITORY https://github.com/curl/curl.git
506-
GIT_TAG curl-8_11_0
507-
FIND_PACKAGE_ARGS
508-
NAMES
509-
CURL
510-
CONFIG)
466+
URL https://curl.se/download/curl-8.11.0.tar.gz
467+
FIND_PACKAGE_ARGS
468+
NAMES
469+
CURL
470+
CONFIG)
511471

512472
fetchcontent_makeavailable(CURL)
513473

src/iceberg/catalog/rest/client.cc

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "client.h"
21+
22+
#include <cpr/cpr.h>
23+
24+
namespace iceberg::catalog::rest {
25+
26+
// PIMPL (Pointer to Implementation) 模式的实现
27+
struct HttpClient::Impl {
28+
cpr::Url base_uri_;
29+
cpr::Header common_headers_;
30+
31+
Impl(const std::string& base_uri, const HttpHeaders& common_headers)
32+
: base_uri_{base_uri},
33+
common_headers_{common_headers.begin(), common_headers.end()} {}
34+
35+
// 这个模板函数是 Rust 版本 `query` 和 `execute` 方法的核心逻辑的 C++ 对等实现。
36+
// - `SuccessType` 对应 Rust 的 `R` (如果是 void,则对应 `execute` 的 `()`)。
37+
// - `SuccessCode` 对应 Rust 的 `SUCCESS_CODE`。
38+
template <typename SuccessType, int SuccessCode>
39+
SuccessType process_response(const cpr::Response& resp) {
40+
if (resp.status_code == SuccessCode) {
41+
// 对应 Rust 的 `if resp.status().as_u16() == SUCCESS_CODE` 分支
42+
43+
// `if constexpr` 允许我们在编译时根据 SuccessType 是否为 void
44+
// 来选择不同的代码路径。 这完美地模拟了 `query` (返回 body) 和 `execute` (返回
45+
// void) 的区别。
46+
if constexpr (std::is_same_v<SuccessType, void>) {
47+
return; // 成功且不需要返回 body
48+
} else {
49+
// 尝试解析成功的响应体
50+
nlohmann::json result = nlohmann::json::parse(resp.text, false);
51+
if (result.is_discarded()) {
52+
// 对应 Rust 中解析成功响应体失败的错误路径
53+
throw ResponseParseException("Failed to parse successful response from server!",
54+
resp.text);
55+
}
56+
return result;
57+
}
58+
} else {
59+
// 对应 Rust 的 `else` 分支,处理错误响应
60+
nlohmann::json error_payload = nlohmann::json::parse(resp.text, false);
61+
if (error_payload.is_discarded()) {
62+
// 对应 Rust 中解析错误响应体失败的错误路径
63+
throw ResponseParseException("Failed to parse error response from server!",
64+
resp.text);
65+
}
66+
// 抛出包含详细信息的异常,对应 `Err(e.into())`
67+
throw ServerErrorException(resp.status_code, resp.text, error_payload);
68+
}
69+
}
70+
};
71+
72+
HttpClient::HttpClient(const std::string& base_uri, const HttpHeaders& common_headers)
73+
: pimpl_(std::make_unique<Impl>(base_uri, common_headers)) {}
74+
75+
HttpClient::~HttpClient() = default;
76+
77+
nlohmann::json HttpClient::Get(const std::string& path, const HttpHeaders& headers) {
78+
cpr::Header final_headers = pimpl_->common_headers_;
79+
final_headers.insert(headers.begin(), headers.end());
80+
81+
cpr::Response r = cpr::Get(pimpl_->base_uri_ + cpr::Url{path}, final_headers);
82+
83+
// 调用通用处理逻辑,期望返回 json,成功状态码为 200 (OK)
84+
return pimpl_->process_response<nlohmann::json, 200>(r);
85+
}
86+
87+
nlohmann::json HttpClient::Post(const std::string& path, const HttpHeaders& headers,
88+
const nlohmann::json& body) {
89+
cpr::Header final_headers = pimpl_->common_headers_;
90+
final_headers.insert(headers.begin(), headers.end());
91+
// 确保 Content-Type 设置为 application/json
92+
if (final_headers.find("Content-Type") == final_headers.end()) {
93+
final_headers["Content-Type"] = "application/json";
94+
}
95+
96+
cpr::Response r = cpr::Post(pimpl_->base_uri_ + cpr::Url{path}, cpr::Body{body.dump()},
97+
final_headers);
98+
99+
// 调用通用处理逻辑,期望返回 json,成功状态码通常为 201 (Created) 或 200 (OK),这里以
100+
// 200 为例
101+
return pimpl_->process_response<nlohmann::json, 200>(r);
102+
}
103+
104+
void HttpClient::Delete(const std::string& path, const HttpHeaders& headers) {
105+
cpr::Header final_headers = pimpl_->common_headers_;
106+
final_headers.insert(headers.begin(), headers.end());
107+
108+
cpr::Response r = cpr::Delete(pimpl_->base_uri_ + cpr::Url{path}, final_headers);
109+
110+
// 调用通用处理逻辑,期望返回 void,成功状态码为 204 (No Content)
111+
return pimpl_->process_response<void, 204>(r);
112+
}
113+
114+
} // namespace iceberg::catalog::rest

src/iceberg/catalog/rest/client.h

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#pragma once
21+
22+
#include <map>
23+
#include <memory>
24+
#include <stdexcept>
25+
#include <string>
26+
27+
#include <nlohmann/json.hpp>
28+
29+
#include "iceberg/iceberg_export.h"
30+
31+
namespace iceberg::catalog::rest {
32+
33+
using HttpHeaders = std::map<std::string, std::string>;
34+
35+
/**
36+
* @brief Base exception for REST client errors.
37+
*/
38+
class ICEBERG_EXPORT RestException : public std::runtime_error {
39+
public:
40+
explicit RestException(const std::string& message) : std::runtime_error(message) {}
41+
};
42+
43+
/**
44+
* @brief Exception thrown when the server returns an error response.
45+
*
46+
* This corresponds to the generic `E` type in the Rust implementation,
47+
* which is deserialized from the error response body.
48+
*/
49+
class ICEBERG_EXPORT ServerErrorException : public RestException {
50+
public:
51+
ServerErrorException(int status_code, std::string response_body,
52+
nlohmann::json error_payload)
53+
: RestException("Server returned an error"),
54+
status_code_(status_code),
55+
response_body_(std::move(response_body)),
56+
error_payload_(std::move(error_payload)) {}
57+
58+
int status_code() const { return status_code_; }
59+
const std::string& response_body() const { return response_body_; }
60+
const nlohmann::json& error_payload() const { return error_payload_; }
61+
62+
private:
63+
int status_code_;
64+
std::string response_body_;
65+
nlohmann::json error_payload_;
66+
};
67+
68+
/**
69+
* @brief Exception thrown when parsing the response body (either success or error) fails.
70+
*
71+
* This corresponds to the `Error::new(ErrorKind::Unexpected, ...)` part in Rust.
72+
*/
73+
class ICEBERG_EXPORT ResponseParseException : public RestException {
74+
public:
75+
ResponseParseException(std::string message, std::string response_body)
76+
: RestException(std::move(message)), response_body_(std::move(response_body)) {}
77+
78+
const std::string& response_body() const { return response_body_; }
79+
80+
private:
81+
std::string response_body_;
82+
};
83+
84+
class ICEBERG_EXPORT HttpClient {
85+
public:
86+
HttpClient(const std::string& base_uri, const HttpHeaders& common_headers);
87+
~HttpClient();
88+
89+
/**
90+
* @brief Performs a GET request, expecting a JSON response on success.
91+
* @param path The request path, relative to the base URI.
92+
* @param headers Additional request-specific headers.
93+
* @return The parsed JSON body of the response.
94+
* @throws ServerErrorException if the server returns a non-200 status code.
95+
* @throws ResponseParseException if the response body cannot be parsed as JSON.
96+
*/
97+
nlohmann::json Get(const std::string& path, const HttpHeaders& headers);
98+
99+
/**
100+
* @brief Performs a POST request, expecting a JSON response on success.
101+
* @param path The request path.
102+
* @param headers Additional headers.
103+
* @param body The JSON body to send with the request.
104+
* @return The parsed JSON body of the response.
105+
* @throws ServerErrorException if the server returns a non-201 status code.
106+
* @throws ResponseParseException if the response body cannot be parsed as JSON.
107+
*/
108+
nlohmann::json Post(const std::string& path, const HttpHeaders& headers,
109+
const nlohmann::json& body);
110+
111+
/**
112+
* @brief Performs a DELETE request, expecting no response body on success.
113+
* @param path The request path.
114+
* @param headers Additional headers.
115+
* @throws ServerErrorException if the server returns a non-204 status code.
116+
* @throws ResponseParseException if the error response body cannot be parsed as JSON.
117+
*/
118+
void Delete(const std::string& path, const HttpHeaders& headers);
119+
120+
// ... other HTTP methods can be added here following the same pattern.
121+
122+
private:
123+
struct Impl;
124+
std::unique_ptr<Impl> pimpl_;
125+
};
126+
127+
} // namespace iceberg::catalog::rest

0 commit comments

Comments
 (0)