CMakeLists.txt
- added copy_frontend as dependency (else it wont automatically execute request_handler.cpp - implemented db connection - fixed routing of short code All Files - Improved logging
This commit is contained in:
parent
cbfc0b563c
commit
0d89ce5e2d
8 changed files with 251 additions and 89 deletions
|
|
@ -21,6 +21,9 @@ add_custom_target(copy_frontend ALL
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/frontend ${CMAKE_BINARY_DIR}/frontend
|
COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/frontend ${CMAKE_BINARY_DIR}/frontend
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_dependencies(Application copy_frontend)
|
||||||
|
|
||||||
|
|
||||||
find_package(Boost REQUIRED COMPONENTS filesystem system)
|
find_package(Boost REQUIRED COMPONENTS filesystem system)
|
||||||
|
|
||||||
if(Boost_FOUND)
|
if(Boost_FOUND)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <soci/soci.h>
|
#include <soci/soci.h>
|
||||||
#include <soci/sqlite3/soci-sqlite3.h>
|
|
||||||
|
|
||||||
class DatabaseService {
|
class DatabaseService {
|
||||||
public:
|
public:
|
||||||
|
|
@ -27,7 +26,7 @@ private:
|
||||||
std::string db_path_;
|
std::string db_path_;
|
||||||
std::size_t pool_size_;
|
std::size_t pool_size_;
|
||||||
std::vector<std::shared_ptr<soci::session>> connection_pool_;
|
std::vector<std::shared_ptr<soci::session>> connection_pool_;
|
||||||
std::size_t current_index_ = 0; // Index used for round-robin selection
|
std::size_t current_index_ = 0;
|
||||||
|
|
||||||
static DatabaseService* INSTANCE;
|
static DatabaseService* INSTANCE;
|
||||||
static std::mutex singleton_mutex;
|
static std::mutex singleton_mutex;
|
||||||
|
|
|
||||||
62
includes/log_utils.hpp
Normal file
62
includes/log_utils.hpp
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#ifndef LOG_UTILS_HPP
|
||||||
|
#define LOG_UTILS_HPP
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// Define log levels
|
||||||
|
enum class LogLevel {
|
||||||
|
INFO,
|
||||||
|
ERROR,
|
||||||
|
BADREQUEST
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to get a color based on file name
|
||||||
|
inline const char* getColorForFile(const std::string& file_name) {
|
||||||
|
// Define color codes
|
||||||
|
const char* colors[] = {
|
||||||
|
"\033[34m", // Blue
|
||||||
|
"\033[32m", // Green
|
||||||
|
"\033[36m", // Cyan
|
||||||
|
"\033[35m", // Magenta
|
||||||
|
"\033[33m" // Yellow
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hash the file name to determine the color
|
||||||
|
std::hash<std::string> hasher;
|
||||||
|
size_t hash = hasher(file_name);
|
||||||
|
return colors[hash % (sizeof(colors) / sizeof(colors[0]))];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging function
|
||||||
|
inline void log(const LogLevel level, const std::string& message, const std::string& file_path) {
|
||||||
|
const auto now = std::chrono::system_clock::now();
|
||||||
|
const auto time = std::chrono::system_clock::to_time_t(now);
|
||||||
|
const std::tm tm = *std::localtime(&time);
|
||||||
|
|
||||||
|
const auto color_reset = "\033[0m";
|
||||||
|
const auto color_error = "\033[31m";
|
||||||
|
|
||||||
|
// Extract file name from full path
|
||||||
|
std::string file_name = std::filesystem::path(file_path).filename().string();
|
||||||
|
|
||||||
|
// Determine color based on log level
|
||||||
|
const char* color = level == LogLevel::ERROR || level == LogLevel::BADREQUEST ? color_error : getColorForFile(file_name);
|
||||||
|
|
||||||
|
// Print log
|
||||||
|
std::cout << color
|
||||||
|
<< "[" << (level == LogLevel::INFO ? "INFO" : "ERROR") << "] "
|
||||||
|
<< std::put_time(&tm, "%H:%M:%S") // Time only
|
||||||
|
<< " [" << file_name << "] "
|
||||||
|
<< message
|
||||||
|
<< color_reset << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper macro to automatically pass the file name
|
||||||
|
#define LOG(level, message) log(level, message, __FILE__)
|
||||||
|
|
||||||
|
#endif // LOG_UTILS_HPP
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
#include "../includes/database_service.hpp"
|
#include "../includes/database_service.hpp"
|
||||||
|
#include "../includes/log_utils.hpp"
|
||||||
#include <boost/uuid/uuid.hpp>
|
#include <boost/uuid/uuid.hpp>
|
||||||
#include <boost/uuid/uuid_generators.hpp>
|
#include <boost/uuid/uuid_generators.hpp>
|
||||||
#include <boost/uuid/uuid_io.hpp>
|
#include <boost/uuid/uuid_io.hpp>
|
||||||
#include <iostream>
|
|
||||||
#include <mutex>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
std::mutex DatabaseService::singleton_mutex;
|
std::mutex DatabaseService::singleton_mutex;
|
||||||
DatabaseService* DatabaseService::INSTANCE = nullptr;
|
DatabaseService* DatabaseService::INSTANCE = nullptr;
|
||||||
|
|
||||||
DatabaseService& DatabaseService::getInstance(const std::string& db_path, std::size_t pool_size) {
|
DatabaseService& DatabaseService::getInstance(const std::string& db_path, std::size_t pool_size) {
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(singleton_mutex);
|
std::lock_guard<std::mutex> lock(singleton_mutex);
|
||||||
if (!INSTANCE) {
|
if (!INSTANCE) {
|
||||||
INSTANCE = new DatabaseService(db_path, pool_size);
|
INSTANCE = new DatabaseService(db_path, pool_size);
|
||||||
|
|
@ -19,7 +20,10 @@ DatabaseService& DatabaseService::getInstance(const std::string& db_path, std::s
|
||||||
|
|
||||||
DatabaseService::DatabaseService(const std::string& db_path, std::size_t pool_size)
|
DatabaseService::DatabaseService(const std::string& db_path, std::size_t pool_size)
|
||||||
: db_path_(db_path), pool_size_(pool_size), current_index_(0) {
|
: db_path_(db_path), pool_size_(pool_size), current_index_(0) {
|
||||||
|
LOG(LogLevel::INFO, "Initializing DatabaseService");
|
||||||
|
|
||||||
if (pool_size_ <= 0) {
|
if (pool_size_ <= 0) {
|
||||||
|
LOG(LogLevel::ERROR, "Invalid pool size");
|
||||||
throw std::invalid_argument("Connection pool size must be greater than zero.");
|
throw std::invalid_argument("Connection pool size must be greater than zero.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,13 +31,19 @@ DatabaseService::DatabaseService(const std::string& db_path, std::size_t pool_si
|
||||||
auto session = std::make_shared<soci::session>(soci::sqlite3, db_path_);
|
auto session = std::make_shared<soci::session>(soci::sqlite3, db_path_);
|
||||||
connection_pool_.emplace_back(session);
|
connection_pool_.emplace_back(session);
|
||||||
}
|
}
|
||||||
|
LOG(LogLevel::INFO, "Connection Pool filled");
|
||||||
|
try {
|
||||||
|
auto conn = getConnection();
|
||||||
|
*conn << "CREATE TABLE IF NOT EXISTS urls ("
|
||||||
|
"short_code CHAR(8) PRIMARY KEY, "
|
||||||
|
"original_url TEXT NOT NULL UNIQUE, "
|
||||||
|
"created_at INTEGER DEFAULT CURRENT_TIMESTAMP);";
|
||||||
|
|
||||||
// Ensure the table exists
|
LOG(LogLevel::INFO, "Database schema initialized successfully");
|
||||||
auto conn = getConnection();
|
} catch (const std::exception& e) {
|
||||||
*conn << "CREATE TABLE IF NOT EXISTS urls ("
|
LOG(LogLevel::ERROR, "Exception during schema creation: " + std::string(e.what()));
|
||||||
"short_code CHAR(8) PRIMARY KEY, "
|
throw;
|
||||||
"original_url TEXT NOT NULL UNIQUE, "
|
}
|
||||||
"created_at INTEGER DEFAULT CURRENT_TIMESTAMP);";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseService::~DatabaseService() {
|
DatabaseService::~DatabaseService() {
|
||||||
|
|
@ -41,13 +51,13 @@ DatabaseService::~DatabaseService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<soci::session> DatabaseService::getConnection() {
|
std::shared_ptr<soci::session> DatabaseService::getConnection() {
|
||||||
std::lock_guard<std::mutex> lock(singleton_mutex);
|
|
||||||
if (connection_pool_.empty()) {
|
if (connection_pool_.empty()) {
|
||||||
throw std::runtime_error("Connection pool is empty.");
|
throw std::runtime_error("Connection pool is empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG(LogLevel::INFO, "Returning connection at index: " + std::to_string(current_index_));
|
||||||
auto conn = connection_pool_[current_index_];
|
auto conn = connection_pool_[current_index_];
|
||||||
current_index_ = (current_index_ + 1) % pool_size_;
|
current_index_ = (current_index_ + 1) % connection_pool_.size();
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,6 +68,7 @@ std::string DatabaseService::generateShortUUID() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseService::shortenURL(const std::string& longURL, std::function<void(std::error_code, std::string)> callback) {
|
void DatabaseService::shortenURL(const std::string& longURL, std::function<void(std::error_code, std::string)> callback) {
|
||||||
|
LOG(LogLevel::INFO, "Shortening URL: " + longURL);
|
||||||
try {
|
try {
|
||||||
auto conn = getConnection();
|
auto conn = getConnection();
|
||||||
std::string shortCode;
|
std::string shortCode;
|
||||||
|
|
@ -73,11 +84,13 @@ void DatabaseService::shortenURL(const std::string& longURL, std::function<void(
|
||||||
soci::use(shortCode), soci::use(longURL);
|
soci::use(shortCode), soci::use(longURL);
|
||||||
callback({}, shortCode);
|
callback({}, shortCode);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
LOG(LogLevel::ERROR, "Failed to shorten URL: " + std::string(e.what()));
|
||||||
callback(std::make_error_code(std::errc::io_error), "");
|
callback(std::make_error_code(std::errc::io_error), "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseService::getLongURL(const std::string& shortCode, std::function<void(std::error_code, std::string)> callback) {
|
void DatabaseService::getLongURL(const std::string& shortCode, std::function<void(std::error_code, std::string)> callback) {
|
||||||
|
LOG(LogLevel::INFO, "Getting long-URL from short-code: " + shortCode);
|
||||||
try {
|
try {
|
||||||
auto conn = getConnection();
|
auto conn = getConnection();
|
||||||
std::string longURL;
|
std::string longURL;
|
||||||
|
|
@ -86,9 +99,11 @@ void DatabaseService::getLongURL(const std::string& shortCode, std::function<voi
|
||||||
if (!longURL.empty()) {
|
if (!longURL.empty()) {
|
||||||
callback({}, longURL);
|
callback({}, longURL);
|
||||||
} else {
|
} else {
|
||||||
|
LOG(LogLevel::ERROR, "Short code not found: " + shortCode);
|
||||||
callback(std::make_error_code(std::errc::no_such_file_or_directory), "");
|
callback(std::make_error_code(std::errc::no_such_file_or_directory), "");
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
LOG(LogLevel::ERROR, "Failed to retrieve long URL: " + std::string(e.what()));
|
||||||
callback(std::make_error_code(std::errc::io_error), "");
|
callback(std::make_error_code(std::errc::io_error), "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
#include "../includes/http_connection.hpp"
|
#include "../includes/http_connection.hpp"
|
||||||
#include <boost/asio/io_context.hpp>
|
#include "../includes/log_utils.hpp"
|
||||||
#include <boost/asio/ip/tcp.hpp>
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
#include <boost/beast/core/error.hpp>
|
#include <boost/beast/core/error.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sys/socket.h>
|
|
||||||
|
|
||||||
HttpConnection::HttpConnection(io_context& io_context)
|
HttpConnection::HttpConnection(io_context& io_context)
|
||||||
: socket_(io_context) {};
|
: socket_(io_context) {};
|
||||||
|
|
@ -22,18 +21,29 @@ void HttpConnection::process_connection() {
|
||||||
|
|
||||||
void HttpConnection::read() {
|
void HttpConnection::read() {
|
||||||
auto self = shared_from_this();
|
auto self = shared_from_this();
|
||||||
|
|
||||||
http::async_read(socket_, buffer_, request_,
|
http::async_read(socket_, buffer_, request_,
|
||||||
[self](boost::beast::error_code error_code, size_t bytes_transferred) {
|
[self](boost::beast::error_code error_code, size_t bytes_transferred) {
|
||||||
if (!error_code) {
|
if (!error_code) {
|
||||||
cout << "Request received:\n" << self->request_ << endl;
|
LOG(LogLevel::INFO, "Request details:");
|
||||||
|
LOG(LogLevel::INFO, "\tMethod: " + std::string(self->request_.method_string()));
|
||||||
|
LOG(LogLevel::INFO, "\tTarget: " + std::string(self->request_.target()));
|
||||||
|
LOG(LogLevel::INFO, "\tBody: " + std::string(self->request_.body()));
|
||||||
|
|
||||||
|
// Log before forwarding to RequestHandler
|
||||||
|
LOG(LogLevel::INFO, "Forwarding request to RequestHandler...");
|
||||||
self->response_ = self->request_handler_.handle(self->request_);
|
self->response_ = self->request_handler_.handle(self->request_);
|
||||||
|
|
||||||
self->write();
|
self->write();
|
||||||
} else {
|
} else {
|
||||||
cerr << "Error reading: " << error_code.message() << endl;
|
LOG(LogLevel::ERROR, "Error reading request: " + error_code.message());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void HttpConnection::write() {
|
void HttpConnection::write() {
|
||||||
auto self = shared_from_this();
|
auto self = shared_from_this();
|
||||||
std::visit(
|
std::visit(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
#include "../includes/http_server.hpp"
|
#include "../includes/http_server.hpp"
|
||||||
|
#include "../includes/log_utils.hpp"
|
||||||
|
#include <iostream>
|
||||||
#include "http_connection.hpp"
|
#include "http_connection.hpp"
|
||||||
#include <boost/asio/io_context.hpp>
|
|
||||||
#include <boost/asio/placeholders.hpp>
|
#include <boost/asio/placeholders.hpp>
|
||||||
#include <sys/socket.h>
|
|
||||||
|
|
||||||
HttpServer::HttpServer(io_context& io_context, const ip::tcp::endpoint& endpoint)
|
HttpServer::HttpServer(io_context& io_context, const ip::tcp::endpoint& endpoint)
|
||||||
: io_context_(io_context), acceptor_(io_context, endpoint) {
|
: io_context_(io_context), acceptor_(io_context, endpoint) {
|
||||||
|
|
@ -11,14 +11,18 @@ HttpServer::HttpServer(io_context& io_context, const ip::tcp::endpoint& endpoint
|
||||||
|
|
||||||
void HttpServer::accept_connection() {
|
void HttpServer::accept_connection() {
|
||||||
HttpConnection::pointer new_connection = HttpConnection::create(io_context_);
|
HttpConnection::pointer new_connection = HttpConnection::create(io_context_);
|
||||||
|
|
||||||
|
LOG(LogLevel::INFO, "Waiting for new connection");
|
||||||
acceptor_.async_accept(new_connection->socket(),
|
acceptor_.async_accept(new_connection->socket(),
|
||||||
bind(&HttpServer::handle_accept, this, new_connection, boost::asio::placeholders::error)
|
bind(&HttpServer::handle_accept, this, new_connection, boost::asio::placeholders::error));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpServer::handle_accept(HttpConnection::pointer& new_connection, const boost::system::error_code& error_code) {
|
void HttpServer::handle_accept(HttpConnection::pointer& new_connection, const boost::system::error_code& error_code) {
|
||||||
if (!error_code) {
|
if (!error_code) {
|
||||||
|
LOG(LogLevel::INFO, "Accepted new connection from: " + new_connection->socket().remote_endpoint().address().to_string());
|
||||||
new_connection->process_connection();
|
new_connection->process_connection();
|
||||||
|
} else {
|
||||||
|
LOG(LogLevel::ERROR, "Error accepting connection: " + error_code.message());
|
||||||
}
|
}
|
||||||
accept_connection();
|
accept_connection();
|
||||||
}
|
}
|
||||||
19
src/main.cpp
19
src/main.cpp
|
|
@ -1,23 +1,24 @@
|
||||||
|
|
||||||
#include "../includes/http_server.hpp"
|
#include "../includes/http_server.hpp"
|
||||||
|
#include "../includes/log_utils.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
using namespace boost::asio;
|
using namespace boost::asio;
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
const int port = 8080;
|
const int port = 8080;
|
||||||
|
|
||||||
std::cout << "Starting server!" << std::endl;
|
LOG(LogLevel::INFO, "Starting Server on 127.0.0.1:" + std::to_string(port));
|
||||||
try
|
|
||||||
{
|
try {
|
||||||
io_context io_context;
|
io_context io_context;
|
||||||
ip::tcp::endpoint endpoint(ip::tcp::v4(), port);
|
ip::tcp::endpoint endpoint(ip::tcp::v4(), port);
|
||||||
|
|
||||||
HttpServer server(io_context, endpoint);
|
HttpServer server(io_context, endpoint);
|
||||||
|
|
||||||
io_context.run();
|
io_context.run();
|
||||||
}
|
} catch (const std::exception& e) {
|
||||||
catch (std::exception& e)
|
LOG(LogLevel::ERROR, "Exception occurred: " + std::string(e.what()));
|
||||||
{
|
|
||||||
std::cerr << e.what() << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
#include "../includes/request_handler.hpp"
|
#include "../includes/request_handler.hpp"
|
||||||
|
#include "../includes/database_service.hpp"
|
||||||
|
#include "../includes/log_utils.hpp"
|
||||||
#include <boost/beast/http/field.hpp>
|
#include <boost/beast/http/field.hpp>
|
||||||
#include <boost/beast/http/file_body.hpp>
|
#include <boost/beast/http/file_body.hpp>
|
||||||
#include <boost/beast/http/message.hpp>
|
#include <boost/beast/http/message.hpp>
|
||||||
#include <boost/beast/http/status.hpp>
|
#include <boost/beast/http/status.hpp>
|
||||||
#include <boost/beast/http/string_body.hpp>
|
#include <boost/beast/http/string_body.hpp>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include "request_handler.hpp"
|
#include <future>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
http::response<http::string_body> RequestHandler::BadRequest(const std::string& why) {
|
http::response<http::string_body> RequestHandler::BadRequest(const std::string& why) {
|
||||||
|
LOG(LogLevel::BADREQUEST, "BadRequest: " + why);
|
||||||
http::response<http::string_body> response;
|
http::response<http::string_body> response;
|
||||||
response.result(http::status::bad_request);
|
response.result(http::status::bad_request);
|
||||||
response.set(http::field::server, "Beast");
|
response.set(http::field::server, "Beast");
|
||||||
|
|
@ -19,87 +23,151 @@ http::response<http::string_body> RequestHandler::BadRequest(const std::string&
|
||||||
}
|
}
|
||||||
|
|
||||||
std::variant<
|
std::variant<
|
||||||
http::response<http::string_body>,
|
http::response<http::string_body>,
|
||||||
http::response<http::file_body>
|
http::response<http::file_body>
|
||||||
>
|
> RequestHandler::handle(const http::request<http::string_body>& request) {
|
||||||
RequestHandler::handle(const http::request<http::string_body>& request) {
|
|
||||||
std::string target = request.target();
|
std::string target = request.target();
|
||||||
http::verb method = request.method();
|
http::verb method = request.method();
|
||||||
|
|
||||||
if(method == http::verb::post){
|
auto& dbService = DatabaseService::getInstance("urls.db", 4);
|
||||||
if(target != "/") {
|
|
||||||
|
LOG(LogLevel::INFO, "Received request:");
|
||||||
|
LOG(LogLevel::INFO, "\tMethod: " + std::string(request.method_string()));
|
||||||
|
LOG(LogLevel::INFO, "\tTarget: " + target);
|
||||||
|
LOG(LogLevel::INFO, "\tBody: " + request.body());
|
||||||
|
|
||||||
|
if (method == http::verb::post) {
|
||||||
|
if (target != "/") {
|
||||||
return BadRequest("Cannot post to anything other than /");
|
return BadRequest("Cannot post to anything other than /");
|
||||||
}
|
}
|
||||||
if(request.find(http::field::content_type) == request.end()) {
|
if (request.find(http::field::content_type) == request.end()) {
|
||||||
return BadRequest("Content-Type header is required for POST requests");
|
return BadRequest("Content-Type header is required for POST requests");
|
||||||
}
|
}
|
||||||
auto content_type = request[http::field::content_type];
|
auto content_type = request[http::field::content_type];
|
||||||
if(content_type != "text/plain") {
|
if (content_type != "text/plain") {
|
||||||
return BadRequest("Content-Type must be text/plain");
|
return BadRequest("Content-Type must be text/plain");
|
||||||
}
|
}
|
||||||
std::string url = request.body();
|
|
||||||
std::regex url_regex("^(https?://)?(?:www\\.)?[-a-zA-Z0-9@%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&/=]*)$");
|
|
||||||
std::smatch url_match;
|
|
||||||
if(!std::regex_match(url, url_match, url_regex)) {
|
|
||||||
return BadRequest("Invalid URL");
|
|
||||||
}
|
|
||||||
if(!url_match[1].matched){
|
|
||||||
url = "https://" + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo: save url to database and return short url
|
std::string url = request.body();
|
||||||
|
std::regex url_regex("^(https?://)?(?:www\\.)?[-a-zA-Z0-9@%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&/=]*)$");
|
||||||
|
std::smatch url_match;
|
||||||
|
if (!std::regex_match(url, url_match, url_regex)) {
|
||||||
|
return BadRequest("Invalid URL");
|
||||||
|
}
|
||||||
|
if (!url_match[1].matched) {
|
||||||
|
url = "https://" + url;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(LogLevel::INFO, "Valid URL: " + url);
|
||||||
|
|
||||||
|
std::promise<std::string> promise;
|
||||||
|
std::future<std::string> future = promise.get_future();
|
||||||
|
dbService.shortenURL(url, [&promise](std::error_code ec, const std::string& shortURL) {
|
||||||
|
if (!ec) {
|
||||||
|
promise.set_value(shortURL);
|
||||||
|
} else {
|
||||||
|
promise.set_exception(std::make_exception_ptr(
|
||||||
|
std::runtime_error(ec.message())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::string shortURL = future.get();
|
||||||
|
LOG(LogLevel::INFO, "Shortened URL generated: " + shortURL);
|
||||||
http::response<http::string_body> response;
|
http::response<http::string_body> response;
|
||||||
response.result(http::status::created);
|
response.result(http::status::created);
|
||||||
response.set(http::field::server, "Beast");
|
response.set(http::field::server, "Beast");
|
||||||
response.set(http::field::content_type, "text/plain");
|
response.set(http::field::content_type, "text/plain");
|
||||||
response.body() = "127.0.0.1:8080/asdf";
|
response.body() = "127.0.0.1:8080/" + shortURL;
|
||||||
response.keep_alive(false);
|
response.keep_alive(false);
|
||||||
response.prepare_payload();
|
response.prepare_payload();
|
||||||
return response;
|
return response;
|
||||||
} else if (method == http::verb::get) {
|
} catch (const std::exception& e) {
|
||||||
if(target == "/"){
|
return BadRequest("Error generating short URL: " + std::string(e.what()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (method == http::verb::get) {
|
||||||
|
if (target == "/") {
|
||||||
|
LOG(LogLevel::INFO, "Serving the index.html file for target /");
|
||||||
target = "/index.html";
|
target = "/index.html";
|
||||||
}
|
}
|
||||||
if(target == "/index.html" || target == "/index.js") {
|
if (target == "/index.html" || target == "/index.js") {
|
||||||
error_code ec;
|
error_code ec;
|
||||||
http::response<http::file_body> response = handle_file_request("frontend" + target, ec);
|
http::response<http::file_body> response = handle_file_request("frontend" + target, ec);
|
||||||
if(ec) {
|
if (ec) {
|
||||||
|
LOG(LogLevel::ERROR, "Failed to read file: frontend" + target);
|
||||||
return BadRequest("Error reading file");
|
return BadRequest("Error reading file");
|
||||||
}else {
|
} else {
|
||||||
|
LOG(LogLevel::INFO, "Served file: frontend" + target);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::regex regex_pattern(".*/([^/]+)$");
|
||||||
|
std::smatch match;
|
||||||
|
std::string shortCode;
|
||||||
|
|
||||||
|
if (std::regex_match(target, match, regex_pattern)) {
|
||||||
|
shortCode = match[1];
|
||||||
|
LOG(LogLevel::INFO, "Extracted short code using regex: " + shortCode);
|
||||||
} else {
|
} else {
|
||||||
|
LOG(LogLevel::ERROR, "Failed to extract short code from target: " + target);
|
||||||
|
return BadRequest("Invalid short URL format");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::promise<std::string> promise;
|
||||||
|
std::future<std::string> future = promise.get_future();
|
||||||
|
|
||||||
|
dbService.getLongURL(shortCode, [&promise](std::error_code ec, const std::string& longURL) {
|
||||||
|
if (!ec) {
|
||||||
|
promise.set_value(longURL);
|
||||||
|
} else {
|
||||||
|
promise.set_exception(std::make_exception_ptr(
|
||||||
|
std::runtime_error(ec.message())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::string expandedURL = future.get();
|
||||||
|
LOG(LogLevel::INFO, "Expanded URL: " + expandedURL);
|
||||||
http::response<http::string_body> response;
|
http::response<http::string_body> response;
|
||||||
std::string short_url = target.substr(1);
|
|
||||||
|
|
||||||
std::string expanded_url = "https://google.com"; //todo: get expanded url from database
|
|
||||||
|
|
||||||
response.result(http::status::moved_permanently);
|
response.result(http::status::moved_permanently);
|
||||||
response.set(http::field::location, expanded_url);
|
response.set(http::field::location, expandedURL);
|
||||||
response.version(request.version());
|
response.version(request.version());
|
||||||
response.set(http::field::server, "Beast");
|
response.set(http::field::server, "Beast");
|
||||||
response.body() = "Redirecting to " + expanded_url;
|
response.body() = "Redirecting to " + expandedURL;
|
||||||
response.keep_alive(false);
|
response.keep_alive(false);
|
||||||
response.prepare_payload();
|
response.prepare_payload();
|
||||||
return response;
|
return response;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG(LogLevel::ERROR, "Failed to expand short URL: " + shortCode + ". Error: " + std::string(e.what()));
|
||||||
|
return BadRequest("Short URL not found or error: " + std::string(e.what()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return BadRequest("No rule matched.");
|
return BadRequest("No rule matched.");
|
||||||
}
|
}
|
||||||
http::response<http::file_body> RequestHandler::handle_file_request(const std::string& path, error_code &ec) {
|
|
||||||
|
http::response<http::file_body> RequestHandler::handle_file_request(const std::string& path, error_code& ec) {
|
||||||
http::file_body::value_type file;
|
http::file_body::value_type file;
|
||||||
file.open(path.c_str(), file_mode::read, ec);
|
file.open(path.c_str(), file_mode::read, ec);
|
||||||
|
if (!ec) {
|
||||||
|
LOG(LogLevel::INFO, "Successfully opened file: " + path);
|
||||||
|
} else {
|
||||||
|
LOG(LogLevel::ERROR, "Failed to open file: " + path + ". Error: " + ec.message());
|
||||||
|
}
|
||||||
|
|
||||||
http::response<http::file_body> response;
|
http::response<http::file_body> response;
|
||||||
if(!ec){
|
if (!ec) {
|
||||||
response.result(http::status::ok);
|
response.result(http::status::ok);
|
||||||
response.set(http::field::server, "Beast");
|
response.set(http::field::server, "Beast");
|
||||||
response.set(http::field::content_type, "text/html");
|
response.set(http::field::content_type, "text/html");
|
||||||
response.body() = std::move(file);
|
response.body() = std::move(file);
|
||||||
response.keep_alive(false);
|
response.keep_alive(false);
|
||||||
response.prepare_payload();
|
response.prepare_payload();
|
||||||
};
|
}
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in a new issue