Commit 676602e9 authored by James Allenby's avatar James Allenby
Browse files

Almost finished

parent 61728942
......@@ -2,4 +2,4 @@ cmake_minimum_required(VERSION 2.8)
project(JAMP)
set(CMAKE_CXX_STANDARD "17")
add_executable(${PROJECT_NAME} "main.cc" "httpresponse.cc" "httprequest.cc")
add_executable(${PROJECT_NAME} "main.cc" "jampserver.cc" "httpresponse.cc" "httprequest.cc")
......@@ -3,112 +3,119 @@
#include <iostream>
#include <algorithm>
namespace JAMP
JAMP::HTTPRequest::HTTPRequest(std::string request)
{
void 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;
Deserialize(request);
}
constexpr size_t header_start = 0;
std::string JAMP::HTTPRequest::GetURI()
{
return m_uri;
}
const size_t start_line_end = in_request.find("\r\n");
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;
const size_t header_fields_start = start_line_end + 2;
constexpr size_t header_start = 0;
const size_t header_end = in_request.find("\r\n\r\n", header_start);
const size_t start_line_end = in_request.find("\r\n");
const size_t message_body_start = header_end + 4;
const size_t message_body_end = in_request.size();
const size_t header_fields_start = start_line_end + 2;
// A HTTP request MUST have a header end position
if (header_end == std::string::npos)
throw "Invalid HTTP request";
const size_t header_end = in_request.find("\r\n\r\n", header_start);
// A HTTP request MUST have at least 1 header provided
if (start_line_end == header_end)
throw "Invalid HTTP request";
const size_t message_body_start = header_end + 4;
const size_t message_body_end = in_request.size();
{ // Process HTTP Start Line
// A HTTP request MUST have a header end position
if (header_end == std::string::npos)
throw "Invalid HTTP request";
// Copy the entire start line into a string
// A HTTP request MUST have at least 1 header provided
if (start_line_end == header_end)
throw "Invalid HTTP request";
std::string start_line = in_request.substr(0, start_line_end);
{ // Process HTTP Start Line
// 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";
// Copy the entire start line into a string
// 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";
std::string start_line = in_request.substr(0, start_line_end);
// Store the request method as string
std::string in_method = start_line.substr(0, method_end_position);
// 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";
// 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";
// 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";
// Copy the request URI
uri = start_line.substr(method_end_position + 1, start_line.size() - 9 - method_end_position);
// Store the request method as string
std::string in_method = start_line.substr(0, method_end_position);
// Copy the HTTP version
http_version = start_line.substr(uri_end_position + 1, start_line.size() - uri_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);
// 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;
// Copy the HTTP version
http_version = start_line.substr(uri_end_position + 1, start_line.size() - uri_end_position);
}
// 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);
// 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;
std::string key = "";
std::string value = "";
// 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);
size_t key_end_pos = header_field.find(":", 0);
if (key_end_pos == std::string::npos)
throw "Bad header field";
// Store the current header field as a string
std::string header_field = in_request.substr(current_position, header_field_end - current_position);
key = header_field.substr(0, key_end_pos);
std::string key = "";
std::string value = "";
size_t value_start_pos = key_end_pos + 1;
if (header_field[key_end_pos + 1] == ' ')
value_start_pos++;
size_t key_end_pos = header_field.find(":", 0);
if (key_end_pos == std::string::npos)
throw "Bad header field";
value = header_field.substr(value_start_pos, header_field.size() - value_start_pos);
key = header_field.substr(0, key_end_pos);
fields.emplace(key, value);
size_t value_start_pos = key_end_pos + 1;
if (header_field[key_end_pos + 1] == ' ')
value_start_pos++;
// Set current position to field length + 2 to account for CRLF sequence
current_position = header_field_end + 2;
}
value = header_field.substr(value_start_pos, header_field.size() - value_start_pos);
this->m_method = method;
this->m_uri = uri;
this->m_http_version = http_version;
this->m_fields = fields;
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;
}
......@@ -23,10 +23,10 @@ namespace JAMP
// Message
std::string m_message;
public:
// Returns string version of this request
std::string Serialize();
HTTPRequest(std::string);
// Takes in a string and converts into correct types
std::string GetURI();
std::string Serialize();
void Deserialize(std::string);
};
}
......
#include "httpresponse.h"
#include <string>
#include <algorithm>
namespace JAMP
#include <unistd.h>
static std::string CRLF = "\r\n";
JAMP::HTTPResponse::HTTPResponse(int connfd)
{
m_connection_fd = connfd;
}
void JAMP::HTTPResponse::SetResponseCode(JAMP::Code response_code)
{
this->m_response_code = response_code;
return;
}
void JAMP::HTTPResponse::AddHeader(std::string key, std::string value)
{
m_fields.emplace(key, value);
}
void JAMP::HTTPResponse::Write(std::string text)
{
m_message += text;
}
std::string JAMP::HTTPResponse::Serialize()
{
// 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;
// Write the headers
std::for_each(m_fields.begin(), m_fields.end(), [&ret](auto field){
ret += field.first + ": " + field.second + CRLF;
});
ret += CRLF;
// Write the message
ret += m_message;
return ret;
}
// Write to file descriptor and close server
void JAMP::HTTPResponse::Close()
{
void HTTPResponse::SetResponseCode(JAMP::Code response_code)
{
this->m_response_code = response_code;
this->m_reason_phrase = JAMP::ReasonPhrase.at(response_code);
return;
}
void HTTPResponse::SetMessageBody(std::string message_body)
{
m_message_body = message_body;
return;
}
std::string HTTPResponse::Serialize()
{
std::string CRLF = "\r\n";
std::string result;
{
result += "HTTP/1.1 " + JAMP::StringCode.at(this->m_response_code) + " " + JAMP::ReasonPhrase.at(this->m_response_code) + CRLF;
} // Craft Start Line
result += "Return-Code: GOOD" + CRLF + CRLF;
result += this->m_message_body;
return result;
}
std::string response = Serialize();
write(this->m_connection_fd, response.c_str(), response.size());
close(m_connection_fd);
return;
}
......@@ -10,13 +10,27 @@ namespace JAMP
class HTTPResponse
{
private:
// Connection file descriptor to write to
int m_connection_fd;
// Response code of current response
JAMP::Code m_response_code;
std::string m_reason_phrase;
std::string m_message_body;
// Map of all headers to send to client
std::multimap<std::string, std::string> m_fields;
// Message to send to client
std::string m_message;
//
public:
explicit HTTPResponse(int connection_fd);
void SetResponseCode(JAMP::Code response_code);
void SetMessageBody(std::string message_body);
void AddHeader(std::string, std::string);
void Write(std::string message_body);
void Close();
std::string Serialize();
};
......
#include "jampserver.hh"
#include <iostream>
#include <functional>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include "httprequest.hh"
#include "httpresponse.h"
JAMP::Server::Server()
{
// Create a new socket object
this->m_sock = socket(AF_INET, SOCK_STREAM, 0);
if (this->m_sock < 0)
{
perror("Socket error");
throw;
}
}
void JAMP::Server::All(std::string uri, HTTPCallback callback)
{
uri_map.emplace(uri, callback);
}
[[noreturn]] void JAMP::Server::Start(ushort port)
{
sockaddr_in server_address = {};
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;
server_address.sin_port = htons(port);
if (bind(this->m_sock, reinterpret_cast<sockaddr*>(&server_address), sizeof(server_address)) < 0)
{
perror("Bind error");
throw;
}
if (listen(this->m_sock, SOMAXCONN))
{
perror("Listen error");
throw;
}
sockaddr_in client_address = {};
socklen_t client_length = {};
while (true)
{
int connection_fd = accept(m_sock, reinterpret_cast<sockaddr*>(&client_address), &client_length);
if (connection_fd < 0)
{
perror("Connection error");
continue;
}
char message[MAX_CLIENT_MESSAGE] = {};
long message_length = read(connection_fd, &message, MAX_CLIENT_MESSAGE);
std::string client_message(message, static_cast<size_t>(message_length));
JAMP::HTTPRequest request(client_message);
JAMP::HTTPResponse response(connection_fd);
try
{
std::cout << "URI: " << request.GetURI() << std::endl
<< "Len: " << request.GetURI().size() << std::endl;
uri_map.at(request.GetURI())(request, response);
}
catch(std::exception e)
{
response.SetResponseCode(JAMP::Code::HTTP_500);
std::string e_msg("Exception: ");
e_msg += e.what();
response.Write(e_msg);
response.Close();
}
}
}
#ifndef JAMPSERVER_HH
#define JAMPSERVER_HH
#include <string>
#include <functional>
#include "httprequest.hh"
#include "httpresponse.h"
constexpr int MAX_CLIENT_MESSAGE = 8192;
typedef std::function<void (JAMP::HTTPRequest&, JAMP::HTTPResponse&)> HTTPCallback;
namespace JAMP
{
class Server
{
private:
int m_sock;
ushort m_port;
std::map<std::string, HTTPCallback> uri_map;
public:
Server();
// Register functions
void All(std::string, HTTPCallback);
[[noreturn]] void Start(ushort port);
};
}
#endif // JAMPSERVER_HH
// This code conforms to RFC7230 HTTP/1.1
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <array>
#include <map>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "httpcommon.h"
#include "httprequest.hh"
#include "httpresponse.h"
void SendIndexPage(int connectionfd)
{
std::ifstream index_page("index.html");
std::string index_page_source((std::istreambuf_iterator<char>(index_page)),
std::istreambuf_iterator<char>());
write(connectionfd, index_page_source.c_str(), index_page_source.size());
}
#include "jampserver.hh"
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
std::cerr << "Error creating socket" << std::endl;
std::exit(EXIT_FAILURE);
}
sockaddr_in server_options = {};
server_options.sin_family = AF_INET;
server_options.sin_addr.s_addr = INADDR_ANY;
server_options.sin_port = htons(10000);
if (bind(sockfd, reinterpret_cast<sockaddr*>(&server_options), sizeof(server_options)) < 0)
{
std::cerr << "Unable to bind address to socket" << std::endl;
std::exit(EXIT_FAILURE);
}
if (listen(sockfd, SOMAXCONN))
{
std::cerr << "Unable to listen on socket" << std::endl;
std::exit(EXIT_FAILURE);
}
while (true)
{
sockaddr_in client_address = {};
socklen_t client_length = 0;
int connectionfd = accept(sockfd, reinterpret_cast<sockaddr*>(&client_address), &client_length);
char incoming_data_buffer[8192];
ssize_t buffer_len = read(connectionfd, &incoming_data_buffer, 8192);
std::string incoming_request(incoming_data_buffer, static_cast<size_t>(buffer_len));
JAMP::HTTPRequest request;
request.Deserialize(incoming_request);
JAMP::HTTPResponse response;
response.SetResponseCode(JAMP::Code::HTTP_413);
response.SetMessageBody("<html><body><h1>Hello, world!</h1><hr></body></html>");
std::string response_str = response.Serialize();
write(connectionfd, response_str.c_str(), response_str.size());
SendIndexPage(connectionfd);
JAMP::Server server;
close(connectionfd);
server.All("/", [](auto& request, auto& response)
{
response.SetResponseCode(JAMP::Code::HTTP_200);
response.Write("hello");
response.Close();
});
}
server.All("/a", [](auto& request, auto& response)
{
response.SetResponseCode(JAMP::Code::HTTP_200);
response.Write("helloworld");
response.Close();
});
close(sockfd);
server.Start(10000);
return 0;
}
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