Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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"
#include "absl/strings/ascii.h"
#include <cstdint>
#if UINTPTR_MAX == UINT64_MAX
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Better include <cstdint> before you use it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oops; fixed.

#include "absl/container/flat_hash_map.h"
#else
#include <unordered_map>
#endif // UINTPTR_MAX == UINT64_MAX
#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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does NOLINT(google-explicit-constructor) do anything on a non-constructor?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes. There is not a different NOLINT for implicit conversion operators.

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
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

#incude <cstdint>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

// 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changing nesting levels within pre-processor conditionals is generally considered bad practice.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed.

} // namespace cloud
} // namespace google

#if UINTPTR_MAX != UINT64_MAX
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Forgive my memory failings, but how does absl::flat_hash_map know how to hash a HttpHeaderName?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The HttpHeaderName type has an implicit std::string conversion operator.

// 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