Skip to content

Commit 3e41068

Browse files
committed
impl(rest): improve http header representation
1 parent 5b7b3c8 commit 3e41068

File tree

5 files changed

+140
-28
lines changed

5 files changed

+140
-28
lines changed

google/cloud/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ cc_library(
256256
visibility = ["//:__subpackages__"],
257257
deps = [
258258
":google_cloud_cpp_common",
259+
"@abseil-cpp//absl/container:flat_hash_map",
259260
"@abseil-cpp//absl/functional:function_ref",
260261
"@abseil-cpp//absl/types:span",
261262
"@curl",

google/cloud/google_cloud_cpp_rest_internal.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ add_library(
137137
target_link_libraries(
138138
google_cloud_cpp_rest_internal
139139
PUBLIC absl::span google-cloud-cpp::common CURL::libcurl
140-
nlohmann_json::nlohmann_json)
140+
absl::flat_hash_map nlohmann_json::nlohmann_json)
141141
if (WIN32)
142142
target_compile_definitions(google_cloud_cpp_rest_internal
143143
PRIVATE WIN32_LEAN_AND_MEAN)

google/cloud/internal/http_header.cc

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,48 +22,50 @@ namespace cloud {
2222
namespace rest_internal {
2323
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2424

25-
HttpHeader::HttpHeader(std::string key) : key_(std::move(key)) {}
25+
HttpHeader::HttpHeader(HttpHeaderName key) : name_(std::move(key)) {}
2626

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

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

33-
HttpHeader::HttpHeader(std::string key,
33+
HttpHeader::HttpHeader(HttpHeaderName key, std::vector<std::string> values)
34+
: name_(std::move(key)), values_(std::move(values)) {}
35+
36+
HttpHeader::HttpHeader(HttpHeaderName key,
3437
std::initializer_list<char const*> values)
35-
: key_(std::move(key)) {
38+
: name_(std::move(key)) {
3639
for (auto&& v : values) values_.emplace_back(v);
3740
}
3841

3942
bool operator==(HttpHeader const& lhs, HttpHeader const& rhs) {
40-
return absl::AsciiStrToLower(lhs.key_) == absl::AsciiStrToLower(rhs.key_) &&
41-
lhs.values_ == rhs.values_;
43+
return lhs.name_ == rhs.name_ && lhs.values_ == rhs.values_;
4244
}
4345

4446
bool operator<(HttpHeader const& lhs, HttpHeader const& rhs) {
45-
return absl::AsciiStrToLower(lhs.key_) < absl::AsciiStrToLower(rhs.key_);
47+
return lhs.name_ < rhs.name_;
4648
}
4749

48-
bool HttpHeader::IsSameKey(std::string const& key) const {
49-
return absl::AsciiStrToLower(key_) == absl::AsciiStrToLower(key);
50+
bool HttpHeader::IsSameKey(std::string_view key) const {
51+
return name_.name() == absl::AsciiStrToLower(key);
5052
}
5153

5254
bool HttpHeader::IsSameKey(HttpHeader const& other) const {
53-
return IsSameKey(other.key_);
55+
return name_ == other.name_;
5456
}
5557

5658
HttpHeader::operator std::string() const {
57-
if (key_.empty()) return {};
58-
if (values_.empty()) return absl::StrCat(key_, ":");
59-
return absl::StrCat(key_, ": ", absl::StrJoin(values_, ","));
59+
if (name_.empty()) return {};
60+
if (values_.empty()) return absl::StrCat(name_.name(), ":");
61+
return absl::StrCat(name_.name(), ": ", absl::StrJoin(values_, ","));
6062
}
6163

6264
std::string HttpHeader::DebugString() const {
63-
if (key_.empty()) return {};
64-
if (values_.empty()) return absl::StrCat(key_, ":");
65+
if (name_.empty()) return {};
66+
if (values_.empty()) return absl::StrCat(name_.name(), ":");
6567
return absl::StrCat(
66-
key_, ": ",
68+
name_.name(), ": ",
6769
absl::StrJoin(values_, ",", [](std::string* out, std::string const& v) {
6870
absl::StrAppend(out, v.substr(0, 10));
6971
}));

google/cloud/internal/http_header.h

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_HTTP_HEADER_H
1717

1818
#include "google/cloud/version.h"
19+
#include "absl/container/flat_hash_map.h"
20+
#include "absl/strings/ascii.h"
1921
#include <string>
2022
#include <vector>
2123

@@ -24,17 +26,67 @@ namespace cloud {
2426
namespace rest_internal {
2527
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2628

29+
// This class represents a case-insensitive HTTP header name by storing all
30+
// strings in lower-case.
31+
class HttpHeaderName {
32+
public:
33+
HttpHeaderName() = default;
34+
HttpHeaderName(std::string name) // NOLINT(google-explicit-constructor)
35+
: name_(std::move(name)) {
36+
absl::AsciiStrToLower(&name_);
37+
}
38+
HttpHeaderName(std::string_view name) // NOLINT(google-explicit-constructor)
39+
: HttpHeaderName(std::string{name}) {}
40+
HttpHeaderName(char const* name) // NOLINT(google-explicit-constructor)
41+
: HttpHeaderName(std::string{name}) {}
42+
43+
operator std::string() const { // NOLINT(google-explicit-constructor)
44+
return name_;
45+
}
46+
operator std::string_view() const { // NOLINT(google-explicit-constructor)
47+
return name_;
48+
}
49+
operator char const*() const { // NOLINT(google-explicit-constructor)
50+
return name_.c_str();
51+
}
52+
53+
bool empty() const { return name_.empty(); }
54+
std::string const& name() const { return name_; }
55+
56+
friend bool operator==(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
57+
return lhs.name_ == rhs.name_;
58+
}
59+
friend bool operator<(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
60+
return lhs.name_ < rhs.name_;
61+
}
62+
friend bool operator!=(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
63+
return !(lhs == rhs);
64+
}
65+
friend bool operator>(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
66+
return !(lhs < rhs) && (lhs != rhs);
67+
}
68+
friend bool operator>=(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
69+
return !(lhs < rhs);
70+
}
71+
friend bool operator<=(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
72+
return !(lhs > rhs);
73+
}
74+
75+
private:
76+
std::string name_;
77+
};
78+
2779
/**
2880
* This class represents an HTTP header field.
2981
*/
3082
class HttpHeader {
3183
public:
3284
HttpHeader() = default;
33-
explicit HttpHeader(std::string key);
34-
HttpHeader(std::string key, std::string value);
35-
HttpHeader(std::string key, std::initializer_list<char const*> values);
36-
37-
HttpHeader(std::string key, std::vector<std::string> values);
85+
explicit HttpHeader(HttpHeaderName key);
86+
explicit HttpHeader(std::pair<std::string, std::string> header);
87+
HttpHeader(HttpHeaderName key, std::string value);
88+
HttpHeader(HttpHeaderName key, std::initializer_list<char const*> values);
89+
HttpHeader(HttpHeaderName key, std::vector<std::string> values);
3890

3991
HttpHeader(HttpHeader&&) = default;
4092
HttpHeader& operator=(HttpHeader&&) = default;
@@ -57,14 +109,20 @@ class HttpHeader {
57109
friend bool operator<(HttpHeader const& lhs, HttpHeader const& rhs);
58110

59111
// If the key is empty, the entire HttpHeader is considered empty.
60-
bool empty() const { return key_.empty(); }
112+
bool empty() const { return name_.empty(); }
113+
114+
// Number of values.
115+
std::size_t size() const { return values_.size(); }
61116

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

65120
// Performs a case-insensitive comparison of the key.
66121
bool IsSameKey(HttpHeader const& other) const;
67-
bool IsSameKey(std::string const& key) const;
122+
bool IsSameKey(std::string_view key) const;
123+
124+
std::string name() const { return name_; }
125+
std::vector<std::string> const& values() const { return values_; }
68126

69127
// While the RFCs indicate that header keys are case-insensitive, no attempt
70128
// to convert them to all lowercase is made. Header keys are printed in the
@@ -83,11 +141,20 @@ class HttpHeader {
83141
HttpHeader& MergeHeader(HttpHeader const& other);
84142
HttpHeader& MergeHeader(HttpHeader&& other);
85143

144+
using value_type = std::string;
145+
using const_iterator = std::vector<value_type>::const_iterator;
146+
const_iterator begin() const { return values_.begin(); }
147+
const_iterator end() const { return values_.end(); }
148+
const_iterator cbegin() const { return begin(); }
149+
const_iterator cend() const { return end(); }
150+
86151
private:
87-
std::string key_;
152+
HttpHeaderName name_;
88153
std::vector<std::string> values_;
89154
};
90155

156+
using HttpHeaders = absl::flat_hash_map<HttpHeaderName, HttpHeader>;
157+
91158
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
92159
} // namespace rest_internal
93160
} // namespace cloud

google/cloud/internal/http_header_test.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,48 @@ namespace {
2323

2424
using ::testing::Eq;
2525

26+
TEST(HttpHeaderName, ConstructorsAndConversions) {
27+
auto constexpr kHeaderName = "header-name";
28+
std::string header(kHeaderName);
29+
std::string_view header_view(header);
30+
31+
HttpHeaderName h1(header);
32+
HttpHeaderName h2(header_view);
33+
HttpHeaderName h3(kHeaderName);
34+
35+
EXPECT_THAT(std::string(h2), Eq(header));
36+
EXPECT_THAT(std::string_view(h3), Eq(header));
37+
EXPECT_THAT(static_cast<char const*>(h1), Eq(header));
38+
}
39+
40+
TEST(HttpHeaderName, Empty) {
41+
HttpHeaderName empty;
42+
EXPECT_TRUE(empty.empty());
43+
EXPECT_THAT(std::string(empty), Eq(""));
44+
45+
HttpHeaderName not_empty("header-name");
46+
EXPECT_FALSE(not_empty.empty());
47+
EXPECT_THAT(std::string(not_empty), Eq("header-name"));
48+
}
49+
50+
TEST(HttpHeaderName, LogicalOperators) {
51+
HttpHeaderName h1("aa");
52+
HttpHeaderName h2("bb");
53+
EXPECT_TRUE(h1 < h2);
54+
EXPECT_FALSE(h2 < h1);
55+
EXPECT_FALSE(h1 == h2);
56+
EXPECT_FALSE(h2 == h1);
57+
EXPECT_TRUE(h1 != h2);
58+
EXPECT_TRUE(h2 != h1);
59+
EXPECT_FALSE(h1 > h2);
60+
EXPECT_TRUE(h2 > h1);
61+
EXPECT_TRUE(h1 >= h1);
62+
EXPECT_TRUE(h2 >= h1);
63+
EXPECT_TRUE(h1 <= h2);
64+
EXPECT_TRUE(h1 <= h1);
65+
EXPECT_TRUE(h2 >= h1);
66+
}
67+
2668
TEST(HttpHeader, ConstructionAndStringFormatting) {
2769
HttpHeader empty;
2870
EXPECT_THAT(std::string(empty), Eq(""));

0 commit comments

Comments
 (0)