Commit 6d9c0fb5 authored by James Allenby's avatar James Allenby
Browse files

Major changes to program structure and functionality

parent 6e8fc080
......@@ -7,6 +7,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Werror")
include_directories("include")
add_library(${PROJECT_NAME} "jampserver.cc" "httpresponse.cc" "httprequest.cc")
add_library(${PROJECT_NAME} "JAMP.cpp" "HTTPRequest.cpp" "HTTPResponse.cpp")
add_executable("WebServer" "main.cc")
target_link_libraries("WebServer" ${PROJECT_NAME})
#include "HTTPRequest.hpp"
#include <iostream>
#include <algorithm>
JAMP::HTTPRequest::HTTPRequest(std::string request)
{
Deserialize(request);
}
void JAMP::HTTPRequest::Deserialize(std::string request)
{
// Storage before committing changes
JAMP::Method method;
std::string uri;
std::string http_version;
std::multimap<std::string, std::string> fields;
std::string message;
// Find the position of where the header ends,
// if it doesn't then throw an error.
const size_t header_end = request.find("\r\n\r\n");
if (header_end == std::string::npos)
throw "Invalid HTTP request";
// Find the length of the start line and make sure
// it exists before continuing to process.
const size_t start_line_length = request.find(CRLF);
if (start_line_length == std::string::npos)
throw "Unable to find start line";
{ // Start of start line processing
// Obtain the request method by searching for the first space,
// throw an error if we overun the start line length.
const size_t req_method_end_pos = request.find(" ");
if (req_method_end_pos > start_line_length || req_method_end_pos == std::string::npos)
throw "Unable to find request method";
// Try to resolve the request method as a JAMP method data-type
// Clear it after we are done with it.
std::string req_method = request.substr(0, req_method_end_pos);
if (req_method == "GET") method = JAMP::Method::GET;
else if (req_method == "HEAD") method = JAMP::Method::HEAD;
else if (req_method == "POST") method = JAMP::Method::POST;
else if (req_method == "PUT") method = JAMP::Method::PUT;
else if (req_method == "DELETE") method = JAMP::Method::DELETE;
else if (req_method == "CONNECT") method = JAMP::Method::CONNECT;
else if (req_method == "OPTIONS") method = JAMP::Method::OPTIONS;
else if (req_method == "TRACE") method = JAMP::Method::TRACE;
else throw "Unknown request method";
req_method.clear();
// Try to find the end position of the request URI
// Search for it one character after the request
// method end position.
size_t req_uri_end_pos = request.find(SP, req_method_end_pos + SP.size());
if (req_method_end_pos > start_line_length || req_method_end_pos == std::string::npos)
throw "Unable to find request URI";
// Try to resolve the URI into a string
uri = request.substr(req_method_end_pos + SP.size(), req_uri_end_pos - (req_method_end_pos + SP.size()));
// Try to find the end position for the HTTP version
// Search for it using the same method as above.
size_t req_httpver_end_pos = request.find(CRLF, req_uri_end_pos + 1);
if (req_method_end_pos > start_line_length || req_method_end_pos == std::string::npos)
throw "Unable to find request HTTP version";
// Try to resolve the HTTP version into a string
http_version = request.substr(req_uri_end_pos + SP.size(), req_httpver_end_pos - (req_uri_end_pos + SP.size()));
} // End of start line processing
// Start current position where the first header set would
// be available.
size_t current_position = start_line_length + CRLF.size();
// If we have not reached the end of the header, then lets
// process request headers.
while (current_position < header_end)
{
// Find the end of this header field and store that position
size_t req_hdr_field_end = request.find(CRLF, current_position);
// Find where the header delimiter exists, check if it does not
// exist, or if it overruns the current header field length.
size_t req_hdr_field_delimiter_pos = request.find(":", current_position);
if (req_hdr_field_delimiter_pos == std::string::npos || req_hdr_field_delimiter_pos > req_hdr_field_end)
throw "Unable to read header field delimiter";
// Copy the header key into a variable
std::string key = request.substr(current_position, req_hdr_field_delimiter_pos - current_position);
// Check if next character is a space, just bump up a position
// if true.
if (request[++req_hdr_field_delimiter_pos] == ' ')
{
std::cerr << "bumping a position..." << std::endl;
++req_hdr_field_delimiter_pos;
}
std::string value = request.substr(req_hdr_field_delimiter_pos, req_hdr_field_end - req_hdr_field_delimiter_pos);
fields.emplace(key, value);
// Set current position to field length + 2 to account for CRLF sequence
current_position = req_hdr_field_end + CRLF.size();
}
// Commit the changes to the object
m_method = method;
m_uri = uri;
m_http_version = http_version;
m_fields = fields;
}
#include "httpresponse.h"
#include "HTTPResponse.hpp"
#include <string>
#include <sstream>
#include <algorithm>
#include <unistd.h>
static std::string CRLF = "\r\n";
JAMP::HTTPResponse::HTTPResponse(int connfd)
{
m_connection_fd = connfd;
}
// Construct a response object with a connection file descriptor
JAMP::HTTPResponse::HTTPResponse(int connection_fd) : m_connection_fd(connection_fd) {}
// Set the response code of the response
void JAMP::HTTPResponse::SetResponseCode(JAMP::Code response_code)
{
this->m_response_code = response_code;
return;
m_response_code = response_code;
}
void JAMP::HTTPResponse::AddHeader(std::string key, std::string value)
// Append a header to the response
void JAMP::HTTPResponse::AddHeader(const std::string& key, const std::string& value)
{
m_fields.emplace(key, value);
}
......@@ -28,24 +26,22 @@ void JAMP::HTTPResponse::Write(std::string text)
m_message += text;
}
std::string JAMP::HTTPResponse::Serialize()
const std::string JAMP::HTTPResponse::Serialize()
{
std::stringstream response_stream;
// Write the start line
std::string ret = "HTTP/1.1 ";
ret += JAMP::StringCode.at(m_response_code) + " ";
ret += JAMP::ReasonPhrase.at(m_response_code) + CRLF;
response_stream << "HTTP/1.1 " << JAMP::StringCode.at(m_response_code) + " " << JAMP::ReasonPhrase.at(m_response_code) + CRLF;
// Write the headers
std::for_each(m_fields.begin(), m_fields.end(), [&ret](auto field){
ret += field.first + ": " + field.second + CRLF;
std::for_each(m_fields.begin(), m_fields.end(), [&response_stream](auto field){
response_stream << field.first << ": " << field.second << CRLF;
});
ret += CRLF;
// Write the message
ret += m_message;
// End the header and append the message
response_stream << CRLF << m_message;
return ret;
return response_stream.str();
}
// Write to file descriptor and close server
......
#include "jampserver.hh"
#include "JAMP.hpp"
#include <iostream>
#include <functional>
#include <sstream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include "httprequest.hh"
#include "httpresponse.h"
#include "HTTPRequest.hpp"
#include "HTTPResponse.hpp"
JAMP::Server::Server()
{
......@@ -67,22 +68,23 @@ void JAMP::Server::All(std::string uri, HTTPCallback callback)
try
{
std::cout << "URI: " << request.GetURI() << std::endl
<< "Len: " << request.GetURI().size() << std::endl;
std::cout << "URI: " << request.URI() << std::endl
<< "Len: " << request.URI().size() << std::endl;
uri_map.at(request.GetURI())(request, response);
uri_map.at(request.URI())(request, response);
}
catch(std::exception e)
catch(std::exception& e)
{
response.SetResponseCode(JAMP::Code::HTTP_404);
std::string e_msg("<html>"
"<body>"
"<h1>404 - Not Found</h1>"
"<hr>"
"<p>Please try another resource</p>"
"</body>"
"</html>");
response.Write(e_msg);
std::stringstream e_msg;
e_msg << "<html>"
"<body>"
"<h1>404 - Not Found</h1>"
"<hr>"
"<p>Please try another resource</p>"
"</body>"
"</html>";
response.Write(e_msg.str());
response.Close();
}
......
#include "httprequest.hh"
#include <iostream>
#include <algorithm>
JAMP::HTTPRequest::HTTPRequest(std::string request)
{
Deserialize(request);
}
std::string JAMP::HTTPRequest::GetURI()
{
return m_uri;
}
void JAMP::HTTPRequest::Deserialize(std::string in_request)
{
JAMP::Method method;
std::string uri;
std::string http_version;
std::multimap<std::string, std::string> fields;
std::string message;
constexpr size_t header_start = 0;
const size_t start_line_end = in_request.find("\r\n");
const size_t header_fields_start = start_line_end + 2;
const size_t header_end = in_request.find("\r\n\r\n", header_start);
const size_t message_body_start = header_end + 4;
const size_t message_body_end = in_request.size();
// A HTTP request MUST have a header end position
if (header_end == std::string::npos)
throw "Invalid HTTP request";
// A HTTP request MUST have at least 1 header provided
if (start_line_end == header_end)
throw "Invalid HTTP request";
{ // Process HTTP Start Line
// Copy the entire start line into a string
std::string start_line = in_request.substr(0, start_line_end);
// Find the position of the end of method
size_t method_end_position = start_line.find(" ", 0);
if (method_end_position == std::string::npos)
throw "Malformed start line";
// Find the position of the end of URI
size_t uri_end_position = start_line.find(" ", method_end_position + 1);
if (uri_end_position == std::string::npos)
throw "Malformed start line";
// Store the request method as string
std::string in_method = start_line.substr(0, method_end_position);
// Copy the request method
if (in_method == "GET") method = JAMP::Method::GET;
else if (in_method == "HEAD") method = JAMP::Method::HEAD;
else if (in_method == "POST") method = JAMP::Method::POST;
else if (in_method == "PUT") method = JAMP::Method::PUT;
else if (in_method == "DELETE") method = JAMP::Method::DELETE;
else if (in_method == "CONNECT") method = JAMP::Method::CONNECT;
else if (in_method == "OPTIONS") method = JAMP::Method::OPTIONS;
else if (in_method == "TRACE") method = JAMP::Method::TRACE;
else throw "Unknown request method";
// Copy the request URI
uri = start_line.substr(method_end_position + 1, start_line.size() - 10 - method_end_position);
// Copy the HTTP version
http_version = start_line.substr(uri_end_position + 1, start_line.size() - uri_end_position);
}
// Store vector of other header fields and set the current position correctly
// Set current_position to (start_line_end + 2), accounting for CRLF sequence
size_t current_position = header_fields_start;
// Process the header fields and store them into an array while we are not
// past the header end position.
while (current_position < header_end)
{
// Find the next line break sequence and store
size_t header_field_end = in_request.find("\r\n", current_position);
// Store the current header field as a string
std::string header_field = in_request.substr(current_position, header_field_end - current_position);
std::string key = "";
std::string value = "";
size_t key_end_pos = header_field.find(":", 0);
if (key_end_pos == std::string::npos)
throw "Bad header field";
key = header_field.substr(0, key_end_pos);
size_t value_start_pos = key_end_pos + 1;
if (header_field[key_end_pos + 1] == ' ')
value_start_pos++;
value = header_field.substr(value_start_pos, header_field.size() - value_start_pos);
fields.emplace(key, value);
// Set current position to field length + 2 to account for CRLF sequence
current_position = header_field_end + 2;
}
this->m_method = method;
this->m_uri = uri;
this->m_http_version = http_version;
this->m_fields = fields;
}
#ifndef HTTPCOMMON_H
#define HTTPCOMMON_H
#ifndef HTTPCOMMON_HPP
#define HTTPCOMMON_HPP
#include <map>
typedef unsigned short ushort;
typedef std::multimap<std::string, std::string> HeaderFields;
static std::string CRLF = "\r\n";
static std::string SP = " ";
namespace JAMP
{
......
#ifndef HTTPREQUEST_HH
#define HTTPREQUEST_HH
#ifndef HTTPREQUEST_HPP
#define HTTPREQUEST_HPP
#include <string>
#include <vector>
#include <map>
#include "httpcommon.h"
#include "HTTPCommon.hpp"
namespace JAMP
{
class HTTPRequest
{
private:
// Start line variables
JAMP::Method m_method;
std::string m_uri;
std::string m_http_version;
JAMP::Method m_method{};
std::string m_uri{};
std::string m_http_version{};
// Header field key-value pairs
std::multimap<std::string, std::string> m_fields;
......@@ -25,9 +24,15 @@ namespace JAMP
public:
HTTPRequest(std::string);
std::string GetURI();
// Private member variable accessors
auto Method() { return m_method; } /* Returns the request method */
auto URI() { return m_uri; } /* Returns the request URI */
auto HTTPVersion() { return m_http_version; } /* Returns the request HTTP version */
auto HeaderFields() { return m_fields; } /* Returns the request header fields */
auto Message() { return m_message; } /* Returns the request message */
std::string Serialize();
void Deserialize(std::string);
void Deserialize(const std::string);
};
}
......
#ifndef HTTPRESPONSE_H
#define HTTPRESPONSE_H
#ifndef HTTPRESPONSE_HPP
#define HTTPRESPONSE_HPP
#include <string>
#include <sstream>
#include "httpcommon.h"
#include "HTTPCommon.hpp"
namespace JAMP
{
......@@ -26,13 +27,13 @@ namespace JAMP
void SetResponseCode(JAMP::Code response_code);
void AddHeader(std::string, std::string);
void AddHeader(const std::string&, const std::string&);
void Write(std::string message_body);
void Close();
std::string Serialize();
const std::string Serialize();
};
}
......
#ifndef JAMPSERVER_HH
#define JAMPSERVER_HH
#ifndef JAMP_HPP
#define JAMP_HPP
#include <string>
#include <functional>
#include "httprequest.hh"
#include "httpresponse.h"
#include "HTTPRequest.hpp"
#include "HTTPResponse.hpp"
constexpr int MAX_CLIENT_MESSAGE = 8192;
......
#include "jampserver.hh"
#include "JAMP.hpp"
int main()
{
......
#ifndef UTILITIES_HH
#define UTILITIES_HH
#include <chrono>
#endif // UTILITIES_HH
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment