Skip to content

Instantly share code, notes, and snippets.

@lpcvoid
Created December 26, 2022 14:16
Show Gist options
  • Save lpcvoid/4b2445d2b81d868177a93c81f77de0b0 to your computer and use it in GitHub Desktop.
Save lpcvoid/4b2445d2b81d868177a93c81f77de0b0 to your computer and use it in GitHub Desktop.

Revisions

  1. lpcvoid created this gist Dec 26, 2022.
    84 changes: 84 additions & 0 deletions http.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,84 @@
    #pragma once

    namespace netlib::http {

    using http_header_entry = std::pair<std::string, std::string>;
    using http_headers = std::vector<http_header_entry>;

    struct http_response {
    http_headers headers;
    uint32_t response_code;
    std::pair<uint32_t, uint32_t> version;
    std::string body;
    inline std::error_condition from_raw_response(const std::string& raw_response) {
    // a very rudimentary http response parser
    if (raw_response.empty()) {
    return std::errc::no_message;
    }

    /* strategy:
    * split into multiple (at least two) parts, delimited by \r\n\r\n"
    * within the first part:
    * first, parse the status line
    * second, parse the header fields, until we arrive at an empty line (only CR LF)
    * last, an optional body
    * then, for the rest of the parts, concat into body
    */

    auto split = [](const std::string& str, const std::string& delimiter) -> std::vector<std::string> {
    std::vector<std::string> split_tokens;
    std::size_t start;
    std::size_t end = 0;
    while ((start = str.find_first_not_of(delimiter, end)) != std::string::npos)
    {
    end = str.find(delimiter, start);
    split_tokens.push_back(str.substr(start, end - start));
    }
    return split_tokens;
    };

    std::vector<std::string> header_body_split = split(raw_response, "\r\n\r\n");
    //split header part of response into response_header_lines
    std::vector<std::string> response_header_lines = split(header_body_split.front(), "\r\n");
    //first line should start with "HTTP"
    if (!response_header_lines.front().starts_with("HTTP")) {
    return std::errc::result_out_of_range;
    }
    //attempt to parse status line
    //split into parts by space
    auto status_parts = split(response_header_lines.front(), " ");
    if (status_parts.size() < 3) {
    return std::errc::bad_message;
    }
    //parse "HTTP/x.x"
    auto version_parts = split(status_parts.front(), "/");
    if (version_parts.size() != 2) {
    return std::errc::bad_message;
    }
    //parse "x.x"
    auto version_components = split(version_parts.back(), ".");
    version.first = std::stoi(version_components.front());
    version.second = std::stoi(version_components.back());
    //parse response code
    response_code = std::stoi(status_parts[1]);
    //there can be an optional code description in the first line, but we ignore that here
    //parse the response header lines until the end
    //start at second line, first is status
    std::for_each(response_header_lines.begin(), response_header_lines.end(), [&](const std::string& header_component){
    auto component_parts = split(header_component, ":");
    if (component_parts.size() == 2) {
    headers.emplace_back(component_parts.front(), component_parts.back());
    }
    });

    //now, take the body part(s) and concat them
    std::for_each(header_body_split.begin() + 1, header_body_split.end(), [&](const std::string& body_line){
    body += body_line;
    });

    return {};

    };
    };

    }