diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e1ced4..01cab32 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) +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 include/HttpRequest.cpp include/HttpRequest.h) add_executable(frnetlib ${SOURCE_FILES}) \ No newline at end of file diff --git a/include/HttpRequest.cpp b/include/HttpRequest.cpp new file mode 100644 index 0000000..1d06b43 --- /dev/null +++ b/include/HttpRequest.cpp @@ -0,0 +1,214 @@ +// +// Created by fred on 10/12/16. +// + +#include "HttpRequest.h" + +namespace fr +{ + HttpRequest::HttpRequest() + : status(Ok) + { + } + + void HttpRequest::parse_request(const std::string &request_data) + { + //Warning: Horrible string parsing code + + //Clear old headers/data + clear(); + + //Make sure there's actual request data to read + if(request_data.empty()) + return; + + //Split by new lines + std::vector lines = split_string(request_data); + if(lines.empty()) + return; + + //Extract request 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; + + //Remove HTTP version + auto http_version = lines[0].find("HTTP"); + if(http_version != std::string::npos && http_version > 0) + lines[0].erase(http_version - 1, lines[0].size() - http_version + 1); + + //Extract URI & GET variables + auto uri_start = lines[0].find(" "); + auto uri_end = lines[0].find("?"); + if(uri_start != std::string::npos) + { + if(uri_end == std::string::npos) //If no GET arguments + { + uri = lines[0].substr(uri_start + 1, lines[0].size() - 1); + } + else //There's get arguments + { + uri = lines[0].substr(uri_start + 1, uri_end - uri_start - 1); + std::string get_lines = lines[0].substr(uri_end + 1, lines[0].size()); + std::string name_buffer, value_buffer; + + bool state = false; + for(size_t a = 0; a < get_lines.size(); a++) + { + if(get_lines[a] == '&') + { + get_variables.emplace(name_buffer, value_buffer); + name_buffer.clear(); + value_buffer.clear(); + state = false; + continue; + } + else if(get_lines[a] == '=') + { + state = true; + } + else if(state) + { + value_buffer += get_lines[a]; + } + else + { + name_buffer += get_lines[a]; + } + } + get_variables.emplace(name_buffer, value_buffer); + } + } + + //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::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::get_request() const + { + //Add HTTP header + std::string request = "HTTP/1.1 " + std::to_string(status) + " \r\n"; + + //Add the headers to the request + for(const auto &header : headers) + { + std::string data = header.first + ": " + header.second + "\r\n"; + request += data; + } + + //Add in required headers if they're missing + if(headers.find("Connection") == headers.end()) + request += "Connection: close\r\n"; + if(headers.find("Content-type") == headers.end()) + request += "Content-type: text/html\r\n"; + + //Add in space + request += "\r\n"; + + //Add in the body + request += body + "\r\n"; + return request; + } + + void HttpRequest::set_body(const std::string &body_) + { + body = body_; + } + + void HttpRequest::clear() + { + headers.clear(); + body.clear(); + get_variables.clear(); + status = Ok; + } + + 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; + } +} \ No newline at end of file diff --git a/include/HttpRequest.h b/include/HttpRequest.h new file mode 100644 index 0000000..7078e4f --- /dev/null +++ b/include/HttpRequest.h @@ -0,0 +1,156 @@ +// +// Created by fred on 10/12/16. +// + +#ifndef FRNETLIB_HTTPREQUEST_H +#define FRNETLIB_HTTPREQUEST_H +#include +#include +#include +#include "TcpSocket.h" + +namespace fr +{ + class HttpRequest + { + public: + enum RequestType + { + Unknown = 0, + Get = 1, + Post = 2, + }; + enum RequestStatus + { + Ok = 200, + BadRequest = 400, + Forbidden = 403, + NotFound = 404, + ImATeapot = 418, + InternalServerError = 500, + }; + + //Constructors + HttpRequest(); + HttpRequest(HttpRequest &&other)=default; + + /*! + * Parses a browser request + * + * @param request_data The request data itself + */ + void parse_request(const std::string &request_data); + + /*! + * Gets the request type (post, get etc) + * + * @return The request type + */ + RequestType type() const; + + /*! + * 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 get_request() 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(); + + private: + /*! + * Splits a string by new line. Ignores escaped \n's + * + * @return The split string + */ + std::vector split_string(const std::string &str); + + //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_HTTPREQUEST_H diff --git a/include/HttpSocket.h b/include/HttpSocket.h new file mode 100644 index 0000000..82f135f --- /dev/null +++ b/include/HttpSocket.h @@ -0,0 +1,35 @@ +// +// Created by fred on 10/12/16. +// + +#ifndef FRNETLIB_HTTPSOCKET_H +#define FRNETLIB_HTTPSOCKET_H + +#include "TcpSocket.h" +#include "HttpRequest.h" + +namespace fr +{ + class HttpSocket : public TcpSocket + { + public: + /*! + * Sends a HTTP request to the connected socket. + * + * @param request The request to send + * @return The status of the operation. + */ + Socket::Status receive(HttpRequest &request); + + /*! + * Sends a HTTP request to the connected socket. + * + * @param request Where to store the received request. + * @return The status of the operation. + */ + Socket::Status send(const HttpRequest &request); + }; + +} + +#endif //FRNETLIB_HTTPSOCKET_H diff --git a/main.cpp b/main.cpp index 9481b9e..ed88574 100644 --- a/main.cpp +++ b/main.cpp @@ -1,11 +1,12 @@ #include -#include -#include -#include -#include +#include "include/Packet.h" +#include "include/TcpSocket.h" +#include "include/TcpListener.h" +#include "include/SocketSelector.h" #include #include #include +#include void server() { @@ -17,7 +18,7 @@ void server() } fr::SocketSelector selector; - std::vector> clients; + std::vector> clients; selector.add(listener); @@ -25,7 +26,7 @@ void server() { if(selector.is_ready(listener)) { - clients.emplace_back(new fr::TcpSocket()); + clients.emplace_back(new fr::HttpSocket()); if(listener.accept(*clients.back()) != fr::Socket::Success) { clients.pop_back(); @@ -41,17 +42,14 @@ void server() { if(selector.is_ready(**iter)) { - std::string message(1024, '\0'); - size_t received; - if((*iter)->receive_raw(&message[0], 1024, received) == fr::Socket::Success) + fr::HttpRequest request; + if((*iter)->receive(request) == fr::Socket::Success) { + std::cout << "Requested: " << request.get_uri() << std::endl; + request.clear(); + request.set_body("

Hello, World!

"); - std::cout << (*iter)->get_remote_address() << " sent: " << message.substr(0, received) << std::endl; - - message.clear(); - message = "HTTP/1.1 " + std::to_string(200) + " \r\nConnection: close\r\nContent-type: text/html\r\n\r\n

Hey

\r\n"; - (*iter)->send_raw(&message[0], message.size()); - std::cout << "Sent" << std::endl; + (*iter)->send(request); (*iter)->close(); } else diff --git a/src/HttpSocket.cpp b/src/HttpSocket.cpp new file mode 100644 index 0000000..b0636b0 --- /dev/null +++ b/src/HttpSocket.cpp @@ -0,0 +1,33 @@ +// +// Created by fred on 10/12/16. +// + +#include "HttpSocket.h" + +namespace fr +{ + + Socket::Status HttpSocket::receive(HttpRequest &request) + { + //Create buffer to receive the request + std::string buffer(2048, '\0'); + + //Receive the request + size_t received; + Socket::Status status = receive_raw(&buffer[0], buffer.size(), received); + if(status != Socket::Success) + return status; + buffer.resize(received); + + //Parse it + request.parse_request(buffer); + + return Socket::Success; + } + + Socket::Status HttpSocket::send(const HttpRequest &request) + { + std::string data = request.get_request(); + return send_raw(&data[0], data.size()); + } +} \ No newline at end of file diff --git a/src/TcpSocket.cpp b/src/TcpSocket.cpp index 90b86c2..804b8c9 100644 --- a/src/TcpSocket.cpp +++ b/src/TcpSocket.cpp @@ -193,6 +193,7 @@ namespace fr if(c == nullptr) return Socket::Status::Error; is_connected = true; + return Socket::Status::Success; } } \ No newline at end of file