Skip to content

Commit

Permalink
HttpHeader
Browse files Browse the repository at this point in the history
  • Loading branch information
uchenily committed May 31, 2024
1 parent f91b3e4 commit 68410cc
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 0 deletions.
1 change: 1 addition & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ all_tests_sources = [
'test_buffered2.cpp',
'test_coredump.cpp',
'test_llhttp.cpp',
'test_caseinsensitive.cpp',
]

# or
Expand Down
27 changes: 27 additions & 0 deletions tests/test_caseinsensitive.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include "uvio/debug.hpp"
#include "uvio/net/http/http_util.hpp"

using namespace uvio::net::http;
auto main() -> int {
CaseInsensitiveEqual case_insensitive_equal;
ASSERT(case_insensitive_equal("Test", "tesT"));
ASSERT(case_insensitive_equal("tesT", "test"));
ASSERT(!case_insensitive_equal("test", "tset"));

CaseInsensitiveHash case_insensitive_hash;
ASSERT(case_insensitive_hash("Test") == case_insensitive_hash("tesT"));
ASSERT(case_insensitive_hash("tesT") == case_insensitive_hash("test"));
ASSERT(case_insensitive_hash("test") != case_insensitive_hash("tset"));

HttpHeader header;
header.add("Content-Type", "application/json");
header.add("Set-Cookie", "ID=id");
header.add("Set-Cookie", "name=name");
ASSERT(header.find("content-type").has_value());
ASSERT(!header.find("accept").has_value());
ASSERT(header.find("Set-Cookie").has_value());

auto real_value = *header.find_all("Set-Cookie");
auto expected = std::vector<std::string>{"ID=id", "name=name"};
ASSERT(real_value == expected);
}
76 changes: 76 additions & 0 deletions uvio/net/http/http_util.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#pragma once

#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

namespace uvio::net::http {

class CaseInsensitiveEqual {
public:
auto operator()(const std::string &left,
const std::string &right) const noexcept -> bool {
return left.size() == right.size()
&& std::equal(left.begin(),
left.end(),
right.begin(),
[](char a, char b) {
return tolower(a) == tolower(b);
});
}
};

// Based on
// https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
class CaseInsensitiveHash {
public:
auto operator()(const std::string &str) const noexcept -> std::size_t {
std::size_t seed = 0;
std::hash<int> hash;
for (auto c : str) {
seed ^= hash(tolower(c)) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
return seed;
}
};

using CaseInsensitiveMultimap = std::unordered_multimap<std::string,
std::string,
CaseInsensitiveHash,
CaseInsensitiveEqual>;

class HttpHeader {
public:
auto add(const std::string &key, const std::string &value) {
header_multimap_.emplace(key, value);
}

auto find(const std::string &case_insensitive_key)
-> std::optional<std::string> {
auto range = header_multimap_.equal_range(case_insensitive_key);
if (range.first != header_multimap_.end()) {
return range.first->second;
} else {
return std::nullopt;
}
}

auto find_all(const std::string &case_insensitive_key)
-> std::optional<std::vector<std::string>> {
auto range = header_multimap_.equal_range(case_insensitive_key);
if (range.first != header_multimap_.end()) {
std::vector<std::string> res;
for (auto it = range.first; it != range.second; ++it) {
res.push_back(it->second);
}
return res;
} else {
return std::nullopt;
}
}

private:
CaseInsensitiveMultimap header_multimap_;
};
} // namespace uvio::net::http

0 comments on commit 68410cc

Please sign in to comment.