Cookie and Header parsing
This commit is contained in:
parent
c567bb2447
commit
21afe9ca13
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "method.h"
|
||||
#include "request.h"
|
||||
#include "response.h"
|
||||
#include "server.h"
|
||||
#include <rum/http/method.h>
|
||||
#include <rum/http/request.h>
|
||||
#include <rum/http/response.h>
|
||||
#include <rum/http/server.h>
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Rum::HTTP {
|
||||
|
||||
enum Method { GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE };
|
||||
|
@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <rum/http/method.h>
|
||||
#include <rum/http/uri.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "method.h"
|
||||
#include "rum/http/uri.h"
|
||||
|
||||
namespace Rum::HTTP {
|
||||
class Request {
|
||||
@ -12,12 +12,16 @@ class Request {
|
||||
Request(Method method, const URI& uri) : method(method), uri(uri), body("") {}
|
||||
|
||||
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; }
|
||||
std::string get_header(std::string name) const { return headers.at(name); }
|
||||
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; }
|
||||
void set_body(std::string body) { this->body = body; }
|
||||
|
||||
@ -29,6 +33,7 @@ class Request {
|
||||
Method method;
|
||||
URI uri;
|
||||
std::map<std::string, std::string> headers;
|
||||
std::map<std::string, std::string> cookies;
|
||||
std::string body;
|
||||
};
|
||||
} // namespace Rum::HTTP
|
@ -1,22 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace Rum::HTTP {
|
||||
class Response {
|
||||
private:
|
||||
const int client_sock;
|
||||
bool sent_header;
|
||||
bool sent_body;
|
||||
bool closed;
|
||||
unsigned int code;
|
||||
|
||||
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();
|
||||
|
||||
void send_header(const std::string& name, const std::string& value);
|
||||
void send_body(const std::string& value);
|
||||
void set_code(unsigned int code) { this->code = code; }
|
||||
void set_code(unsigned int code) {
|
||||
if (code >= 100 && code < 600)
|
||||
this->code = code;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> cookies;
|
||||
std::map<std::string, std::string> headers;
|
||||
std::string body;
|
||||
};
|
||||
} // namespace Rum::HTTP
|
@ -7,9 +7,9 @@
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
#include <vector>
|
||||
#include "../tcp/server.h"
|
||||
#include "request.h"
|
||||
#include "response.h"
|
||||
#include <rum/tcp/server.h>
|
||||
#include <rum/http/request.h>
|
||||
#include <rum/http/response.h>
|
||||
|
||||
#define DEFAULT_WORKER_COUNT 10
|
||||
#define DEFAULT_BUFFER_SIZE 8196
|
||||
|
@ -29,6 +29,9 @@ class URI {
|
||||
const std::map<std::string, std::string>& get_parameters() const { return parameters; }
|
||||
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;
|
||||
};
|
||||
} // namespace Rum::HTTP
|
@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "error.h"
|
||||
#include "server.h"
|
||||
#include "utility.h"
|
||||
#include <rum/tcp/error.h>
|
||||
#include <rum/tcp/server.h>
|
||||
#include <rum/tcp/utility.h>
|
@ -1,8 +1,8 @@
|
||||
#include <rum/http/method.h>
|
||||
#include <rum/http/request.h>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "rum/http/method.h"
|
||||
|
||||
namespace Rum::HTTP {
|
||||
|
||||
@ -16,7 +16,7 @@ Request::operator std::string() const {
|
||||
"\tmethod: {}\n"
|
||||
"\t{}\n"
|
||||
"\theaders: \n{}"
|
||||
"\tbody: \n'{}'"
|
||||
"\tbody: '{}'\n"
|
||||
"}}",
|
||||
std::make_format_args(to_string(method), (std::string)uri, headers_string, body));
|
||||
}
|
||||
|
@ -1,72 +1,73 @@
|
||||
#include <rum/http/response.h>
|
||||
#include <rum/tcp/error.h>
|
||||
#include <sys/socket.h>
|
||||
#include <format>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Rum::HTTP {
|
||||
std::string to_string(int code) {
|
||||
static const std::map<int, std::string> codes{{100, "Continue"},
|
||||
{101, "Switching Protocols"},
|
||||
{102, "Processing"},
|
||||
{103, "Early Hints"},
|
||||
{200, "OK"},
|
||||
{201, "Created"},
|
||||
{202, "Accepted"},
|
||||
{203, "Non-Authoritative Information"},
|
||||
{204, "No Content"},
|
||||
{205, "Reset Content"},
|
||||
{206, "Partial Content"},
|
||||
{207, "Multi-Status"},
|
||||
{208, "Already Reported"},
|
||||
{226, "IM Used"},
|
||||
{300, "Multiple Choices"},
|
||||
{301, "Moved Permanently"},
|
||||
{302, "Found"},
|
||||
{303, "See Other"},
|
||||
{304, "Not Modified"},
|
||||
{307, "Temporary Redirect"},
|
||||
{308, "Permanent Redirect"},
|
||||
{400, "Bad Request"},
|
||||
{401, "Unauthorized"},
|
||||
{402, "Payment Required"},
|
||||
{403, "Forbidden"},
|
||||
{404, "Not Found"},
|
||||
{405, "Method Not Allowed"},
|
||||
{406, "Not Acceptable"},
|
||||
{407, "Proxy Authentication Required"},
|
||||
{408, "Request Timeout"},
|
||||
{409, "Conflict"},
|
||||
{410, "Gone"},
|
||||
{411, "Length Required"},
|
||||
{412, "Precondition Failed"},
|
||||
{413, "Content Too Large"},
|
||||
{414, "URI Too Long"},
|
||||
{415, "Unsupported Media Type"},
|
||||
{416, "Range Not Satisfiable"},
|
||||
{417, "Expectation Failed"},
|
||||
{418, "I'm a teapot"},
|
||||
{421, "Misdirected Request"},
|
||||
{422, "Unprocessable Content"},
|
||||
{423, "Locked"},
|
||||
{424, "Failed Dependency"},
|
||||
{425, "Too Early"},
|
||||
{426, "Upgrade Required"},
|
||||
{428, "Precondition Required"},
|
||||
{429, "Too Many Requests"},
|
||||
{431, "Request Header Fields Too Large"},
|
||||
{451, "Unavailable For Legal Reasons"},
|
||||
{500, "Internal Server Error"},
|
||||
{501, "Not Implemented"},
|
||||
{502, "Bad Gateway"},
|
||||
{503, "Service Unavailable"},
|
||||
{504, "Gateway Timeout"},
|
||||
{505, "HTTP Version Not Supported"},
|
||||
{506, "Variant Also Negotiates"},
|
||||
{507, "Insufficient Storage"},
|
||||
{508, "Loop Detected"},
|
||||
{510, "Not Extended"},
|
||||
{511, "Network Authentication Required"}};
|
||||
{101, "Switching Protocols"},
|
||||
{102, "Processing"},
|
||||
{103, "Early Hints"},
|
||||
{200, "OK"},
|
||||
{201, "Created"},
|
||||
{202, "Accepted"},
|
||||
{203, "Non-Authoritative Information"},
|
||||
{204, "No Content"},
|
||||
{205, "Reset Content"},
|
||||
{206, "Partial Content"},
|
||||
{207, "Multi-Status"},
|
||||
{208, "Already Reported"},
|
||||
{226, "IM Used"},
|
||||
{300, "Multiple Choices"},
|
||||
{301, "Moved Permanently"},
|
||||
{302, "Found"},
|
||||
{303, "See Other"},
|
||||
{304, "Not Modified"},
|
||||
{307, "Temporary Redirect"},
|
||||
{308, "Permanent Redirect"},
|
||||
{400, "Bad Request"},
|
||||
{401, "Unauthorized"},
|
||||
{402, "Payment Required"},
|
||||
{403, "Forbidden"},
|
||||
{404, "Not Found"},
|
||||
{405, "Method Not Allowed"},
|
||||
{406, "Not Acceptable"},
|
||||
{407, "Proxy Authentication Required"},
|
||||
{408, "Request Timeout"},
|
||||
{409, "Conflict"},
|
||||
{410, "Gone"},
|
||||
{411, "Length Required"},
|
||||
{412, "Precondition Failed"},
|
||||
{413, "Content Too Large"},
|
||||
{414, "URI Too Long"},
|
||||
{415, "Unsupported Media Type"},
|
||||
{416, "Range Not Satisfiable"},
|
||||
{417, "Expectation Failed"},
|
||||
{418, "I'm a teapot"},
|
||||
{421, "Misdirected Request"},
|
||||
{422, "Unprocessable Content"},
|
||||
{423, "Locked"},
|
||||
{424, "Failed Dependency"},
|
||||
{425, "Too Early"},
|
||||
{426, "Upgrade Required"},
|
||||
{428, "Precondition Required"},
|
||||
{429, "Too Many Requests"},
|
||||
{431, "Request Header Fields Too Large"},
|
||||
{451, "Unavailable For Legal Reasons"},
|
||||
{500, "Internal Server Error"},
|
||||
{501, "Not Implemented"},
|
||||
{502, "Bad Gateway"},
|
||||
{503, "Service Unavailable"},
|
||||
{504, "Gateway Timeout"},
|
||||
{505, "HTTP Version Not Supported"},
|
||||
{506, "Variant Also Negotiates"},
|
||||
{507, "Insufficient Storage"},
|
||||
{508, "Loop Detected"},
|
||||
{510, "Not Extended"},
|
||||
{511, "Network Authentication Required"}};
|
||||
|
||||
try {
|
||||
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() {
|
||||
if (!sent_header && !closed) {
|
||||
std::string resp("HTTP/1.1 " + std::to_string(code) + " " + to_string(code));
|
||||
send(client_sock, resp.c_str(), resp.size(), 0);
|
||||
std::string headers_string;
|
||||
for (auto header : headers) {
|
||||
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
|
@ -1,6 +1,6 @@
|
||||
#include "rum/tcp/server.h"
|
||||
#include <rum/http/server.h>
|
||||
#include <rum/tcp/error.h>
|
||||
#include <rum/tcp/server.h>
|
||||
#include <rum/tcp/utility.h>
|
||||
#include <iostream>
|
||||
#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);
|
||||
switch (recieved) {
|
||||
case TCP::Error::CLOSED:
|
||||
std::cout << address << ": connection closed" << std::endl;
|
||||
std::cerr << address << ": connection closed" << std::endl;
|
||||
return;
|
||||
case TCP::Error::UNKNOWN:
|
||||
std::cout << "socket error" << std::endl;
|
||||
std::cerr << "socket error" << std::endl;
|
||||
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::smatch match;
|
||||
if (std::regex_match(message.cbegin(), message.cbegin() + message.find("\r\n"), match, method_regex)) {
|
||||
// create request object
|
||||
request = Request(string_to_method(match.str(1)), URI("http://0.0.0.0:" + std::to_string(TCP::Server::get_port()) + match.str(2)));
|
||||
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)));
|
||||
} catch (std::exception&) {
|
||||
return;
|
||||
}
|
||||
|
||||
message = message.substr(message.find("\r\n"));
|
||||
stage = HEADER;
|
||||
} else {
|
||||
@ -127,7 +131,29 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b
|
||||
std::regex 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++) {
|
||||
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"));
|
||||
|
||||
@ -143,10 +169,9 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << request << std::endl;
|
||||
|
||||
try {
|
||||
Response resp(client_sock);
|
||||
resp.headers["Content-type"] = "text/html";
|
||||
|
||||
bool found = false;
|
||||
|
||||
@ -160,7 +185,7 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b
|
||||
|
||||
if (!found) {
|
||||
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 (TCP::Error) {
|
||||
|
@ -4,10 +4,6 @@
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
#define MATCH(var, i) \
|
||||
if (match[i].matched) \
|
||||
var = match.str(i);
|
||||
|
||||
namespace Rum::HTTP {
|
||||
|
||||
URI::operator std::string() const {
|
||||
@ -38,6 +34,10 @@ URI::URI(const std::string& uri) {
|
||||
std::cout << uri << std::endl;
|
||||
|
||||
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(user, 4)
|
||||
MATCH(password, 6)
|
||||
|
@ -10,7 +10,7 @@ int main(int argc, char** argv) {
|
||||
Flags::Parser parser;
|
||||
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);
|
||||
|
||||
|
||||
bool* help = parser.add<bool>("help", "print help", false, false);
|
||||
|
||||
if (!parser.parse(argc, argv) || *help) {
|
||||
@ -29,14 +29,15 @@ int main(int argc, char** argv) {
|
||||
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;
|
||||
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) {
|
||||
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();
|
||||
|
Loading…
Reference in New Issue
Block a user