Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ TEST(ThingAdminRestMetadataDecoratorTest, DropDatabaseExplicitRoutingMatch) {
EXPECT_THAT(context.GetHeader("x-goog-quota-user"), IsEmpty());
EXPECT_THAT(context.GetHeader("x-server-timeout"), IsEmpty());
EXPECT_THAT(
context.GetHeader("x-goog-request-params")[0],
context.GetHeader("x-goog-request-params").values().front(),
AllOf(
HasSubstr(std::string("project=projects%2Fmy_project")),
HasSubstr(std::string("instance=instances%2Fmy_instance")),
Expand Down
1 change: 1 addition & 0 deletions google/cloud/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ cc_library(
visibility = ["//:__subpackages__"],
deps = [
":google_cloud_cpp_common",
"@abseil-cpp//absl/container:flat_hash_map",
"@abseil-cpp//absl/functional:function_ref",
"@abseil-cpp//absl/types:span",
"@curl",
Expand Down
9 changes: 5 additions & 4 deletions google/cloud/bigquery/v2/minimal/internal/log_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ Result LogWrapper(Functor&& functor, rest_internal::RestContext& context,
TracingOptions const& options) {
auto formatter = [options](std::string* out, auto const& header) {
auto const* delim = options.single_line_mode() ? "&" : "\n";
absl::StrAppend(
out, " { name: \"", header.first, "\" value: \"",
internal::DebugString(absl::StrJoin(header.second, delim), options),
"\" }");
absl::StrAppend(out, " { name: \"", std::string_view{header.first},
"\" value: \"",
internal::DebugString(
absl::StrJoin(header.second.values(), delim), options),
"\" }");
};
GCP_LOG(DEBUG) << where << "() << "
<< request.DebugString(request_name, options) << ", Context {"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ StatusOr<rest_internal::RestRequest> PrepareRestRequest(
if (!rest_context.headers().empty()) {
for (auto const& h : rest_context.headers()) {
if (!h.second.empty()) {
rest_request->AddHeader(h.first, absl::StrJoin(h.second, "&"));
rest_request->AddHeader(h.first, absl::StrJoin(h.second.values(), "&"));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ static auto const kUserProject = "test-only-project";
static auto const kQuotaUser = "test-quota-user";

void VerifyMetadataContext(rest_internal::RestContext& context) {
EXPECT_THAT(context.GetHeader("x-goog-api-client"),
EXPECT_THAT(context.GetHeader("x-goog-api-client").values(),
ElementsAre(internal::HandCraftedLibClientHeader()));
EXPECT_THAT(context.GetHeader("x-goog-request-params"), IsEmpty());
EXPECT_THAT(context.GetHeader("x-goog-user-project"),
EXPECT_THAT(context.GetHeader("x-goog-request-params").values(), IsEmpty());
EXPECT_THAT(context.GetHeader("x-goog-user-project").values(),
ElementsAre(kUserProject));
EXPECT_THAT(context.GetHeader("x-goog-quota-user"), ElementsAre(kQuotaUser));
EXPECT_THAT(context.GetHeader("x-server-timeout"), ElementsAre("3.141"));
EXPECT_THAT(context.GetHeader("x-goog-quota-user").values(),
ElementsAre(kQuotaUser));
EXPECT_THAT(context.GetHeader("x-server-timeout").values(),
ElementsAre("3.141"));
}

Options GetMetadataOptions() {
Expand Down
3 changes: 2 additions & 1 deletion google/cloud/google_cloud_cpp_rest_internal.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ add_library(
target_link_libraries(
google_cloud_cpp_rest_internal
PUBLIC absl::span google-cloud-cpp::common CURL::libcurl
nlohmann_json::nlohmann_json)
absl::flat_hash_map nlohmann_json::nlohmann_json)
if (WIN32)
target_compile_definitions(google_cloud_cpp_rest_internal
PRIVATE WIN32_LEAN_AND_MEAN)
Expand Down Expand Up @@ -197,6 +197,7 @@ google_cloud_cpp_add_pkgconfig(
"Provides REST Transport for the Google Cloud C++ Client Library."
"google_cloud_cpp_common"
"libcurl"
"absl_flat_hash_map"
NON_WIN32_REQUIRES
openssl
WIN32_LIBS
Expand Down
5 changes: 2 additions & 3 deletions google/cloud/internal/curl_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,9 @@ void CurlImpl::MergeAndWriteHeaders(
}
}

void CurlImpl::SetHeaders(
std::unordered_map<std::string, std::vector<std::string>> const& headers) {
void CurlImpl::SetHeaders(HttpHeaders const& headers) {
for (auto const& header : headers) {
SetHeader(HttpHeader(header.first, header.second));
SetHeader(header.second);
}
}

Expand Down
4 changes: 1 addition & 3 deletions google/cloud/internal/curl_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>

namespace google {
Expand Down Expand Up @@ -84,8 +83,7 @@ class CurlImpl {
CurlImpl& operator=(CurlImpl&&) = default;

void SetHeader(HttpHeader header);
void SetHeaders(
std::unordered_map<std::string, std::vector<std::string>> const& headers);
void SetHeaders(HttpHeaders const& headers);

std::string MakeEscapedString(std::string const& s);

Expand Down
10 changes: 6 additions & 4 deletions google/cloud/internal/curl_rest_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,12 @@ StatusOr<std::unique_ptr<CurlImpl>> CurlRestClient::CreateCurlImpl(
auto impl =
std::make_unique<CurlImpl>(std::move(handle), handle_factory_, options);
if (credentials_) {
auto auth_header =
credentials_->AuthenticationHeader(std::chrono::system_clock::now());
if (!auth_header.ok()) return std::move(auth_header).status();
impl->SetHeader(HttpHeader(auth_header->first, auth_header->second));
auto auth_headers =
credentials_->AuthenticationHeaders(std::chrono::system_clock::now());
if (!auth_headers.ok()) return std::move(auth_headers).status();
for (auto& header : *auth_headers) {
impl->SetHeader(std::move(header));
}
}
impl->SetHeader(HostHeader(options, endpoint_address_));
impl->SetHeaders(context.headers());
Expand Down
18 changes: 9 additions & 9 deletions google/cloud/internal/curl_rest_client_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,21 @@ TEST(CurlRestClientStandaloneFunctions, HostHeader) {
std::string expected;
} cases[] = {
{"https://storage.googleapis.com", "storage.googleapis.com",
"Host: storage.googleapis.com"},
{"https://storage.googleapis.com", "", "Host: storage.googleapis.com"},
{"https://storage.googleapis.com", "auth", "Host: auth"},
"host: storage.googleapis.com"},
{"https://storage.googleapis.com", "", "host: storage.googleapis.com"},
{"https://storage.googleapis.com", "auth", "host: auth"},
{"https://storage.googleapis.com:443", "storage.googleapis.com",
"Host: storage.googleapis.com"},
"host: storage.googleapis.com"},
{"https://restricted.googleapis.com", "storage.googleapis.com",
"Host: storage.googleapis.com"},
"host: storage.googleapis.com"},
{"https://private.googleapis.com", "storage.googleapis.com",
"Host: storage.googleapis.com"},
"host: storage.googleapis.com"},
{"https://restricted.googleapis.com", "iamcredentials.googleapis.com",
"Host: iamcredentials.googleapis.com"},
"host: iamcredentials.googleapis.com"},
{"https://private.googleapis.com", "iamcredentials.googleapis.com",
"Host: iamcredentials.googleapis.com"},
"host: iamcredentials.googleapis.com"},
{"http://localhost:8080", "", ""},
{"http://localhost:8080", "auth", "Host: auth"},
{"http://localhost:8080", "auth", "host: auth"},
{"http://[::1]", "", ""},
{"http://[::1]/", "", ""},
{"http://[::1]/foo/bar", "", ""},
Expand Down
40 changes: 21 additions & 19 deletions google/cloud/internal/http_header.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,50 @@ namespace cloud {
namespace rest_internal {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

HttpHeader::HttpHeader(std::string key) : key_(std::move(key)) {}
HttpHeader::HttpHeader(HttpHeaderName key) : name_(std::move(key)) {}

HttpHeader::HttpHeader(std::string key, std::string value)
: key_(std::move(key)), values_({std::move(value)}) {}
HttpHeader::HttpHeader(std::pair<std::string, std::string> header)
: HttpHeader(std::move(header.first), std::move(header.second)) {}

HttpHeader::HttpHeader(std::string key, std::vector<std::string> values)
: key_(std::move(key)), values_(std::move(values)) {}
HttpHeader::HttpHeader(HttpHeaderName key, std::string value)
: name_(std::move(key)), values_({std::move(value)}) {}

HttpHeader::HttpHeader(std::string key,
HttpHeader::HttpHeader(HttpHeaderName key, std::vector<std::string> values)
: name_(std::move(key)), values_(std::move(values)) {}

HttpHeader::HttpHeader(HttpHeaderName key,
std::initializer_list<char const*> values)
: key_(std::move(key)) {
: name_(std::move(key)) {
for (auto&& v : values) values_.emplace_back(v);
}

bool operator==(HttpHeader const& lhs, HttpHeader const& rhs) {
return absl::AsciiStrToLower(lhs.key_) == absl::AsciiStrToLower(rhs.key_) &&
lhs.values_ == rhs.values_;
return lhs.name_ == rhs.name_ && lhs.values_ == rhs.values_;
}

bool operator<(HttpHeader const& lhs, HttpHeader const& rhs) {
return absl::AsciiStrToLower(lhs.key_) < absl::AsciiStrToLower(rhs.key_);
return lhs.name_ < rhs.name_;
}

bool HttpHeader::IsSameKey(std::string const& key) const {
return absl::AsciiStrToLower(key_) == absl::AsciiStrToLower(key);
bool HttpHeader::IsSameKey(HttpHeaderName const& name) const {
return name_ == name.name();
}

bool HttpHeader::IsSameKey(HttpHeader const& other) const {
return IsSameKey(other.key_);
return name_ == other.name_;
}

HttpHeader::operator std::string() const {
if (key_.empty()) return {};
if (values_.empty()) return absl::StrCat(key_, ":");
return absl::StrCat(key_, ": ", absl::StrJoin(values_, ","));
if (name_.empty()) return {};
if (values_.empty()) return absl::StrCat(name_.name(), ":");
return absl::StrCat(name_.name(), ": ", absl::StrJoin(values_, ","));
}

std::string HttpHeader::DebugString() const {
if (key_.empty()) return {};
if (values_.empty()) return absl::StrCat(key_, ":");
if (name_.empty()) return {};
if (values_.empty()) return absl::StrCat(name_.name(), ":");
return absl::StrCat(
key_, ": ",
name_.name(), ": ",
absl::StrJoin(values_, ",", [](std::string* out, std::string const& v) {
absl::StrAppend(out, v.substr(0, 10));
}));
Expand Down
109 changes: 101 additions & 8 deletions google/cloud/internal/http_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_HTTP_HEADER_H

#include "google/cloud/version.h"
#if UINTPTR_MAX == UINT64_MAX
#include "absl/container/flat_hash_map.h"
#else
#include <unordered_map>
#endif
#include "absl/strings/ascii.h"
#include <cstdint>
#include <string>
#include <vector>

Expand All @@ -24,17 +31,67 @@ namespace cloud {
namespace rest_internal {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

// This class represents a case-insensitive HTTP header name by storing all
// strings in lower-case.
class HttpHeaderName {
public:
HttpHeaderName() = default;
HttpHeaderName(std::string name) // NOLINT(google-explicit-constructor)
: name_(std::move(name)) {
absl::AsciiStrToLower(&name_);
}
HttpHeaderName(std::string_view name) // NOLINT(google-explicit-constructor)
: HttpHeaderName(std::string{name}) {}
HttpHeaderName(char const* name) // NOLINT(google-explicit-constructor)
: HttpHeaderName(std::string{name}) {}

operator std::string() const { // NOLINT(google-explicit-constructor)
return name_;
}
operator std::string_view() const { // NOLINT(google-explicit-constructor)
return name_;
}
operator char const*() const { // NOLINT(google-explicit-constructor)
return name_.c_str();
}

bool empty() const { return name_.empty(); }
std::string const& name() const { return name_; }

friend bool operator==(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return lhs.name_ == rhs.name_;
}
friend bool operator<(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return lhs.name_ < rhs.name_;
}
friend bool operator!=(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return !(lhs == rhs);
}
friend bool operator>(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return !(lhs < rhs) && (lhs != rhs);
}
friend bool operator>=(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return !(lhs < rhs);
}
friend bool operator<=(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return !(lhs > rhs);
}

private:
std::string name_;
};

/**
* This class represents an HTTP header field.
*/
class HttpHeader {
public:
HttpHeader() = default;
explicit HttpHeader(std::string key);
HttpHeader(std::string key, std::string value);
HttpHeader(std::string key, std::initializer_list<char const*> values);

HttpHeader(std::string key, std::vector<std::string> values);
explicit HttpHeader(HttpHeaderName key);
explicit HttpHeader(std::pair<std::string, std::string> header);
HttpHeader(HttpHeaderName key, std::string value);
HttpHeader(HttpHeaderName key, std::initializer_list<char const*> values);
HttpHeader(HttpHeaderName key, std::vector<std::string> values);

HttpHeader(HttpHeader&&) = default;
HttpHeader& operator=(HttpHeader&&) = default;
Expand All @@ -57,14 +114,20 @@ class HttpHeader {
friend bool operator<(HttpHeader const& lhs, HttpHeader const& rhs);

// If the key is empty, the entire HttpHeader is considered empty.
bool empty() const { return key_.empty(); }
bool empty() const { return name_.empty(); }

// Number of values.
std::size_t size() const { return values_.size(); }

// Checks to see if the values are empty. Does not inspect the key field.
bool EmptyValues() const { return values_.empty(); }

// Performs a case-insensitive comparison of the key.
bool IsSameKey(HttpHeader const& other) const;
bool IsSameKey(std::string const& key) const;
bool IsSameKey(HttpHeaderName const& name) const;

std::string name() const { return name_; }
std::vector<std::string> const& values() const { return values_; }

// While the RFCs indicate that header keys are case-insensitive, no attempt
// to convert them to all lowercase is made. Header keys are printed in the
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This comment is now outdated. With the introduction of HttpHeaderName, header keys are converted to lowercase, and the original case is lost. When printed, they will be in lowercase. This comment should be removed or updated to avoid confusion.

Expand All @@ -83,14 +146,44 @@ class HttpHeader {
HttpHeader& MergeHeader(HttpHeader const& other);
HttpHeader& MergeHeader(HttpHeader&& other);

using value_type = std::string;
using const_iterator = std::vector<value_type>::const_iterator;
const_iterator begin() const { return values_.begin(); }
const_iterator end() const { return values_.end(); }
const_iterator cbegin() const { return begin(); }
const_iterator cend() const { return end(); }

private:
std::string key_;
HttpHeaderName name_;
std::vector<std::string> values_;
};

// Abseil does not guarantee compatibility with 32-bit platforms that they do
// not test with. Support for such platforms is a community effort. Using
// std::unordered_map on 32-bit platforms reduces the likelihood of issues
// arising due to this arrangement.
#if UINTPTR_MAX == UINT64_MAX
// 64-bit architecture
using HttpHeaders = absl::flat_hash_map<HttpHeaderName, HttpHeader>;
#else
// 32-bit architecture
using HttpHeaders = std::unordered_map<HttpHeaderName, HttpHeader>;
#endif // UINTPTR_MAX == UINT64_MAX

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace rest_internal
} // namespace cloud
} // namespace google

#if UINTPTR_MAX != UINT64_MAX
// This specialization has to be in the global namespace.
template <>
struct std::hash<google::cloud::rest_internal::HttpHeaderName> {
std::size_t operator()(
google::cloud::rest_internal::HttpHeaderName const& k) const noexcept {
return std::hash<std::string>()(k.name());
}
};
#endif // UINTPTR_MAX != UINT64_MAX

#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_HTTP_HEADER_H
Loading
Loading