0

I'm writing an HTTP client in C++11/14 (VS 2015) for practice with asynchronous and network programming using ASIO (standalone, not boost, by the way, so please refrain from boost-specific solutions). This is a fairly basic question, but I nonetheless would appreciate an answer.

struct Header {
    Header(const std::string& key, const std::string& val); // initializes key/val
    std::string key, val;
    bool operator==(const Header& header); // returns true if key/val are equal
    friend std::ostream& operator<<(std::ostream& os, const Header& header) {
        os << header.key << ": " << header.val;
        return os;
    }
};

I've run into an issue, though. Some HTTP headers can be repeated. Others should not be. In particular, when I generate the "Host: website.com" header, I want to check and see if the user has not already input a Host parameter.

  • My initial thought was to use std::unordered_set<Header> and provide a hash function that only hashes the Key value, thus preventing duplicates. However, some headers are perfectly valid to duplicate. Set-Cookie for example
  • I then thought of creating a vector similar to this:

    static const std::unordered_set<std::string, HashIgnoreCase>& AllowedDuplicates{
        "set-cookie",
        "cookie",
    }; // HashIgnoreCase uses std::hash<std::string> on lowercase-converted strings.
    

    Then use that to check user-provided values. However, this seemed ugly since I'd have on std::unordered_set<Header> for most headers and a std::vector<Header> for the duplicate headers.

  • Another idea would be to completely restrict the user from providing headers that should be singular, such as throwing an exception (or some clever static_assert) to prevent values like "Host" and "Content-Length" from being provided by the user, but that seems functionally restrictive, which is not typically my goal.

What would be the best way to go about this?

Goodies
  • 4,439
  • 3
  • 31
  • 57
  • 2
    How about `std::map` or `std::multimap` – Mine Oct 20 '16 at 06:51
  • 1
    Just use a standard [`std::unordered_multimap`](http://en.cppreference.com/w/cpp/container/unordered_multimap) and report an error if the user wants to insert a header that already exists and is not allowed as a duplicate? – Some programmer dude Oct 20 '16 at 06:52
  • @JoachimPileborg any chance of a very basic implementation of this? – Goodies Oct 20 '16 at 06:53
  • `std::unordered_map<>` for unique headers, `std::unordered_multimap<>` for headers that could appear more than once, `const std::unordered_set` with unique header names, and a custom iterator for the Headers class, which would iterate over unique and non-unique headers – user3159253 Oct 20 '16 at 07:00
  • Also, likely you need to explicitly keep an incoming order of the headers, to be able to reproduce it during the iteration – user3159253 Oct 20 '16 at 07:07

2 Answers2

2

My understanding from here: https://stackoverflow.com/a/4371395/493106

is that headers can only be repeated if they could be combined into a single comma separated list.

That seems to imply that a normal map would be fine but if you try to add a new header that already exists, you just append "," then the new value onto the end of the old one.

Community
  • 1
  • 1
xaxxon
  • 19,189
  • 5
  • 50
  • 80
  • 1
    Well, that answer as it seems to me specify which headers may appear more than once, not the form in which they may appear. I'm not sure about HTTP, but Generic MIME messages with multiple headers are perfectly valid, e.g. `Received`. The order is important. – user3159253 Oct 20 '16 at 07:04
  • @user3159253 This is more specifically for HTTP. – Goodies Oct 20 '16 at 07:05
  • Thanks for that link. I suppose one option is to check and see if the header is already in and, if it is, append it to the string with a comma. I'd still need to validate which headers must be unique (if any). – Goodies Oct 20 '16 at 07:07
  • @Goodies there's no way around that if you want to enforce correct headers... regardless of any of the approaches suggested. – xaxxon Oct 20 '16 at 07:08
  • @user3159253 "Multiple message-header fields with the same field-name MAY be present in a message **if and only if** the entire field-value for that header field is defined as a comma-separated list " – xaxxon Oct 20 '16 at 07:09
  • Yes, I have read the statement. But the definition of the field-value doesn't restrict the way field-value(s) appear in a message. So if for a header with a given name its field-value is defined that way, then there could be multiple headers of the same name, each containing value of a comma-separated list, possibly with a single element within. – user3159253 Oct 20 '16 at 12:45
  • 1
    @user3159253 and you could just append one list onto the other – xaxxon Oct 20 '16 at 12:49
  • Ah, yes, indeed, I can. The only disadvantage of such approach, seemingly, is inability to keep the exact order of headers, if initially they were shuffled. – user3159253 Oct 21 '16 at 04:05
1

Expanding on my comment...

You simply try to find the header in the map, and if it's in the map and it's not allowed multiple times.

You can use a normal vector or array of strings to store allowed duplicates, and do a simple search for it there.

Perhaps something like this

std::unodered_multimap<std::string, std::string> headers;
std::array<std::string, 2> allowed_duplicates = {{
    "set-cookie", "cookie"
}};

void add_header(std::string const& header, std::string const& data)
{
    if (headers.find(header) != headers.end())
    {
        // Header found, check if it's allowed multiple times
        if (std::find(allowed_duplicates.begin(),
                      allowed_duplicates.end(), header) == allowed_duplicates.end())
        {
            // Not found, the header is not allowed multiple times
            return;
        }
    }

    // Header is allowed
    headers.emplace(std::make_pair(header, data));
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Based on your answer and the link provided by @xaxxon, I think a better option may be to allow duplicates by default and restrict unique ones. i.e. if host exists, then allow the user to override it. Otherwise, provide it. Thanks! – Goodies Oct 20 '16 at 07:12
  • 1
    The query interface for that wouldn't be fun, though, since you'd always have to return an array/vector/whatever of strings for a get_header(std:;string const&) kind of api.. if it were common for headers to be repeated, this wouldn't be as big a deal, but repeated headers are usually quite uncommon – xaxxon Oct 20 '16 at 07:12
  • @xaxxon Right. I'm thinking just returning a `pair` would be fine (or Header object). The reason I was using a header object is because I had a similar object for Parameters in HTTP POST requests (among other things) and wanted to overload what happens when provided with just headers and just parameters, and couldn't do so if they were both the same type. Although parameters cannot be duplicated afaik. – Goodies Oct 20 '16 at 07:16
  • The second string would be the comma-separated list of values. – Goodies Oct 20 '16 at 07:16