Cookie and Header parsing

This commit is contained in:
BENEDEK László 2023-12-05 22:41:20 +01:00
parent c567bb2447
commit 21afe9ca13
12 changed files with 152 additions and 139 deletions

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "method.h" #include <rum/http/method.h>
#include "request.h" #include <rum/http/request.h>
#include "response.h" #include <rum/http/response.h>
#include "server.h" #include <rum/http/server.h>

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <string> #include <string>
namespace Rum::HTTP { namespace Rum::HTTP {
enum Method { GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE }; enum Method { GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE };

View File

@ -1,9 +1,9 @@
#pragma once #pragma once
#include <rum/http/method.h>
#include <rum/http/uri.h>
#include <map> #include <map>
#include <string> #include <string>
#include "method.h"
#include "rum/http/uri.h"
namespace Rum::HTTP { namespace Rum::HTTP {
class Request { class Request {
@ -12,12 +12,16 @@ class Request {
Request(Method method, const URI& uri) : method(method), uri(uri), body("") {} Request(Method method, const URI& uri) : method(method), uri(uri), body("") {}
Method get_method() const { return method; } Method get_method() const { return method; }
const URI& get_uri() const { return uri; } URI& get_uri() { return uri; }
const std::map<std::string, std::string>& get_headers() const { return headers; } const std::map<std::string, std::string>& get_headers() const { return headers; }
std::string get_header(std::string name) const { return headers.at(name); } std::string get_header(std::string name) const { return headers.at(name); }
void set_header(std::string name, std::string value) { headers[name] = value; } void set_header(std::string name, std::string value) { headers[name] = value; }
const std::map<std::string, std::string>& get_cookies() const { return cookies; }
std::string get_cookie(std::string name) const { return cookies.at(name); }
void set_cookie(std::string name, std::string value) { cookies[name] = value; }
std::string get_body() const { return body; } std::string get_body() const { return body; }
void set_body(std::string body) { this->body = body; } void set_body(std::string body) { this->body = body; }
@ -29,6 +33,7 @@ class Request {
Method method; Method method;
URI uri; URI uri;
std::map<std::string, std::string> headers; std::map<std::string, std::string> headers;
std::map<std::string, std::string> cookies;
std::string body; std::string body;
}; };
} // namespace Rum::HTTP } // namespace Rum::HTTP

View File

@ -1,22 +1,25 @@
#pragma once #pragma once
#include <map>
#include <string> #include <string>
namespace Rum::HTTP { namespace Rum::HTTP {
class Response { class Response {
private: private:
const int client_sock; const int client_sock;
bool sent_header;
bool sent_body;
bool closed;
unsigned int code; unsigned int code;
public: public:
Response(int client_sock) : client_sock(client_sock), sent_header(false), sent_body(false), closed(false), code(200) {} Response(int client_sock) : client_sock(client_sock), code(200) {}
~Response(); ~Response();
void send_header(const std::string& name, const std::string& value); void set_code(unsigned int code) {
void send_body(const std::string& value); if (code >= 100 && code < 600)
void set_code(unsigned int code) { this->code = code; } this->code = code;
}
std::map<std::string, std::string> cookies;
std::map<std::string, std::string> headers;
std::string body;
}; };
} // namespace Rum::HTTP } // namespace Rum::HTTP

View File

@ -7,9 +7,9 @@
#include <queue> #include <queue>
#include <regex> #include <regex>
#include <vector> #include <vector>
#include "../tcp/server.h" #include <rum/tcp/server.h>
#include "request.h" #include <rum/http/request.h>
#include "response.h" #include <rum/http/response.h>
#define DEFAULT_WORKER_COUNT 10 #define DEFAULT_WORKER_COUNT 10
#define DEFAULT_BUFFER_SIZE 8196 #define DEFAULT_BUFFER_SIZE 8196

View File

@ -29,6 +29,9 @@ class URI {
const std::map<std::string, std::string>& get_parameters() const { return parameters; } const std::map<std::string, std::string>& get_parameters() const { return parameters; }
const std::string& get_fragment() const { return fragment; } const std::string& get_fragment() const { return fragment; }
void set_host(const std::string& host) { this->host = host; }
void set_port(const std::string& port) { this->port = port; }
operator std::string() const; operator std::string() const;
}; };
} // namespace Rum::HTTP } // namespace Rum::HTTP

View File

@ -1,5 +1,5 @@
#pragma once #pragma once
#include "error.h" #include <rum/tcp/error.h>
#include "server.h" #include <rum/tcp/server.h>
#include "utility.h" #include <rum/tcp/utility.h>

View File

@ -1,8 +1,8 @@
#include <rum/http/method.h>
#include <rum/http/request.h> #include <rum/http/request.h>
#include <format> #include <format>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include "rum/http/method.h"
namespace Rum::HTTP { namespace Rum::HTTP {
@ -16,7 +16,7 @@ Request::operator std::string() const {
"\tmethod: {}\n" "\tmethod: {}\n"
"\t{}\n" "\t{}\n"
"\theaders: \n{}" "\theaders: \n{}"
"\tbody: \n'{}'" "\tbody: '{}'\n"
"}}", "}}",
std::make_format_args(to_string(method), (std::string)uri, headers_string, body)); std::make_format_args(to_string(method), (std::string)uri, headers_string, body));
} }

View File

@ -1,72 +1,73 @@
#include <rum/http/response.h> #include <rum/http/response.h>
#include <rum/tcp/error.h> #include <rum/tcp/error.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <format>
#include <map> #include <map>
#include <stdexcept> #include <stdexcept>
namespace Rum::HTTP { namespace Rum::HTTP {
std::string to_string(int code) { std::string to_string(int code) {
static const std::map<int, std::string> codes{{100, "Continue"}, static const std::map<int, std::string> codes{{100, "Continue"},
{101, "Switching Protocols"}, {101, "Switching Protocols"},
{102, "Processing"}, {102, "Processing"},
{103, "Early Hints"}, {103, "Early Hints"},
{200, "OK"}, {200, "OK"},
{201, "Created"}, {201, "Created"},
{202, "Accepted"}, {202, "Accepted"},
{203, "Non-Authoritative Information"}, {203, "Non-Authoritative Information"},
{204, "No Content"}, {204, "No Content"},
{205, "Reset Content"}, {205, "Reset Content"},
{206, "Partial Content"}, {206, "Partial Content"},
{207, "Multi-Status"}, {207, "Multi-Status"},
{208, "Already Reported"}, {208, "Already Reported"},
{226, "IM Used"}, {226, "IM Used"},
{300, "Multiple Choices"}, {300, "Multiple Choices"},
{301, "Moved Permanently"}, {301, "Moved Permanently"},
{302, "Found"}, {302, "Found"},
{303, "See Other"}, {303, "See Other"},
{304, "Not Modified"}, {304, "Not Modified"},
{307, "Temporary Redirect"}, {307, "Temporary Redirect"},
{308, "Permanent Redirect"}, {308, "Permanent Redirect"},
{400, "Bad Request"}, {400, "Bad Request"},
{401, "Unauthorized"}, {401, "Unauthorized"},
{402, "Payment Required"}, {402, "Payment Required"},
{403, "Forbidden"}, {403, "Forbidden"},
{404, "Not Found"}, {404, "Not Found"},
{405, "Method Not Allowed"}, {405, "Method Not Allowed"},
{406, "Not Acceptable"}, {406, "Not Acceptable"},
{407, "Proxy Authentication Required"}, {407, "Proxy Authentication Required"},
{408, "Request Timeout"}, {408, "Request Timeout"},
{409, "Conflict"}, {409, "Conflict"},
{410, "Gone"}, {410, "Gone"},
{411, "Length Required"}, {411, "Length Required"},
{412, "Precondition Failed"}, {412, "Precondition Failed"},
{413, "Content Too Large"}, {413, "Content Too Large"},
{414, "URI Too Long"}, {414, "URI Too Long"},
{415, "Unsupported Media Type"}, {415, "Unsupported Media Type"},
{416, "Range Not Satisfiable"}, {416, "Range Not Satisfiable"},
{417, "Expectation Failed"}, {417, "Expectation Failed"},
{418, "I'm a teapot"}, {418, "I'm a teapot"},
{421, "Misdirected Request"}, {421, "Misdirected Request"},
{422, "Unprocessable Content"}, {422, "Unprocessable Content"},
{423, "Locked"}, {423, "Locked"},
{424, "Failed Dependency"}, {424, "Failed Dependency"},
{425, "Too Early"}, {425, "Too Early"},
{426, "Upgrade Required"}, {426, "Upgrade Required"},
{428, "Precondition Required"}, {428, "Precondition Required"},
{429, "Too Many Requests"}, {429, "Too Many Requests"},
{431, "Request Header Fields Too Large"}, {431, "Request Header Fields Too Large"},
{451, "Unavailable For Legal Reasons"}, {451, "Unavailable For Legal Reasons"},
{500, "Internal Server Error"}, {500, "Internal Server Error"},
{501, "Not Implemented"}, {501, "Not Implemented"},
{502, "Bad Gateway"}, {502, "Bad Gateway"},
{503, "Service Unavailable"}, {503, "Service Unavailable"},
{504, "Gateway Timeout"}, {504, "Gateway Timeout"},
{505, "HTTP Version Not Supported"}, {505, "HTTP Version Not Supported"},
{506, "Variant Also Negotiates"}, {506, "Variant Also Negotiates"},
{507, "Insufficient Storage"}, {507, "Insufficient Storage"},
{508, "Loop Detected"}, {508, "Loop Detected"},
{510, "Not Extended"}, {510, "Not Extended"},
{511, "Network Authentication Required"}}; {511, "Network Authentication Required"}};
try { try {
return codes.at(code); return codes.at(code);
@ -75,47 +76,21 @@ std::string to_string(int code) {
} }
} }
void Response::send_header(const std::string& name, const std::string& value) {
if (!sent_header) {
std::string resp("HTTP/1.1 " + std::to_string(code) + " " + to_string(code) + "\r\n");
if (-1 == send(client_sock, resp.c_str(), resp.size(), 0)) {
closed = true;
throw TCP::Error(TCP::Error::CLOSED);
}
sent_header = true;
}
std::string header = name + ": " + value + "\r\n";
if (-1 == send(client_sock, header.c_str(), header.size(), 0)) {
closed = true;
throw TCP::Error(TCP::Error::CLOSED);
}
}
void Response::send_body(const std::string& value) {
if (!sent_header) {
send_header("Content-Type", "text/html");
}
if (!sent_body) {
if (-1 == send(client_sock, "\r\n", 2, 0)) {
closed = true;
throw TCP::Error(TCP::Error::CLOSED);
}
sent_body = true;
}
if (-1 == send(client_sock, value.c_str(), value.size(), 0)) {
closed = true;
throw TCP::Error(TCP::Error::CLOSED);
}
}
Response::~Response() { Response::~Response() {
if (!sent_header && !closed) { std::string headers_string;
std::string resp("HTTP/1.1 " + std::to_string(code) + " " + to_string(code)); for (auto header : headers) {
send(client_sock, resp.c_str(), resp.size(), 0); headers_string += header.first + ": " + header.second + "\r\n";
}
for (auto cookie : cookies) {
headers_string += "Set-cookie: " + cookie.first + "=" + cookie.second + "\r\n";
} }
}
std::string message = std::vformat(
"HTTP/1.1 {} {}\r\n"
"{}"
"\r\n"
"{}",
std::make_format_args(std::to_string(code), to_string(code), headers_string, body));
send(client_sock, message.c_str(), message.size(), 0);
}
} // namespace Rum::HTTP } // namespace Rum::HTTP

View File

@ -1,6 +1,6 @@
#include "rum/tcp/server.h"
#include <rum/http/server.h> #include <rum/http/server.h>
#include <rum/tcp/error.h> #include <rum/tcp/error.h>
#include <rum/tcp/server.h>
#include <rum/tcp/utility.h> #include <rum/tcp/utility.h>
#include <iostream> #include <iostream>
#include <regex> #include <regex>
@ -101,10 +101,10 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b
ssize_t recieved = recv(client_sock, buffer, buffer_size, 0); ssize_t recieved = recv(client_sock, buffer, buffer_size, 0);
switch (recieved) { switch (recieved) {
case TCP::Error::CLOSED: case TCP::Error::CLOSED:
std::cout << address << ": connection closed" << std::endl; std::cerr << address << ": connection closed" << std::endl;
return; return;
case TCP::Error::UNKNOWN: case TCP::Error::UNKNOWN:
std::cout << "socket error" << std::endl; std::cerr << "socket error" << std::endl;
return; return;
} }
@ -114,8 +114,12 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b
std::regex method_regex("(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE) (\\/.*) HTTP.*"); std::regex method_regex("(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE) (\\/.*) HTTP.*");
std::smatch match; std::smatch match;
if (std::regex_match(message.cbegin(), message.cbegin() + message.find("\r\n"), match, method_regex)) { if (std::regex_match(message.cbegin(), message.cbegin() + message.find("\r\n"), match, method_regex)) {
// create request object try {
request = Request(string_to_method(match.str(1)), URI("http://0.0.0.0:" + std::to_string(TCP::Server::get_port()) + match.str(2))); request = Request(string_to_method(match.str(1)), URI("http://0.0.0.0:" + std::to_string(TCP::Server::get_port()) + match.str(2)));
} catch (std::exception&) {
return;
}
message = message.substr(message.find("\r\n")); message = message.substr(message.find("\r\n"));
stage = HEADER; stage = HEADER;
} else { } else {
@ -127,7 +131,29 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b
std::regex header_regex("(.*): (.*)"); std::regex header_regex("(.*): (.*)");
for (std::sregex_iterator it = std::sregex_iterator(message.cbegin(), message.cbegin() + message.find("\r\n\r\n"), header_regex); for (std::sregex_iterator it = std::sregex_iterator(message.cbegin(), message.cbegin() + message.find("\r\n\r\n"), header_regex);
it != std::sregex_iterator(); it++) { it != std::sregex_iterator(); it++) {
request.set_header(it->str(1), it->str(2)); std::string key(it->str(1));
std::string value(it->str(2));
if (key == "Host") {
if (size_t pos = value.find(':'); pos != value.npos) {
request.get_uri().set_host(value.substr(0, pos));
request.get_uri().set_port(value.substr(pos + 1));
} else {
request.get_uri().set_host(value);
}
} else if (key == "Cookie") {
std::stringstream cookies_string(value);
std::string cookie;
while (std::getline(cookies_string, cookie, ';')) {
if (size_t pos = cookie.find('='); pos != cookie.npos) {
request.set_cookie(cookie.substr(0, pos), cookie.substr(pos + 1));
} else {
return;
}
}
} else {
request.set_header(it->str(1), it->str(2));
}
} }
message = message.substr(message.find("\r\n\r\n")); message = message.substr(message.find("\r\n\r\n"));
@ -143,10 +169,9 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b
} }
} }
std::cout << request << std::endl;
try { try {
Response resp(client_sock); Response resp(client_sock);
resp.headers["Content-type"] = "text/html";
bool found = false; bool found = false;
@ -160,7 +185,7 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b
if (!found) { if (!found) {
resp.set_code(404); resp.set_code(404);
resp.send_body("<h1>404: Page not found :C</h1>"); resp.body = "<h1>404: Page not found :C</h1>";
} }
} catch (std::out_of_range) { } catch (std::out_of_range) {
} catch (TCP::Error) { } catch (TCP::Error) {

View File

@ -4,10 +4,6 @@
#include <regex> #include <regex>
#include <string> #include <string>
#define MATCH(var, i) \
if (match[i].matched) \
var = match.str(i);
namespace Rum::HTTP { namespace Rum::HTTP {
URI::operator std::string() const { URI::operator std::string() const {
@ -38,6 +34,10 @@ URI::URI(const std::string& uri) {
std::cout << uri << std::endl; std::cout << uri << std::endl;
if (std::regex_match(uri.cbegin(), uri.cend(), match, uri_regex)) { if (std::regex_match(uri.cbegin(), uri.cend(), match, uri_regex)) {
#define MATCH(var, i) \
if (match[i].matched) \
var = match.str(i);
MATCH(scheme, 2) MATCH(scheme, 2)
MATCH(user, 4) MATCH(user, 4)
MATCH(password, 6) MATCH(password, 6)

View File

@ -10,7 +10,7 @@ int main(int argc, char** argv) {
Flags::Parser parser; Flags::Parser parser;
int* port = parser.add<int>("port", "tcp port number", false, 8080); int* port = parser.add<int>("port", "tcp port number", false, 8080);
unsigned long int* workers = parser.add<unsigned long int>("workers", "number of worker threads", false, 10); unsigned long int* workers = parser.add<unsigned long int>("workers", "number of worker threads", false, 10);
bool* help = parser.add<bool>("help", "print help", false, false); bool* help = parser.add<bool>("help", "print help", false, false);
if (!parser.parse(argc, argv) || *help) { if (!parser.parse(argc, argv) || *help) {
@ -29,14 +29,15 @@ int main(int argc, char** argv) {
server->end(); server->end();
}); });
server->add_path<Rum::HTTP::GET>("/asd", [](const Rum::HTTP::Request& req, Rum::HTTP::Response& resp) { server->add_path<Rum::HTTP::GET>("/asd(/?|/.*)", [](const Rum::HTTP::Request& req, Rum::HTTP::Response& resp) {
std::cout << "request accepted" << std::endl; std::cout << "request accepted" << std::endl;
resp.send_body("<h1>asd</h1><pre>" + (std::string)req + "</pre>"); resp.headers["Server"] = "Rum Barrel";
resp.body = "<h1>asd</h1><pre>" + (std::string)req + "</pre>";
}); });
server->add_path<Rum::HTTP::GET>("/.*", [](const Rum::HTTP::Request& req, Rum::HTTP::Response& resp) { server->add_path<Rum::HTTP::GET>("/.*", [](const Rum::HTTP::Request& req, Rum::HTTP::Response& resp) {
std::cout << "request accepted" << std::endl; std::cout << "request accepted" << std::endl;
resp.send_body("<h1>Hello World</h1><pre>" + (std::string)req + "</pre>"); resp.body = "<h1>Hello World</h1><pre>" + (std::string)req + "</pre>";
}); });
server->listen(); server->listen();