userver: /data/code/userver/libraries/s3api/src/s3api/s3_connection.cpp Source File
Loading...
Searching...
No Matches
s3_connection.cpp
1#include <s3api/s3_connection.hpp>
2
3#include <userver/clients/http/client.hpp>
4#include <userver/http/common_headers.hpp>
5#include <userver/logging/log.hpp>
6#include <userver/s3api/models/request.hpp>
7
8USERVER_NAMESPACE_BEGIN
9
10namespace s3api {
11
12namespace {
13clients::http::Request& GetMethod(
14 clients::http::Request& req,
15 std::string_view url,
16 std::string_view body,
17 clients::http::HttpMethod method
18) {
19 // TODO: Get rid of extra string_view->string conversion once
20 // http::Request can work with string_view directly
21 switch (method) {
22 case clients::http::HttpMethod::kGet:
23 return req.get(std::string{url});
24 case clients::http::HttpMethod::kHead:
25 return req.head(std::string{url});
26 case clients::http::HttpMethod::kPost:
27 return req.post(std::string{url}, std::string{body});
28 case clients::http::HttpMethod::kPut:
29 return req.put(std::string{url}, std::string{body});
30 case clients::http::HttpMethod::kDelete:
31 return req.delete_method(std::string{url});
32 default:
33 throw std::runtime_error("Unknown http method");
34 }
35}
36} // namespace
37
38// api_url_ может содержать схему и путь (в тестах endpoint указывает на
39// mockserver, например "http://localhost:PORT/s3mds"). Заголовок Host должен
40// нести только authority (host[:port]), поэтому схему и путь отбрасываем.
41// Префикс "bucket." сохраняем всегда: при обращении к localhost GetUrl не
42// кладёт bucket в путь, и mock определяет bucket именно по Host. Внешнее
43// связывание — используется в unit-тесте.
44std::string S3Connection::MakeHostHeader(std::string_view api_url, std::string_view bucket) {
45 const auto schema_pos = api_url.find("://");
46 auto authority = schema_pos == std::string_view::npos ? api_url : api_url.substr(schema_pos + 3);
47
48 const auto path_pos = authority.find('/');
49 if (path_pos != std::string_view::npos) {
50 authority = authority.substr(0, path_pos);
51 }
52
53 if (!bucket.empty()) {
54 return fmt::format("{}.{}", bucket, authority);
55 }
56
57 return std::string{authority};
58}
59
60std::string S3Connection::GetHostHeader(const Request& r) const { return MakeHostHeader(api_url_, r.bucket); }
61
62std::shared_ptr<clients::http::Response> S3Connection::RequestApi(Request& r, std::string_view method_name) {
63 r.headers[USERVER_NAMESPACE::http::headers::kHost] = GetHostHeader(r);
64 LOG_DEBUG() << "S3 Host: " << r.headers[USERVER_NAMESPACE::http::headers::kHost];
65
66 const std::string full_url = GetUrl(r, connection_type_);
67 LOG_DEBUG() << "S3 file full_url: " << full_url;
68
69 auto http_req =
70 http_client_.CreateNotSignedRequest().timeout(config_.timeout).retry(config_.retries).headers(r.headers);
71
72 if (config_.proxy.has_value()) {
73 http_req.proxy(config_.proxy.value());
74 }
75 http_req
76 .SetDestinationMetricName(fmt::format("{}/{}", r.headers[USERVER_NAMESPACE::http::headers::kHost], method_name)
77 );
78 std::shared_ptr<clients::http::Response> response;
79 try {
80 response = GetMethod(http_req, full_url, r.body, r.method).perform();
81 response->raise_for_status(clients::http::Response::RaiseIncludeBody::kYes);
82 } catch (const clients::http::TimeoutException& e) {
83 LOG_WARNING() << "S3Api : Http Request Timeout: " << full_url;
84 throw;
85 } catch (const clients::http::HttpException& exc) {
86 LOG_INFO() << "S3Api : Http Request to mds failed " << response->body() << " : " << full_url;
87 throw;
88 }
89 return response;
90}
91
92std::shared_ptr<clients::http::Response> S3Connection::DoStartApiRequest(const Request& r) const {
93 auto headers = r.headers;
94 headers[USERVER_NAMESPACE::http::headers::kHost] = GetHostHeader(r);
95
96 const std::string full_url = GetUrl(r, connection_type_);
97
98 auto http_req =
99 http_client_.CreateNotSignedRequest().headers(headers).retry(config_.retries).timeout(config_.timeout);
100 return GetMethod(http_req, full_url, r.body, r.method).perform();
101}
102
103std::shared_ptr<clients::http::Response> S3Connection::StartApiRequest(const Request& request) const {
104 return DoStartApiRequest(request);
105}
106
107std::string S3Connection::GetUrl(const Request& r, S3ConnectionType connection_type) const {
108 std::string full_url = api_url_;
109 const bool is_localhost = api_url_.find("localhost:") != std::string::npos;
110 const auto schema_pos = full_url.find("://");
111 if (schema_pos == std::string::npos) {
112 if (!is_localhost && !r.bucket.empty()) {
113 full_url = fmt::format("{}.{}", r.bucket, api_url_);
114 }
115 if (connection_type == S3ConnectionType::kHttps) {
116 full_url = "https://" + full_url;
117 } else {
118 full_url = "http://" + full_url;
119 }
120 } else {
121 if (!is_localhost && !r.bucket.empty()) {
122 const auto schema = full_url.substr(0, schema_pos);
123 const auto schemaless_url = full_url.substr(schema_pos + 3);
124 full_url = fmt::format("{}://{}.{}", schema, r.bucket, schemaless_url);
125 }
126 }
127 if (!r.req.empty()) {
128 full_url += '/';
129 full_url += r.req;
130 }
131 return full_url;
132}
133
135 clients::http::Client& http_client,
136 S3ConnectionType connection_type,
137 std::string server_url,
138 const ConnectionCfg& params
139) {
140 return std::make_shared<S3Connection>(http_client, connection_type, std::move(server_url), params);
141}
142
143} // namespace s3api
144
145USERVER_NAMESPACE_END