Ready. Set. Go!

This commit is contained in:
apio 2022-10-07 16:24:12 +02:00
commit e52806bc7a
17 changed files with 1074 additions and 0 deletions

12
CMakeLists.txt Normal file
View File

@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.8..3.22)
project(webcxx LANGUAGES CXX)
add_library(${PROJECT_NAME} STATIC ${CMAKE_CURRENT_LIST_DIR}/src/App.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Socket.cpp ${CMAKE_CURRENT_LIST_DIR}/src/HTMLTemplate.cpp ${CMAKE_CURRENT_LIST_DIR}/src/MIMETypes.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Response.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Request.cpp ${CMAKE_CURRENT_LIST_DIR}/src/HTTPStatus.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Map.cpp)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/include/webcxx ${CMAKE_CURRENT_LIST_DIR}/src)
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17)
set(webcxx_include ${CMAKE_CURRENT_LIST_DIR}/include)
set(webcxx_libs ${PROJECT_NAME})

70
include/webcxx/App.h Normal file
View File

@ -0,0 +1,70 @@
#pragma once
#include <functional>
#include <map>
#include <memory>
#include <netinet/in.h>
#include <string>
namespace webcxx {
class Response;
class Request;
class ErrorWatcher;
class App {
public:
static std::shared_ptr<App> create();
int run(int port);
void terminate();
static std::shared_ptr<App> current_app();
void on(int methods, std::string path,
std::function<Response(Request &)> action);
void on_error(int error, std::function<Response(void)> action);
void set_static_resource_path(const std::string& path);
void set_favicon(const std::string& favicon_path);
private:
App() = default;
int socket_init(int);
int main_loop();
bool m_is_running;
int m_socket_fd;
static std::shared_ptr<App> s_instance;
std::string static_resource_path = "/static";
int m_current_connection_fd;
sockaddr_in m_addr;
int handle_request(Request& req);
void get_client_addr();
std::string m_client_ip;
struct Action {
std::function<Response(Request &)> get_action;
std::function<Response(Request &)> post_action;
std::function<Response(Request &)> put_action;
std::function<Response(Request &)> delete_action;
};
std::map<std::string, Action> m_action_map;
std::map<int, std::function<Response(void)>> m_error_action_map;
static void app_terminate(int); // Terminate the current app.
friend class ErrorWatcher;
};
Response text(const std::string &content);
Response json(const std::string &content);
Response html(const std::string &content, const std::string& title = "");
Response redirect(const std::string &url, int status_code = 302);
Response error(int status_code = 500);
Response file(const std::string& item_path);
} // namespace webcxx

23
include/webcxx/Map.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include <map>
#include <string>
namespace webcxx
{
/* Wrapper around std::map that provides a read-only interface with a contains() function */
class Map {
public:
typedef std::map<std::string, std::string> MapType;
Map(const MapType& map);
std::string at(const std::string& key);
bool contains(const std::string& key);
std::string operator[](const std::string& key);
MapType::const_iterator begin() const noexcept;
MapType::const_iterator cbegin() const noexcept;
MapType::const_iterator end() const noexcept;
MapType::const_iterator cend() const noexcept;
private:
const MapType m_map;
};
}

46
include/webcxx/Request.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include <map>
#include <string>
namespace webcxx {
enum RequestType { GET = 1 << 0, POST = 1 << 1, PUT = 1 << 2, DELETE = 1 << 3 };
class App;
class Map;
class Request {
public:
RequestType method() { return m_method; }
std::string ip() { return m_ip; }
Map headers() const;
Map args() const;
std::string path() const { return m_path; }
std::string content() const { return m_content; }
private:
explicit Request(int fd);
bool is_bad_request() { return m_bad_request; };
RequestType m_method;
std::string m_path;
std::string m_real_path;
std::string m_content;
std::map<std::string, std::string> m_headers;
bool m_bad_request = false;
std::string m_ip;
std::map<std::string, std::string> m_args;
std::pair<std::string, std::string> split_header(std::string input,
std::string delim);
void decode_http_header(std::string header);
void log(int status_code);
friend class App;
};
} // namespace webcxx

24
include/webcxx/Response.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <map>
#include <string>
namespace webcxx {
class App;
class Response {
public:
explicit Response(int status_code, std::string content = "",
std::string content_type = "text/html",
std::map<std::string, std::string> extra_headers = {});
std::string to_string();
void set_status(int status_code);
private:
int status;
std::map<std::string, std::string> headers;
std::string content;
int send(int fd);
friend class App;
};
} // namespace webcxx

419
src/App.cpp Normal file
View File

@ -0,0 +1,419 @@
#include "App.h"
#include "HTTPStatus.h"
#include "Request.h"
#include "Response.h"
#include <arpa/inet.h>
#include <poll.h>
#include <sstream>
#include <stdexcept>
#include <stdlib.h>
#include <string.h>
#include <sys/signal.h>
#include <sys/socket.h>
#include "HTMLTemplate.h"
#include <fstream>
#include <limits.h>
#include <sys/stat.h>
#include <filesystem>
#include "MIMETypes.h"
namespace fs = std::filesystem;
webcxx::Response webcxx::text(const std::string &content) {
return Response{200, content, {"text/plain"}};
}
webcxx::Response webcxx::json(const std::string &content) {
return Response{200, content, {"application/json"}};
}
webcxx::Response webcxx::html(const std::string& content, const std::string& title) {
return Response{200, _webcxx_internal::build_html_from_template(content, title)};
}
webcxx::Response webcxx::redirect(const std::string &url, int status_code) {
return Response{
status_code,
_webcxx_internal::build_html_from_template("<p>If you aren't redirected automatically, please click <a href=\"" +
url + "\">here</a>.</p>","Redirecting..."),
"text/html",
{{"Location", url}}};
}
static webcxx::Response _webcxx_default_error(int status_code)
{
std::ostringstream ss;
ss << "<h1>";
ss << _webcxx_internal::http_status_codes[status_code];
ss << "</h1>";
ss << "<p>";
ss << "<i>webcxx/0.1</i>";
ss << "</p>";
std::ostringstream title;
title << status_code;
title << " ";
title << _webcxx_internal::http_status_codes[status_code];
return webcxx::Response{status_code, _webcxx_internal::build_html_from_template(ss.str(),title.str())};
}
class webcxx::ErrorWatcher
{
public:
std::map<int, std::function<Response(void)>>& get_app_error_map(std::shared_ptr<App> app)
{
return app->m_error_action_map;
}
};
webcxx::Response webcxx::error(int status_code) {
static bool is_recursing = false;
if(is_recursing) return _webcxx_default_error(status_code);
is_recursing = true;
ErrorWatcher e;
auto& error_map = e.get_app_error_map(webcxx::App::current_app());
if(error_map.find(status_code) != error_map.end())
{
Response resp = error_map.at(status_code)();
resp.set_status(status_code);
is_recursing = false;
return resp;
} else {
is_recursing = false;
return _webcxx_default_error(status_code);
}
}
std::shared_ptr<webcxx::App> webcxx::App::s_instance;
std::shared_ptr<webcxx::App> webcxx::App::create() {
s_instance = std::shared_ptr<App>(new App);
return s_instance;
}
std::shared_ptr<webcxx::App> webcxx::App::current_app() {
if (!s_instance) {
throw std::runtime_error(
"Trying to access the current app before creating an app");
}
return s_instance;
}
int webcxx::App::run(int port) {
if (signal(SIGINT, app_terminate) == SIG_ERR) {
perror("signal(SIGINT)");
exit(1);
}
int rc = socket_init(port);
if (rc != 0)
return rc;
printf("webcxx - running on port %d\n", port);
fflush(stdout);
return main_loop();
}
void webcxx::App::app_terminate(int __signum) {
if (!s_instance)
exit(1);
s_instance->terminate();
}
int webcxx::App::socket_init(int port) {
m_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (m_socket_fd < 0) {
perror("socket");
return 1;
}
m_addr.sin_port = htons(port);
m_addr.sin_family = AF_INET;
m_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(m_socket_fd, (struct sockaddr *)&m_addr, sizeof(sockaddr)) < 0) {
perror("bind");
return 1;
}
if (listen(m_socket_fd, 20) < 0) {
perror("listen");
return 1;
}
return 0;
}
void webcxx::App::terminate() { m_is_running = false; }
void webcxx::App::get_client_addr() {
char result[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &m_addr.sin_addr.s_addr, result, sizeof(result));
m_client_ip = result;
}
void webcxx::App::on(int methods, std::string path,
std::function<Response(Request &)> action) {
Action act;
if (m_action_map.find(path) != m_action_map.end()) {
act = m_action_map.at(path);
}
if (methods & (int)RequestType::GET) {
act.get_action = action;
}
if (methods & (int)RequestType::POST) {
act.post_action = action;
}
if (methods & (int)RequestType::PUT) {
act.put_action = action;
}
if (methods & (int)RequestType::DELETE) {
act.delete_action = action;
}
m_action_map[path] = act;
}
void webcxx::App::on_error(int error,
std::function<Response(void)> action) {
if(error < 400)
{
throw std::runtime_error("on_error can only register handlers for status codes 400 and over");
}
m_error_action_map[error] = action;
}
void webcxx::App::set_favicon(const std::string& favicon_path)
{
std::string path = favicon_path;
on(GET, "/favicon.ico", [=](Request& req){
return file(path);
});
}
void webcxx::App::set_static_resource_path(const std::string& path)
{
static_resource_path = path;
}
int webcxx::App::main_loop() {
m_is_running = true;
struct pollfd poll_info;
poll_info.fd = m_socket_fd;
poll_info.events = POLL_IN;
while (m_is_running) {
bool is_ready = false;
m_current_connection_fd = -1;
while (!is_ready) {
if (!m_is_running) {
break;
}
int rc = poll(&poll_info, 1, 2000);
if (rc < 0) {
if (errno == EINTR) {
close(m_socket_fd);
return 0;
}
perror("poll");
close(m_socket_fd);
return 1;
}
if (rc == 0) {
continue;
}
if (poll_info.revents & POLL_IN) {
auto addrlen = sizeof(sockaddr);
m_current_connection_fd = accept(
m_socket_fd, (struct sockaddr *)&m_addr, (socklen_t *)&addrlen);
if (m_current_connection_fd < 0) {
perror("accept");
close(m_socket_fd);
return 1;
}
get_client_addr();
is_ready = true;
}
}
if (!m_is_running) {
close(m_current_connection_fd);
break;
}
Request req(m_current_connection_fd);
req.m_ip = m_client_ip;
int status;
if((status = handle_request(req)))
{
return status;
}
close(m_current_connection_fd);
}
close(m_socket_fd);
return 0;
}
static inline bool string_starts_with(const std::string& string, const std::string& substring)
{
return string.rfind(substring, 0) == 0;
}
static std::string get_current_working_directory()
{
char buffer[PATH_MAX];
getcwd(buffer, PATH_MAX);
return std::move(std::string(buffer));
}
static inline void trim_string_with_substring(std::string& string, const std::string& substring)
{
string.assign(string.begin() + substring.length(), string.end());
}
static inline size_t get_file_size(std::ifstream& file)
{
file.seekg(0, file.end);
std::size_t result = file.tellg();
file.seekg(0, file.beg);
return result;
}
static std::string read_whole_file(std::ifstream& file)
{
std::string result;
while(file.good())
{
char ch = file.get();
if(ch != -1) result.push_back(ch);
}
return std::move(result);
}
static std::string remove_trailing_newline(std::string str)
{
while(str[str.size() - 1] == '\n')
{
str.pop_back();
}
return std::move(str);
}
webcxx::Response webcxx::file(const std::string& item_path)
{
std::string path = fs::absolute(item_path).string();
struct stat statbuf;
if(stat(path.c_str(), &statbuf) < 0)
{
if(errno == EACCES)
{
return error(403);
}
return error(404);
}
if(S_ISDIR(statbuf.st_mode))
{
return error(403);
}
std::ifstream item(path, std::ios::binary);
std::string content = read_whole_file(item);
Response res = Response{200, content, remove_trailing_newline(_webcxx_internal::get_mime_type_from_filepath(path))};
item.close();
return res;
}
int webcxx::App::handle_request(Request& req)
{
Response res(200);
if (req.is_bad_request()) {
res = error(400);
} else if (!static_resource_path.empty() && string_starts_with(req.path(), static_resource_path)) {
std::string path = req.path();
trim_string_with_substring(path, static_resource_path);
std::string item_path = fs::path("static").concat(path);
res = file(item_path);
} else if (m_action_map.find(req.path()) != m_action_map.end()) {
Action act = m_action_map.at(req.path());
switch (req.m_method) {
case RequestType::GET:
if (act.get_action) {
try {
res = act.get_action(req);
} catch (...) {
res = error(500);
req.log(res.status);
res.send(m_current_connection_fd);
close(m_current_connection_fd);
close(m_socket_fd);
std::rethrow_exception(std::current_exception());
}
} else {
res = error(405);
}
break;
case RequestType::POST:
if (act.post_action) {
try {
res = act.post_action(req);
} catch (...) {
res = error(500);
req.log(res.status);
res.send(m_current_connection_fd);
close(m_current_connection_fd);
close(m_socket_fd);
std::rethrow_exception(std::current_exception());
}
} else {
res = error(405);
}
break;
case RequestType::PUT:
if (act.put_action) {
try {
res = act.put_action(req);
} catch (...) {
res = error(500);
req.log(res.status);
res.send(m_current_connection_fd);
close(m_current_connection_fd);
close(m_socket_fd);
std::rethrow_exception(std::current_exception());
}
} else {
res = error(405);
}
break;
case RequestType::DELETE:
if (act.delete_action) {
try {
res = act.delete_action(req);
} catch (...) {
res = error(500);
req.log(res.status);
res.send(m_current_connection_fd);
close(m_current_connection_fd);
close(m_socket_fd);
std::rethrow_exception(std::current_exception());
}
} else {
res = error(405);
}
break;
}
} else {
res = error(404);
}
req.log(res.status);
if (res.send(m_current_connection_fd) < 0) {
perror("send");
close(m_current_connection_fd);
close(m_socket_fd);
return 1;
}
return 0;
}

25
src/HTMLTemplate.cpp Normal file
View File

@ -0,0 +1,25 @@
#include "HTMLTemplate.h"
#include <sstream>
std::string _webcxx_internal::build_html_from_template(const std::string& content, const std::string& title)
{
std::ostringstream ss;
ss << "<!DOCTYPE html>"
"<html>"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
if (!title.empty())
{
ss << "<title>";
ss << title;
ss << "</title>";
}
ss << "</head>"
"<body>";
ss << content;
ss << "</body>"
"</html>";
return std::move(ss.str());
}

6
src/HTMLTemplate.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include <string>
namespace _webcxx_internal {
std::string build_html_from_template(const std::string& content, const std::string& title = "");
}

58
src/HTTPStatus.cpp Normal file
View File

@ -0,0 +1,58 @@
#include "HTTPStatus.h"
std::map<int, std::string> _webcxx_internal::http_status_codes = {
{100, "Continue"},
{101, "Switching Protocols"},
{103, "Early Hints"},
{200, "OK"},
{201, "Created"},
{202, "Accepted"},
{203, "Non-Authoritative Information"},
{204, "No Content"},
{205, "Reset Content"},
{206, "Partial Content"},
{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, "Payload Too Large"},
{414, "URI Too Long"},
{415, "Unsupported Media Type"},
{416, "Range Not Satisfiable"},
{417, "Expectation Failed"},
{418, "I'm a teapot"},
{422, "Unprocessable Entity"},
{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"},
};

7
src/HTTPStatus.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <map>
#include <string>
namespace _webcxx_internal {
extern std::map<int, std::string> http_status_codes;
}

69
src/MIMETypes.cpp Normal file
View File

@ -0,0 +1,69 @@
#include "MIMETypes.h"
#ifdef __linux__
#define OS_UNIX_LIKE
#elif defined(__unix__)
#define OS_UNIX_LIKE
#elif defined(_WIN32) || defined(WIN32)
#define OS_WINDOWS
#endif
#ifdef OS_UNIX_LIKE
#include <unistd.h>
#elif defined(OS_WINDOWS)
#include <Windows.h>
#include <urlmon.h>
#else
#error "Unsupported platform"
#endif
#include <stdexcept>
#ifdef OS_UNIX_LIKE
std::string _webcxx_internal::get_mime_type_from_filepath(const std::string& filepath)
{
std::string extension;
size_t dot = filepath.rfind('.');
if(dot == filepath.npos) goto use_file_command;
extension = filepath.substr(dot+1);
if(extension == "js") return "text/javascript";
if(extension == "json") return "application/json";
if(extension == "css") return "text/css";
use_file_command:
FILE* pipe = popen(("file --mime-type -b " + filepath).c_str(), "r");
if(!pipe)
{
throw std::runtime_error("Unable to determine mimetype for file " + filepath);
}
std::string result;
int ch;
while((ch=fgetc(pipe)) != EOF)
{
result.push_back(ch);
}
pclose(pipe);
return std::move(result);
}
#else
std::string _webcxx_internal::get_mime_type_from_filepath(const std::string& filepath)
{
std::wstring str = filepath;
LPWSTR pwzMimeOut = NULL;
HRESULT hr = FindMimeFromData( NULL, str.c_str(), NULL, 0,
NULL, FMFD_URLASFILENAME, &pwzMimeOut, 0x0 );
if ( SUCCEEDED( hr ) ) {
std::wstring strResult( pwzMimeOut );
// Despite the documentation stating to call operator delete, the
// returned string must be cleaned up using CoTaskMemFree
CoTaskMemFree( pwzMimeOut );
return std::move(std::string(strResult));
}
return "application/octet-stream";
}
#endif

7
src/MIMETypes.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <string>
namespace _webcxx_internal
{
std::string get_mime_type_from_filepath(const std::string& filepath);
}

43
src/Map.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "Map.h"
namespace webcxx {
Map::Map(const MapType& map) : m_map(map)
{
}
std::string Map::at(const std::string& key)
{
return m_map.at(key);
}
std::string Map::operator[](const std::string& key)
{
return std::move(at(key));
}
bool Map::contains(const std::string& key)
{
return m_map.find(key) != m_map.end();
}
Map::MapType::const_iterator Map::begin() const noexcept
{
return m_map.cbegin();
}
Map::MapType::const_iterator Map::cbegin() const noexcept
{
return m_map.cbegin();
}
Map::MapType::const_iterator Map::end() const noexcept
{
return m_map.cend();
}
Map::MapType::const_iterator Map::cend() const noexcept
{
return m_map.cend();
}
}

163
src/Request.cpp Normal file
View File

@ -0,0 +1,163 @@
#include "Request.h"
#include "Socket.h"
#include <fcntl.h>
#include <sstream>
#include <stdexcept>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vector>
#include "Map.h"
using namespace _webcxx_internal;
static std::pair<std::string, std::string> _split_header(std::string input,
std::string delim) {
int start = 0;
int end = input.find(delim);
if (end == std::string::npos) {
return {"", ""};
}
return {input.substr(start, end),
input.substr(end + delim.size(), input.size() - end - delim.size())};
}
static bool is_pair_empty(const std::pair<std::string, std::string>& p) {
return (p.first.size() == 0) && (p.second.size() == 0);
}
std::pair<std::string, std::string>
webcxx::Request::split_header(std::string input, std::string delim) {
auto result = _split_header(input, delim);
if (is_pair_empty(result))
m_bad_request = true;
return result;
}
static std::pair<std::string, std::string>
remove_newlines(std::pair<std::string, std::string> header) {
if (header.first[header.first.size() - 1] == '\n') {
header.first.pop_back();
}
if (header.second[header.second.size() - 1] == '\n') {
header.second.pop_back();
}
return header;
}
void webcxx::Request::decode_http_header(std::string header) {
auto rc = split_header(header, " ");
if (rc.first == "GET") {
m_method = RequestType::GET;
} else if (rc.first == "POST") {
m_method = RequestType::POST;
} else if (rc.first == "PUT") {
m_method = RequestType::PUT;
} else if (rc.first == "DELETE") {
m_method = RequestType::DELETE;
} else {
m_bad_request = true;
return;
}
rc = split_header(rc.second, " ");
m_path = rc.first;
m_real_path = rc.first;
if (m_path.find('?') != std::string::npos) {
auto path_parts = split_header(m_path, "?");
m_path = path_parts.first;
std::vector<std::string> arg_strings;
std::pair<std::string, std::string> p =
_split_header(path_parts.second, "&");
while (!is_pair_empty(p)) {
arg_strings.push_back(p.first);
if(p.second.find("&") != std::string::npos) p = split_header(p.second, "&");
else break;
}
if (arg_strings.size() == 0) {
arg_strings.push_back(path_parts.second);
} else {
arg_strings.push_back(p.second);
}
for (auto &arg : arg_strings) {
if (arg.find('=') != std::string::npos) {
auto _p = split_header(arg, "=");
m_args[_p.first] = _p.second;
} else {
m_args[arg] = "";
}
}
}
if (rc.second != "HTTP/1.1\n") {
m_bad_request = true;
}
}
webcxx::Request::Request(int fd) {
std::string m_http_header_line = "";
std::string m_request_header = "";
char buffer[4096];
if (sockgetline(fd, buffer, sizeof(buffer)) > 2) {
m_http_header_line = buffer;
decode_http_header(m_http_header_line);
while (sockgetline(fd, buffer, sizeof(buffer)) > 2) {
m_request_header = buffer;
m_headers.insert(remove_newlines(split_header(m_request_header, ": ")));
}
} else {
m_bad_request = true;
}
if(!(m_method == POST || m_method == PUT)) { m_content = ""; return; }
try {
std::string content_length = m_headers.at("Content-Length");
int content_length_as_int;
int rc = sscanf(content_length.c_str(), "%d", &content_length_as_int);
if (rc != 1) {
printf("%d\n", rc);
m_bad_request = true;
return;
}
char *buf = (char *)malloc(content_length_as_int);
if (read(fd, buf, content_length_as_int) < 0) {
m_bad_request = true;
return;
}
m_content = buf;
free(buf);
} catch (std::out_of_range e) {
m_content = "";
}
}
static std::string method_to_string(webcxx::RequestType method) {
switch (method) {
case webcxx::RequestType::GET:
return "GET";
case webcxx::RequestType::POST:
return "POST";
case webcxx::RequestType::PUT:
return "PUT";
case webcxx::RequestType::DELETE:
return "DELETE";
default:
return "Unknown";
}
}
void webcxx::Request::log(int status_code) {
printf("%s - \"%s %s HTTP/1.1\" - %d\n", m_ip.c_str(),
method_to_string(m_method).c_str(),
m_real_path.c_str(),
status_code);
fflush(stdout);
}
webcxx::Map webcxx::Request::headers() const
{
return Map(m_headers);
}
webcxx::Map webcxx::Request::args() const
{
return Map(m_args);
}

52
src/Response.cpp Normal file
View File

@ -0,0 +1,52 @@
#include "Response.h"
#include "HTTPStatus.h"
#include <sstream>
#include <sys/socket.h>
static int socket_send(int fd, const char *str, size_t size, int flags) {
return send(fd, str, size, flags);
};
void webcxx::Response::set_status(int status_code)
{
status = status_code;
}
webcxx::Response::Response(int status_code, std::string content,
std::string content_type,
std::map<std::string, std::string> extra_headers)
: status(status_code), headers(extra_headers), content(content) {
headers["Content-Type"] = content_type;
if (content != "") {
std::ostringstream ss;
ss << content.size();
headers["Content-Length"] = ss.str();
}
headers["Server"] = "webcxx/0.1";
headers["Connection"] = "close";
}
std::string webcxx::Response::to_string() {
std::ostringstream result;
result << "HTTP/1.1 ";
result << status;
result << " ";
result << _webcxx_internal::http_status_codes[status];
result << "\r\n";
for (auto &header : headers) {
result << header.first;
result << ": ";
result << header.second;
result << "\r\n";
}
result << "\r\n";
if (content != "") {
result << content;
}
return result.str();
}
int webcxx::Response::send(int fd) {
std::string as_string = to_string();
return socket_send(fd, as_string.c_str(), as_string.size(), 0);
}

42
src/Socket.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "Socket.h"
#include <sys/socket.h>
namespace _webcxx_internal {
char sockgetchar(int fd) {
char c;
if (recv(fd, &c, 1, 0) < 0) {
return -1;
}
return c;
}
char sockpeekchar(int fd) {
char c;
if (recv(fd, &c, 1, MSG_PEEK) < 0) {
return -1;
}
return c;
}
int sockgetline(int fd, char *buf, size_t n) {
int i = 0;
char c = '\0';
while ((i < n - 1) && (c != '\n')) {
c = sockgetchar(fd);
if (c > 0) {
if (c == '\r') {
c = sockpeekchar(fd);
if ((c > 0) && (c == '\n'))
c = sockgetchar(fd);
else
c = '\n';
}
buf[i] = c;
i++;
} else
c = '\n';
}
buf[i] = '\0';
return i;
}
} // namespace _webcxx_internal

8
src/Socket.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include <stddef.h>
namespace _webcxx_internal {
char sockgetchar(int fd);
char sockpeekchar(int fd);
int sockgetline(int fd, char *buf, size_t n);
} // namespace _webcxx_internal