150 lines
3.9 KiB
C++
150 lines
3.9 KiB
C++
#pragma once
|
|
|
|
#include "sockets/epoll.hpp"
|
|
#include "router.hpp"
|
|
#include "http_parser.hpp"
|
|
#include <iostream>
|
|
#include <string.h>
|
|
#include <string_view>
|
|
#include <array>
|
|
#include <string>
|
|
|
|
class HttpServer {
|
|
public:
|
|
explicit HttpServer(uint16_t port, Router& router) : socket_(port), router_(router) {
|
|
socket_.on_connection([this](int fd) { handle_connection(fd); });
|
|
socket_.on_data([this](int fd) { handle_data(fd); });
|
|
socket_.on_disconnect([this](int fd) { handle_disconnect(fd); });
|
|
}
|
|
|
|
bool start() { return socket_.start(); }
|
|
void run() { socket_.run(); }
|
|
void stop() { socket_.stop(); }
|
|
|
|
// Utility function to extract path parameters
|
|
static std::string get_path_param(string_view path, size_t segment_index = 0) {
|
|
size_t start = 0;
|
|
size_t current_segment = 0;
|
|
|
|
while (start < path.length()) {
|
|
if (path[start] == '/') {
|
|
start++;
|
|
continue;
|
|
}
|
|
|
|
size_t end = path.find('/', start);
|
|
if (end == std::string_view::npos) end = path.length();
|
|
|
|
if (current_segment == segment_index) {
|
|
return std::string(path.substr(start, end - start));
|
|
}
|
|
|
|
current_segment++;
|
|
start = end;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
private:
|
|
static constexpr int BUFFER_SIZE = 8192;
|
|
|
|
EpollSocket socket_;
|
|
Router& router_;
|
|
std::array<char, BUFFER_SIZE> buffer_;
|
|
|
|
void handle_connection(int client_fd) {
|
|
// Client connected
|
|
}
|
|
|
|
void handle_data(int client_fd) {
|
|
while (true) {
|
|
ssize_t bytes_read = read(client_fd, buffer_.data(), buffer_.size());
|
|
|
|
if (bytes_read == -1) {
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
|
|
return;
|
|
}
|
|
|
|
if (bytes_read == 0) return;
|
|
|
|
std::string request_data(buffer_.data(), bytes_read);
|
|
if (request_data.find("\r\n\r\n") != std::string::npos) {
|
|
process_request(client_fd, request_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void handle_disconnect(int client_fd) {
|
|
// Client disconnected
|
|
}
|
|
|
|
void process_request(int client_fd, std::string_view request_data) {
|
|
HttpRequest req = HttpParser::parse(request_data);
|
|
|
|
if (!req.valid) {
|
|
send_error_response(client_fd, "Bad Request", 400);
|
|
return;
|
|
}
|
|
|
|
HttpResponse response;
|
|
|
|
// Try to route the request (router will populate req.params)
|
|
if (router_.handle(req, response)) {
|
|
send_http_response(client_fd, response);
|
|
} else {
|
|
response.status = 404;
|
|
response.set_text("Not Found");
|
|
send_http_response(client_fd, response);
|
|
}
|
|
}
|
|
|
|
void send_http_response(int client_fd, const HttpResponse& response) {
|
|
std::string http_response = "HTTP/1.1 " + std::to_string(response.status);
|
|
|
|
switch (response.status) {
|
|
case 200: http_response += " OK"; break;
|
|
case 201: http_response += " Created"; break;
|
|
case 400: http_response += " Bad Request"; break;
|
|
case 404: http_response += " Not Found"; break;
|
|
case 500: http_response += " Internal Server Error"; break;
|
|
default: http_response += " Unknown"; break;
|
|
}
|
|
|
|
http_response += "\r\n";
|
|
http_response += "Content-Type: " + response.content_type + "\r\n";
|
|
http_response += "Content-Length: " + std::to_string(response.body.size()) + "\r\n";
|
|
|
|
// Add custom headers
|
|
for (const auto& [key, value] : response.headers) {
|
|
http_response += key + ": " + value + "\r\n";
|
|
}
|
|
|
|
http_response += "Connection: keep-alive\r\n\r\n";
|
|
http_response += response.body;
|
|
|
|
send_raw_response(client_fd, http_response);
|
|
}
|
|
|
|
void send_raw_response(int client_fd, const std::string& response) {
|
|
ssize_t total_sent = 0;
|
|
ssize_t response_len = response.size();
|
|
|
|
while (total_sent < response_len) {
|
|
ssize_t sent = write(client_fd, response.data() + total_sent, response_len - total_sent);
|
|
if (sent == -1) {
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
|
break;
|
|
}
|
|
total_sent += sent;
|
|
}
|
|
}
|
|
|
|
void send_error_response(int client_fd, const std::string& message, int status) {
|
|
HttpResponse response;
|
|
response.status = status;
|
|
response.set_text(message);
|
|
response.headers["Connection"] = "close";
|
|
send_http_response(client_fd, response);
|
|
}
|
|
};
|