Added ability to set/get HTTP response/request version
+ Additional tests
This commit is contained in:
@@ -15,6 +15,12 @@ namespace fr
|
||||
class Http : public Sendable
|
||||
{
|
||||
public:
|
||||
enum RequestVersion
|
||||
{
|
||||
V1 = 1, // HTTP/1.0
|
||||
V1_1 = 2, // HTTP/1.1
|
||||
VersionCount = 3
|
||||
};
|
||||
enum RequestType
|
||||
{
|
||||
Get = 0,
|
||||
@@ -224,6 +230,20 @@ namespace fr
|
||||
*/
|
||||
const std::string &get_body() const;
|
||||
|
||||
/*!
|
||||
* Sets the HTTP version
|
||||
*
|
||||
* @param version The HTTP version to use
|
||||
*/
|
||||
void set_version(RequestVersion version);
|
||||
|
||||
/*!
|
||||
* Gets the HTTP version
|
||||
*
|
||||
* @return The current HTTP version
|
||||
*/
|
||||
RequestVersion get_version() const;
|
||||
|
||||
/*!
|
||||
* URL Encodes a given string
|
||||
*
|
||||
@@ -263,7 +283,7 @@ namespace fr
|
||||
* @param str The string to convert
|
||||
* @return The converted RequestType. Unknown on failure. Or Partial if str is part of a request type.
|
||||
*/
|
||||
static RequestType string_to_request_type(const std::string &str) ;
|
||||
static RequestType string_to_request_type(const std::string &str);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
@@ -298,7 +318,7 @@ namespace fr
|
||||
* @param socket The socket to send through
|
||||
* @return Status indicating if the send succeeded or not.
|
||||
*/
|
||||
virtual Socket::Status send(Socket *socket) const override;
|
||||
Socket::Status send(Socket *socket) const override;
|
||||
|
||||
/*!
|
||||
* Overrideable receive, to allow
|
||||
@@ -308,7 +328,7 @@ namespace fr
|
||||
* @param socket The socket to send through
|
||||
* @return Status indicating if the send succeeded or not.
|
||||
*/
|
||||
virtual Socket::Status receive(Socket *socket) override;
|
||||
Socket::Status receive(Socket *socket) override;
|
||||
|
||||
//Other request info
|
||||
std::unordered_map<std::string, std::string> header_data;
|
||||
@@ -318,8 +338,7 @@ namespace fr
|
||||
RequestType request_type;
|
||||
std::string uri;
|
||||
RequestStatus status;
|
||||
|
||||
private:
|
||||
RequestVersion version;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
15
src/Http.cpp
15
src/Http.cpp
@@ -6,6 +6,8 @@
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <frnetlib/Http.h>
|
||||
|
||||
#include "frnetlib/Http.h"
|
||||
|
||||
namespace fr
|
||||
@@ -13,7 +15,8 @@ namespace fr
|
||||
Http::Http()
|
||||
: request_type(Unknown),
|
||||
uri("/"),
|
||||
status(Ok)
|
||||
status(Ok),
|
||||
version(V1_1)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1016,4 +1019,14 @@ namespace fr
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void Http::set_version(Http::RequestVersion v)
|
||||
{
|
||||
version = v;
|
||||
}
|
||||
|
||||
Http::RequestVersion Http::get_version() const
|
||||
{
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,9 @@ namespace fr
|
||||
request += "&";
|
||||
}
|
||||
}
|
||||
request += " HTTP/1.1\r\n";
|
||||
|
||||
static_assert(RequestVersion::VersionCount == 3, "Update me");
|
||||
request += (version == RequestVersion::V1) ? " HTTP/1.0\r\n" : " HTTP/1.1\r\n";
|
||||
|
||||
//Add the headers to the request
|
||||
for(const auto &header : header_data)
|
||||
@@ -197,27 +199,32 @@ namespace fr
|
||||
void HttpRequest::parse_header_uri(const std::string &str)
|
||||
{
|
||||
auto uri_begin = str.find('/');
|
||||
auto uri_end = str.find("HTTP") - 1;
|
||||
if(uri_begin != std::string::npos)
|
||||
auto uri_end = str.find("HTTP");
|
||||
if(uri_begin == std::string::npos || uri_end == std::string::npos)
|
||||
{
|
||||
//Parse GET variables
|
||||
auto get_begin = str.find('?');
|
||||
if(get_begin != std::string::npos)
|
||||
{
|
||||
auto get_vars = parse_argument_list(str.substr(get_begin, uri_end - get_begin));
|
||||
for(auto &c : get_vars)
|
||||
{
|
||||
std::transform(c.first.begin(), c.first.end(), c.first.begin(), ::tolower);
|
||||
get_data.emplace(std::move(c.first), std::move(c.second));
|
||||
}
|
||||
set_uri(str.substr(uri_begin, get_begin - uri_begin));
|
||||
}
|
||||
else
|
||||
{
|
||||
set_uri(str.substr(uri_begin, uri_end - uri_begin));
|
||||
}
|
||||
return;
|
||||
throw std::invalid_argument("No URI found in: " + str);
|
||||
}
|
||||
throw std::invalid_argument("No URI found in: " + str);
|
||||
--uri_end;
|
||||
|
||||
//Parse GET variables
|
||||
auto get_begin = str.find('?');
|
||||
if(get_begin != std::string::npos)
|
||||
{
|
||||
auto get_vars = parse_argument_list(str.substr(get_begin, uri_end - get_begin));
|
||||
for(auto &c : get_vars)
|
||||
{
|
||||
std::transform(c.first.begin(), c.first.end(), c.first.begin(), ::tolower);
|
||||
get_data.emplace(std::move(c.first), std::move(c.second));
|
||||
}
|
||||
set_uri(str.substr(uri_begin, get_begin - uri_begin));
|
||||
}
|
||||
else
|
||||
{
|
||||
set_uri(str.substr(uri_begin, uri_end - uri_begin));
|
||||
}
|
||||
|
||||
//Parse HTTP version. HTTP/1.0 or HTTP/1.1
|
||||
static_assert(RequestVersion::VersionCount == 3, "Update me");
|
||||
version = str.compare(uri_end + 1, 8, "HTTP/1.0") == 0 ? RequestVersion::V1 : RequestVersion::V1_1;
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,9 @@ namespace fr
|
||||
std::string HttpResponse::construct(const std::string &host) const
|
||||
{
|
||||
//Add HTTP header
|
||||
std::string response = "HTTP/1.1 " + std::to_string(status) + " \r\n";
|
||||
|
||||
static_assert(RequestVersion::VersionCount == 3, "Update me");
|
||||
std::string response = ((version == RequestVersion::V1) ? "HTTP/1.0 " : "HTTP/1.1 ") + std::to_string(status) + " \r\n";
|
||||
|
||||
//Add the headers to the response
|
||||
for(const auto &header : header_data)
|
||||
@@ -103,6 +105,10 @@ namespace fr
|
||||
return false;
|
||||
auto end_pos = header_lines[0].find(' ', status_begin + 1);
|
||||
status = (RequestStatus)std::stoi(header_lines[0].substr(status_begin, end_pos - status_begin));
|
||||
|
||||
//Get HTTP version
|
||||
static_assert(RequestVersion::VersionCount == 3, "Update me");
|
||||
version = header_lines[0].compare(0, status_begin, "HTTP/1.0") == 0 ? RequestVersion::V1 : RequestVersion::V1_1;
|
||||
line++;
|
||||
|
||||
//Read in headers
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace fr
|
||||
#ifndef _WIN32
|
||||
|
||||
SocketSelector::SocketSelector()
|
||||
: epoll_fd(-1)
|
||||
: epoll_fd(-1)
|
||||
{
|
||||
epoll_fd = epoll_create1(O_CLOEXEC);
|
||||
if(epoll_fd < 0)
|
||||
@@ -33,8 +33,7 @@ namespace fr
|
||||
throw std::logic_error("Can't add disconnected socket");
|
||||
}
|
||||
|
||||
auto add_iter = added_sockets.emplace(socket->get_socket_descriptor(),
|
||||
Opaque(socket->get_socket_descriptor(), socket, opaque));
|
||||
auto add_iter = added_sockets.emplace(socket->get_socket_descriptor(), Opaque(socket->get_socket_descriptor(), socket, opaque));
|
||||
if(!add_iter.second)
|
||||
{
|
||||
throw std::logic_error("Can't add duplicate socket: " + std::to_string(socket->get_socket_descriptor()));
|
||||
|
||||
@@ -31,6 +31,7 @@ TEST(HttpRequestTest, get_request_parse)
|
||||
ASSERT_EQ(request.header_exists("non-existant"), false);
|
||||
|
||||
//Check that headers are intact
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
ASSERT_EQ(request.header("Host"), "frednicolson.co.uk");
|
||||
ASSERT_EQ(request.header("Content-Type"), "application/x-www-form-urlencoded");
|
||||
ASSERT_EQ(request.header("My-Other-Header"), "header2");
|
||||
@@ -62,6 +63,7 @@ TEST(HttpRequestTest, post_request_parse)
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Post);
|
||||
|
||||
//Test that URI is intact
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
ASSERT_EQ(request.get_uri(), "/index.html");
|
||||
|
||||
//Parse code is the same for GET, so skip header checks. Test if POST data exists.
|
||||
@@ -77,7 +79,9 @@ TEST(HttpRequestTest, post_request_parse)
|
||||
TEST(HttpRequestTest, request_type_parse)
|
||||
{
|
||||
const std::string get_request = "GET / HTTP/1.1\r\n\r\n";
|
||||
const std::string get_request_v2 = "GET / HTTP/1.0\r\n\r\n";
|
||||
const std::string post_request = "POST / HTTP/1.1\r\n\r\n";
|
||||
const std::string post_request_v2 = "POST / HTTP/1.0\r\n\r\n";
|
||||
const std::string put_request = "PUT / HTTP/1.1\r\n\r\n";
|
||||
const std::string delete_request = "DELETE / HTTP/1.1\r\n\r\n";
|
||||
const std::string patch_request = "PATCH / HTTP/1.1\r\n\r\n";
|
||||
@@ -87,30 +91,47 @@ TEST(HttpRequestTest, request_type_parse)
|
||||
fr::HttpRequest request;
|
||||
ASSERT_EQ(request.parse(get_request.c_str(), get_request.size()), fr::Socket::Success);
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Get);
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
request = {};
|
||||
|
||||
ASSERT_EQ(request.parse(post_request.c_str(), post_request.size()), fr::Socket::Success);
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Post);
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
request = {};
|
||||
|
||||
ASSERT_EQ(request.parse(get_request_v2.c_str(), get_request_v2.size()), fr::Socket::Success);
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Get);
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1);
|
||||
request = {};
|
||||
|
||||
ASSERT_EQ(request.parse(post_request_v2.c_str(), post_request_v2.size()), fr::Socket::Success);
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Post);
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1);
|
||||
request = {};
|
||||
|
||||
ASSERT_EQ(request.parse(put_request.c_str(), put_request.size()), fr::Socket::Success);
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Put);
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
request = {};
|
||||
|
||||
ASSERT_EQ(request.parse(delete_request.c_str(), delete_request.size()), fr::Socket::Success);
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Delete);
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
request = {};
|
||||
|
||||
ASSERT_EQ(request.parse(patch_request.c_str(), patch_request.size()), fr::Socket::Success);
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Patch);
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
request = {};
|
||||
|
||||
ASSERT_EQ(request.parse(invalid_request.c_str(), invalid_request.size()), fr::Socket::ParseError);
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Unknown);
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
request = {};
|
||||
|
||||
ASSERT_EQ(request.parse(invalid_request2.c_str(), invalid_request2.size()), fr::Socket::ParseError);
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Unknown);
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
request = {};
|
||||
}
|
||||
|
||||
@@ -119,6 +140,7 @@ TEST(HttpRequestTest, get_request_construction)
|
||||
//Create a request
|
||||
fr::HttpRequest request;
|
||||
ASSERT_EQ(request.get_uri(), "/");
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
|
||||
request.header("MyHeader") = "header1";
|
||||
request.header("MyOther-Header") = "header2";
|
||||
@@ -126,7 +148,7 @@ TEST(HttpRequestTest, get_request_construction)
|
||||
request.get("my_other_get") = "var2";
|
||||
request.set_uri("heyo/bobby");
|
||||
request.set_type(fr::Http::Get);
|
||||
const std::string constructed_request = request.construct("frednicolson.co.uk");
|
||||
std::string constructed_request = request.construct("frednicolson.co.uk");
|
||||
|
||||
//Parse it and check that everything's correct
|
||||
request = {};
|
||||
@@ -137,6 +159,14 @@ TEST(HttpRequestTest, get_request_construction)
|
||||
ASSERT_EQ(request.get("my_other_get"), "var2");
|
||||
ASSERT_EQ(request.get_uri(), "/heyo/bobby");
|
||||
ASSERT_EQ(request.get_type(), fr::Http::Get);
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
|
||||
//Quick v1 test
|
||||
request.set_version(fr::Http::RequestVersion::V1);
|
||||
constructed_request = request.construct("frednicolson.co.uk");
|
||||
request = {};
|
||||
request.parse(constructed_request.c_str(), constructed_request.size());
|
||||
ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1);
|
||||
}
|
||||
|
||||
TEST(HttpRequestTest, post_request_construction)
|
||||
|
||||
@@ -5,7 +5,26 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <frnetlib/HttpResponse.h>
|
||||
|
||||
TEST(HttpResponseTest, response_parse)
|
||||
TEST(HttpResponseTest, response_parse_v1)
|
||||
{
|
||||
const std::string raw_response =
|
||||
"HTTP/1.0 301 Moved Permanently\n"
|
||||
"Server: nginx/1.10.2\n"
|
||||
"Date: Mon, 25 Sep 2017 13:51:56 GMT\n"
|
||||
"Content-Type: text/html\n"
|
||||
"Content-Length: 0\n"
|
||||
"Connection: keep-alive\n"
|
||||
"Location: https://frednicolson.co.uk/\n\n";
|
||||
|
||||
//Parse response
|
||||
fr::HttpResponse test;
|
||||
ASSERT_EQ(test.parse(raw_response.c_str(), raw_response.size()), fr::Socket::Success);
|
||||
|
||||
//Verify it
|
||||
ASSERT_EQ(test.get_version(), fr::Http::RequestVersion::V1);
|
||||
}
|
||||
|
||||
TEST(HttpResponseTest, response_parse_v2)
|
||||
{
|
||||
const std::string raw_response =
|
||||
"HTTP/1.1 301 Moved Permanently\n"
|
||||
@@ -38,6 +57,7 @@ TEST(HttpResponseTest, response_parse)
|
||||
ASSERT_EQ(test.parse(raw_response.c_str(), raw_response.size()), fr::Socket::Success);
|
||||
|
||||
//Verify it
|
||||
ASSERT_EQ(test.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
ASSERT_EQ(test.get_status(), fr::Http::MovedPermanently);
|
||||
ASSERT_EQ(test.header("Content-length"), "177");
|
||||
ASSERT_EQ(test.get_body(), response_body);
|
||||
@@ -116,4 +136,33 @@ TEST(HttpResponseTest, body_length_test)
|
||||
buff += std::string(MAX_HTTP_BODY_SIZE + 1, '\0');
|
||||
fr::HttpResponse response;
|
||||
ASSERT_EQ(response.parse(buff.c_str(), buff.size()), fr::Socket::HttpBodyTooBig);
|
||||
}
|
||||
|
||||
TEST(HttpResponseTest, HttpResponseConstruction)
|
||||
{
|
||||
{
|
||||
fr::HttpResponse response;
|
||||
response.set_status(fr::Http::ImATeapot);
|
||||
response.header("bob") = "trob";
|
||||
response.set_body("lob");
|
||||
auto constructed = response.construct("frednicolson.co.uk");
|
||||
response = {};
|
||||
ASSERT_EQ(response.parse(constructed.c_str(), constructed.size()), fr::Socket::Status::Success);
|
||||
|
||||
ASSERT_EQ(response.get_version(), fr::Http::RequestVersion::V1_1);
|
||||
ASSERT_EQ(response.get_status(), fr::Http::RequestStatus::ImATeapot);
|
||||
ASSERT_EQ(response.get_body(), "lob");
|
||||
ASSERT_EQ(response.header("bob"), "trob");
|
||||
}
|
||||
|
||||
{
|
||||
fr::HttpResponse response;
|
||||
response.set_version(fr::Http::RequestVersion::V1);
|
||||
auto constructed = response.construct("frednicolson.co.uk");
|
||||
response = {};
|
||||
response.parse(constructed.c_str(), constructed.size());
|
||||
|
||||
ASSERT_EQ(response.get_version(), fr::Http::RequestVersion::V1);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user