diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ea241d..0671483 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,5 +4,5 @@ project(frnetlib) include_directories(include) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -m64 -fPIC -pthread") -set(SOURCE_FILES main.cpp src/TcpSocket.cpp include/TcpSocket.h src/TcpListener.cpp include/TcpListener.h src/Socket.cpp include/Socket.h src/Packet.cpp include/Packet.h include/NetworkEncoding.h src/SocketSelector.cpp include/SocketSelector.h src/HttpSocket.cpp include/HttpSocket.h src/HttpRequest.cpp include/HttpRequest.h src/HttpResponse.cpp include/HttpResponse.h) +set(SOURCE_FILES main.cpp src/TcpSocket.cpp include/TcpSocket.h src/TcpListener.cpp include/TcpListener.h src/Socket.cpp include/Socket.h src/Packet.cpp include/Packet.h include/NetworkEncoding.h src/SocketSelector.cpp include/SocketSelector.h src/HttpSocket.cpp include/HttpSocket.h src/HttpRequest.cpp include/HttpRequest.h src/HttpResponse.cpp include/HttpResponse.h src/Http.cpp include/Http.h) add_executable(frnetlib ${SOURCE_FILES}) \ No newline at end of file diff --git a/include/Http.h b/include/Http.h new file mode 100644 index 0000000..c3e447e --- /dev/null +++ b/include/Http.h @@ -0,0 +1,178 @@ +// +// Created by fred on 11/12/16. +// + +#ifndef FRNETLIB_HTTP_H +#define FRNETLIB_HTTP_H +#include +#include +#include + +namespace fr +{ + class Http + { + public: + enum RequestType + { + Unknown = 0, + Get = 1, + Post = 2, + RequestTypeCount = 3, + }; + enum RequestStatus + { + Ok = 200, + BadRequest = 400, + Forbidden = 403, + NotFound = 404, + ImATeapot = 418, + InternalServerError = 500, + }; + + Http(); + + /*! + * Parse a raw request or response from a string + * into the object. + * + * @param data The request/response to parse + */ + virtual void parse(const std::string &data)=0; + + /*! + * Constructs a HTTP request/response to send. + * + * @return The HTTP request + */ + virtual std::string construct() const=0; + + /*! + * Gets the request type (post, get etc) + * + * @return The request type + */ + RequestType get_type() const; + + /*! + * Sets the request type (post, get etc) + * + * @param type The type of request to set it to + */ + void set_type(RequestType type); + + /*! + * Access a header + * + * @param key The name of the header data to access/create + * @return The header data. + */ + std::string &operator[](const std::string &key); + + /*! + * Sets the request body + * + * @param body_ The request body + */ + void set_body(const std::string &body_); + + /*! + * Clear current request data + */ + void clear(); + + /*! + * Returns a reference to a get variable. + * Can be used to either set/get the value. + * If the key does not exist, then it will be + * created and an empty value will be returned. + * + * @param key The name of the GET variable + * @return A reference to the GET variable + */ + std::string &get(const std::string &key); + + /*! + * Returns a reference to a POST variable. + * Can be used to either set/get the value. + * If the key does not exist, then it will be + * created and an empty value will be returned. + * + * @param key The name of the POST variable + * @return A reference to the POST variable + */ + std::string &post(const std::string &key); + + /*! + * Checks to see if a given GET variable exists + * + * @param key The name of the GET variable + * @return True if it does. False otherwise. + */ + bool get_exists(const std::string &key) const; + + /*! + * Checks to see if a given POST variable exists + * + * @param key The name of the POST variable + * @return True if it does. False otherwise. + */ + bool post_exists(const std::string &key) const; + + /*! + * Returns the requested URI + * + * @return The URI + */ + const std::string &get_uri() const; + + /*! + * Sets the response status (400, 200, etc) + * + * @param status The status to send back + */ + void set_status(RequestStatus status); + + /*! + * Gets the reponse status + * + * @return The status + */ + RequestStatus get_status(); + + /*! + * Sets the request URI. + * + * @param str What to set the URI to. + */ + void set_uri(const std::string &str); + + /*! + * Gets the body of the HTTP request + * + * @return The request body + */ + const std::string &get_body() const; + + protected: + /*! + * Splits a string by new line. Ignores escaped \n's + * + * @return The split string + */ + std::vector split_string(const std::string &str); + + std::string request_type_to_string(RequestType type) const; + + //Other request info + std::unordered_map headers; + std::unordered_map get_variables; + std::string body; + RequestType request_type; + std::string uri; + RequestStatus status; + }; +} + + +#endif //FRNETLIB_HTTP_H diff --git a/include/HttpRequest.h b/include/HttpRequest.h index c9fa641..ddc00b7 100644 --- a/include/HttpRequest.h +++ b/include/HttpRequest.h @@ -8,185 +8,30 @@ #include #include #include "TcpSocket.h" +#include "Http.h" namespace fr { - class HttpRequest + class HttpRequest : public Http { public: - enum RequestType - { - Unknown = 0, - Get = 1, - Post = 2, - RequestTypeCount = 3, - }; - enum RequestStatus - { - Ok = 200, - BadRequest = 400, - Forbidden = 403, - NotFound = 404, - ImATeapot = 418, - InternalServerError = 500, - }; - //Constructors - HttpRequest(); - HttpRequest(HttpRequest &&other)=default; + HttpRequest() = default; + HttpRequest(HttpRequest &&other) = default; /*! - * Parses a browser request + * Parse a HTTP response. * - * @param request_data The request data itself + * @param data The HTTP response to parse */ - void parse_request(const std::string &request_data); + void parse(const std::string &data) override; /*! - * Parses a browser response + * Constructs a Http Request, ready to send. * - * @param request_data The response data itself + * @return The constructed HTTP request. */ - void parse_response(const std::string &response_data); - - /*! - * Gets the request type (post, get etc) - * - * @return The request type - */ - RequestType get_type() const; - - /*! - * Sets the request type (post, get etc) - * - * @param type The type of request to set it to - */ - void set_type(RequestType type); - - /*! - * Access a header - * - * @param key The name of the header data to access/create - * @return The header data. - */ - std::string &operator[](const std::string &key); - - /*! - * Constructs a HTTP web request from the object. - * - * @return The HTTP request - */ - std::string construct_request() const; - - /*! - * Constructs a HTTP web response from the object. - * - * @return The HTTP response. - */ - std::string construct_response() const; - - /*! - * Sets the request body - * - * @param body_ The request body - */ - void set_body(const std::string &body_); - - /*! - * Clear current request data - */ - void clear(); - - /*! - * Returns a reference to a get variable. - * Can be used to either set/get the value. - * If the key does not exist, then it will be - * created and an empty value will be returned. - * - * @param key The name of the GET variable - * @return A reference to the GET variable - */ - std::string &get(const std::string &key); - - /*! - * Returns a reference to a POST variable. - * Can be used to either set/get the value. - * If the key does not exist, then it will be - * created and an empty value will be returned. - * - * @param key The name of the POST variable - * @return A reference to the POST variable - */ - std::string &post(const std::string &key); - - /*! - * Checks to see if a given GET variable exists - * - * @param key The name of the GET variable - * @return True if it does. False otherwise. - */ - bool get_exists(const std::string &key) const; - - /*! - * Checks to see if a given POST variable exists - * - * @param key The name of the POST variable - * @return True if it does. False otherwise. - */ - bool post_exists(const std::string &key) const; - - /*! - * Returns the requested URI - * - * @return The URI - */ - const std::string &get_uri() const; - - /*! - * Sets the response status (400, 200, etc) - * - * @param status The status to send back - */ - void set_status(RequestStatus status); - - /*! - * Gets the reponse status - * - * @return The status - */ - RequestStatus get_status(); - - /*! - * Sets the request URI. - * - * @param str What to set the URI to. - */ - void set_uri(const std::string &str); - - /*! - * Gets the body of the HTTP request - * - * @return The request body - */ - const std::string &get_body() const; - - private: - /*! - * Splits a string by new line. Ignores escaped \n's - * - * @return The split string - */ - std::vector split_string(const std::string &str); - - std::string request_type_to_string(RequestType type) const; - - //Other request info - std::unordered_map headers; - std::unordered_map get_variables; - std::string body; - RequestType request_type; - std::string uri; - RequestStatus status; + std::string construct() const override; }; } diff --git a/include/HttpResponse.h b/include/HttpResponse.h index a94fb9f..573261c 100644 --- a/include/HttpResponse.h +++ b/include/HttpResponse.h @@ -5,11 +5,34 @@ #ifndef FRNETLIB_HTTPRESPONSE_H #define FRNETLIB_HTTPRESPONSE_H +#include +#include +#include +#include "Http.h" -class HttpResponse +namespace fr { + class HttpResponse : public Http + { + public: + //Constructors + HttpResponse() = default; + HttpResponse(HttpResponse &&other) = default; -}; + /*! + * Parse a HTTP response. + * + * @param data The HTTP response to parse + */ + void parse(const std::string &data) override; + /*! + * Constructs a HttpResponse, ready to send. + * + * @return The constructed HTTP response. + */ + std::string construct() const override; + }; +} #endif //FRNETLIB_HTTPRESPONSE_H diff --git a/include/HttpSocket.h b/include/HttpSocket.h index ab2f9ce..342268a 100644 --- a/include/HttpSocket.h +++ b/include/HttpSocket.h @@ -6,7 +6,7 @@ #define FRNETLIB_HTTPSOCKET_H #include "TcpSocket.h" -#include "HttpRequest.h" +#include "Http.h" namespace fr { @@ -14,20 +14,12 @@ namespace fr { public: /*! - * Sends a HTTP request to the connected socket. + * Receives a HTTP request from the connected socket * - * @param request The request to send + * @param request Where to store the received request. * @return The status of the operation. */ - Socket::Status receive_request(HttpRequest &request); - - /*! - * Sends a HTTP response to the connected socket. - * - * @param request The response to send - * @return The status of the operation. - */ - Socket::Status receive_response(HttpRequest &response); + Socket::Status receive(Http &request); /*! * Sends a HTTP request to the connected socket. @@ -35,15 +27,7 @@ namespace fr * @param request The HTTP request to send. * @return The status of the operation. */ - Socket::Status send_request(const HttpRequest &request); - - /*! - * Sends a HTTP response to the connected socket. - * - * @param request The HTTP response to send. - * @return The status of the operation. - */ - Socket::Status send_response(const HttpRequest &request); + Socket::Status send(const Http &request); }; } diff --git a/main.cpp b/main.cpp index a0af946..6bfaab6 100644 --- a/main.cpp +++ b/main.cpp @@ -6,7 +6,9 @@ #include #include #include -#include +#include "HttpSocket.h" +#include "HttpRequest.h" +#include "HttpResponse.h" void server() { @@ -52,17 +54,17 @@ void server() { //This client has sent a HTTP request, so receive_request it fr::HttpRequest request; - if((*iter)->receive_request(request) == fr::Socket::Success) + if((*iter)->receive(request) == fr::Socket::Success) { //Print to the console what we've been requested for std::cout << "Requested: " << request.get_uri() << std::endl; //Construct a response - request.clear(); - request.set_body("

Hello, World!

"); + fr::HttpResponse response; + response.set_body("

Hello, World!

"); //Send the response, and close the connection - (*iter)->send_response(request); + (*iter)->send(response); (*iter)->close(); } else @@ -93,13 +95,14 @@ void client() fr::HttpRequest request; request.get("name") = "fred"; - if(socket.send_request(request) != fr::Socket::Success) + if(socket.send(request) != fr::Socket::Success) std::cout << "Failed to send HTTP request to server!" << std::endl; - if(socket.receive_response(request) != fr::Socket::Success) + fr::HttpResponse response; + if(socket.receive(response) != fr::Socket::Success) std::cout << "Failed to receive HTTP response from the server!" << std::endl; - std::cout << "Got page body: " << request.get_body() << std::endl; + std::cout << "Got page body: " << response.get_body() << std::endl; return; } diff --git a/src/Http.cpp b/src/Http.cpp new file mode 100644 index 0000000..606b74e --- /dev/null +++ b/src/Http.cpp @@ -0,0 +1,116 @@ +// +// Created by fred on 11/12/16. +// + +#include +#include "Http.h" + +namespace fr +{ + const static std::string request_type_strings[Http::RequestType::RequestTypeCount] = {"UNKNOWN", "GET", "POST"}; + + Http::Http() + { + clear(); + } + + Http::RequestType Http::get_type() const + { + return request_type; + } + + std::string &Http::operator[](const std::string &key) + { + return headers[key]; + } + + std::vector Http::split_string(const std::string &str) + { + char last_character = '\0'; + size_t line_start = 0; + std::vector result; + + for(size_t a = 0; a < str.size(); a++) + { + if(str[a] == '\n' && last_character != '\\') + { + result.emplace_back(str.substr(line_start, a - line_start)); + line_start = a + 1; + } + last_character = str[a]; + } + return result; + } + + void Http::set_body(const std::string &body_) + { + body = body_; + } + + void Http::clear() + { + headers.clear(); + body.clear(); + get_variables.clear(); + uri = "/"; + status = Ok; + request_type = Get; + } + + std::string &Http::get(const std::string &key) + { + return get_variables[key]; + } + + std::string &Http::post(const std::string &key) + { + return headers[key]; + } + + bool Http::get_exists(const std::string &key) const + { + return get_variables.find(key) != get_variables.end(); + } + + bool Http::post_exists(const std::string &key) const + { + return headers.find(key) != headers.end(); + } + + const std::string &Http::get_uri() const + { + return uri; + } + + void Http::set_status(RequestStatus status_) + { + status = status_; + } + + Http::RequestStatus Http::get_status() + { + return status; + } + + void Http::set_uri(const std::string &str) + { + uri = str; + } + + std::string Http::request_type_to_string(RequestType type) const + { + if(type >= RequestType::RequestTypeCount) + return request_type_strings[0]; + return request_type_strings[type]; + } + + void Http::set_type(Http::RequestType type) + { + request_type = type; + } + + const std::string &Http::get_body() const + { + return body; + } +} \ No newline at end of file diff --git a/src/HttpRequest.cpp b/src/HttpRequest.cpp index 3128460..212dc6a 100644 --- a/src/HttpRequest.cpp +++ b/src/HttpRequest.cpp @@ -6,14 +6,7 @@ namespace fr { - const static std::string request_type_strings[HttpRequest::RequestType::RequestTypeCount] = {"UNKNOWN", "GET", "POST"}; - - HttpRequest::HttpRequest() - { - clear(); - } - - void HttpRequest::parse_request(const std::string &request_data) + void HttpRequest::parse(const std::string &request_data) { //Warning: Horrible string parsing code @@ -112,86 +105,7 @@ namespace fr return; } - void HttpRequest::parse_response(const std::string &response_data) - { - //Warning: Horrible string parsing code - std::cout << "Parsing response: " << std::endl << response_data << std::endl; - //Clear old headers/data - clear(); - - //Make sure there's actual request data to read - if(response_data.empty()) - return; - - //Split by new lines - std::vector lines = split_string(response_data); - if(lines.empty()) - return; - - //Extract request get_type - if(lines[0].find("GET") != std::string::npos) - request_type = RequestType::Get; - else if(lines[0].find("POST") != std::string::npos) - request_type = RequestType::Post; - else - request_type = RequestType::Unknown; - - //Extract headers - size_t a; - for(a = 1; a < lines.size(); a++) - { - //New line indicates headers have ended - if(lines[a].empty() || lines[a].size() <= 2) - break; - - //Find the colon separating the header name and header data - auto colon_iter = lines[a].find(":"); - if(colon_iter == std::string::npos) - continue; - - //Store the header - std::string header_name = lines[a].substr(0, colon_iter); - std::string header_content = lines[a].substr(colon_iter + 2, lines[a].size () - colon_iter - 3); - headers.emplace(header_name, header_content); - } - - //Store request body - for(; a < lines.size(); a++) - { - body += lines[a] + "\n"; - } - return; - } - - HttpRequest::RequestType HttpRequest::get_type() const - { - return request_type; - } - - std::string &HttpRequest::operator[](const std::string &key) - { - return headers[key]; - } - - std::vector HttpRequest::split_string(const std::string &str) - { - char last_character = '\0'; - size_t line_start = 0; - std::vector result; - - for(size_t a = 0; a < str.size(); a++) - { - if(str[a] == '\n' && last_character != '\\') - { - result.emplace_back(str.substr(line_start, a - line_start)); - line_start = a + 1; - } - last_character = str[a]; - } - return result; - } - - std::string HttpRequest::construct_request() const + std::string HttpRequest::construct() const { //Add HTTP header std::string request = request_type_to_string(request_type) + " " + uri + " HTTP/1.1\r\n"; @@ -214,102 +128,4 @@ namespace fr request += body + "\r\n"; return request; } - - std::string HttpRequest::construct_response() const - { - //Add HTTP header - std::string response = "HTTP/1.1 " + std::to_string(status) + " \r\n"; - - //Add the headers to the response - for(const auto &header : headers) - { - std::string data = header.first + ": " + header.second + "\r\n"; - response += data; - } - - //Add in required headers if they're missing - if(headers.find("Connection") == headers.end()) - response += "Connection: close\r\n"; - if(headers.find("Content-type") == headers.end()) - response += "Content-type: text/html\r\n"; - - //Add in space - response += "\r\n"; - - //Add in the body - response += body + "\r\n"; - return response; - } - - void HttpRequest::set_body(const std::string &body_) - { - body = body_; - } - - void HttpRequest::clear() - { - headers.clear(); - body.clear(); - get_variables.clear(); - uri = "/"; - status = Ok; - request_type = Get; - } - - std::string &HttpRequest::get(const std::string &key) - { - return get_variables[key]; - } - - std::string &HttpRequest::post(const std::string &key) - { - return headers[key]; - } - - bool HttpRequest::get_exists(const std::string &key) const - { - return get_variables.find(key) != get_variables.end(); - } - - bool HttpRequest::post_exists(const std::string &key) const - { - return headers.find(key) != headers.end(); - } - - const std::string &HttpRequest::get_uri() const - { - return uri; - } - - void HttpRequest::set_status(RequestStatus status_) - { - status = status_; - } - - HttpRequest::RequestStatus HttpRequest::get_status() - { - return status; - } - - void HttpRequest::set_uri(const std::string &str) - { - uri = str; - } - - std::string HttpRequest::request_type_to_string(RequestType type) const - { - if(type >= RequestType::RequestTypeCount) - return request_type_strings[0]; - return request_type_strings[type]; - } - - void HttpRequest::set_type(HttpRequest::RequestType type) - { - request_type = type; - } - - const std::string &HttpRequest::get_body() const - { - return body; - } } \ No newline at end of file diff --git a/src/HttpResponse.cpp b/src/HttpResponse.cpp index 0de1985..cc3b8bf 100644 --- a/src/HttpResponse.cpp +++ b/src/HttpResponse.cpp @@ -2,4 +2,83 @@ // Created by fred on 10/12/16. // +#include #include "HttpResponse.h" + +namespace fr +{ + void HttpResponse::parse(const std::string &response_data) + { + //Clear old headers/data + clear(); + + //Make sure there's actual request data to read + if(response_data.empty()) + return; + + //Split by new lines + std::vector lines = split_string(response_data); + if(lines.empty()) + return; + + //Extract request get_type + if(lines[0].find("GET") != std::string::npos) + request_type = RequestType::Get; + else if(lines[0].find("POST") != std::string::npos) + request_type = RequestType::Post; + else + request_type = RequestType::Unknown; + + //Extract headers + size_t a; + for(a = 1; a < lines.size(); a++) + { + //New line indicates headers have ended + if(lines[a].empty() || lines[a].size() <= 2) + break; + + //Find the colon separating the header name and header data + auto colon_iter = lines[a].find(":"); + if(colon_iter == std::string::npos) + continue; + + //Store the header + std::string header_name = lines[a].substr(0, colon_iter); + std::string header_content = lines[a].substr(colon_iter + 2, lines[a].size () - colon_iter - 3); + headers.emplace(header_name, header_content); + } + + //Store request body + for(; a < lines.size(); a++) + { + body += lines[a] + "\n"; + } + return; + } + + std::string HttpResponse::construct() const + { + //Add HTTP header + std::string response = "HTTP/1.1 " + std::to_string(status) + " \r\n"; + + //Add the headers to the response + for(const auto &header : headers) + { + std::string data = header.first + ": " + header.second + "\r\n"; + response += data; + } + + //Add in required headers if they're missing + if(headers.find("Connection") == headers.end()) + response += "Connection: close\r\n"; + if(headers.find("Content-type") == headers.end()) + response += "Content-type: text/html\r\n"; + + //Add in space + response += "\r\n"; + + //Add in the body + response += body + "\r\n"; + return response; + } +} \ No newline at end of file diff --git a/src/HttpSocket.cpp b/src/HttpSocket.cpp index d4441a9..8031067 100644 --- a/src/HttpSocket.cpp +++ b/src/HttpSocket.cpp @@ -2,12 +2,13 @@ // Created by fred on 10/12/16. // +#include #include "HttpSocket.h" namespace fr { - Socket::Status HttpSocket::receive_request(HttpRequest &request) + Socket::Status HttpSocket::receive(Http &request) { //Create buffer to receive_request the request std::string buffer(2048, '\0'); @@ -20,38 +21,14 @@ namespace fr buffer.resize(received); //Parse it - request.parse_request(buffer); + request.parse(buffer); return Socket::Success; } - Socket::Status HttpSocket::receive_response(HttpRequest &response) + Socket::Status HttpSocket::send(const Http &request) { - //Create buffer to receive_request the response - std::string buffer(2048, '\0'); - - //Receive the response - size_t received; - Socket::Status status = receive_raw(&buffer[0], buffer.size(), received); - if(status != Socket::Success) - return status; - buffer.resize(received); - - //Parse it - response.parse_response(buffer); - - return Socket::Success; - } - - Socket::Status HttpSocket::send_request(const HttpRequest &request) - { - std::string data = request.construct_request(); - return send_raw(&data[0], data.size()); - } - - Socket::Status HttpSocket::send_response(const HttpRequest &request) - { - std::string data = request.construct_response(); + std::string data = request.construct(); return send_raw(&data[0], data.size()); } } \ No newline at end of file