feat: serve static pages
This commit is contained in:
parent
c5f7b82997
commit
bcd33dacd7
@ -62,7 +62,7 @@
|
||||
{ include: ["\"webserv/config/ConfigManager.hpp\"", "public", "<webserv/config/ConfigManager.hpp>", "public"] },
|
||||
{ include: ["\"webserv/config/ServerConfig.hpp\"", "public", "<webserv/config/ServerConfig.hpp>", "public"] },
|
||||
{ include: ["\"webserv/config/LocationConfig.hpp\"", "public", "<webserv/config/LocationConfig.hpp>", "public"] },
|
||||
{ include: ["\"webserv/config/utils.hpp\"", "public", "<webserv/config/utils.hpp>", "public"] },
|
||||
{ include: ["\i"webserv/utils/utls.hpp\"", "public", "<webserv/utils/utils.hpp>", "public"] },
|
||||
|
||||
{ include: ["\"webserv/server/Server.hpp\"", "public", "<webserv/server/Server.hpp>", "public"] },
|
||||
{ include: ["\"webserv/client/Client.hpp\"", "public", "<webserv/client/Client.hpp>", "public"] },
|
||||
|
||||
@ -26,7 +26,8 @@ server {
|
||||
|
||||
location / {
|
||||
autoindex off;
|
||||
index index.html;
|
||||
root ./static_site/;
|
||||
index index2.html index.html;
|
||||
allowed_methods GET POST DELETE;
|
||||
}
|
||||
|
||||
@ -37,7 +38,7 @@ server {
|
||||
}
|
||||
|
||||
location /images {
|
||||
root /var/www/images;
|
||||
root ./static_site/img;
|
||||
autoindex off;
|
||||
index index.jpg;
|
||||
allowed_methods GET;
|
||||
|
||||
BIN
static_site/img/nginy.jpg
Normal file
BIN
static_site/img/nginy.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
192
static_site/index.html
Normal file
192
static_site/index.html
Normal file
@ -0,0 +1,192 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebServ - HTTP Server</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Arial', sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 3.5rem;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
header p {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 2rem;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
color: #5a67d8;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.status {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 2rem;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #48bb78;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 3rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 0.8rem 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 25px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>WebServ</h1>
|
||||
<p>High-Performance HTTP Server</p>
|
||||
<div class="nav-links">
|
||||
<a href="/">Home</a>
|
||||
<a href="/about">About</a>
|
||||
<a href="/docs">Documentation</a>
|
||||
<a href="/test">Test Page</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="features">
|
||||
<div class="feature-card">
|
||||
<h3>⚡ Fast & Efficient</h3>
|
||||
<p>Built with performance in mind, handling multiple concurrent connections with minimal resource usage.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>🛡️ Secure</h3>
|
||||
<p>Implements robust security measures and follows best practices for web server development.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>🔧 Configurable</h3>
|
||||
<p>Highly customizable with flexible configuration options to suit your specific needs.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>📊 HTTP/1.1 Compliant</h3>
|
||||
<p>Full support for HTTP/1.1 protocol with proper request/response handling.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status">
|
||||
<h2>Server Status</h2>
|
||||
<p><span class="status-indicator"></span>Server is running and ready to serve requests</p>
|
||||
<p><strong>Version:</strong> 1.0.0</p>
|
||||
<p><strong>Started:</strong> <span id="timestamp"></span></p>
|
||||
</div>
|
||||
<img src="/images/nginy.jpg" alt="Nginx"
|
||||
style="display: block; margin: 2rem auto; max-width: 100%; border-radius: 15px; box-shadow: 0 8px 32px rgba(0,0,0,0.1);">
|
||||
<footer class="footer">
|
||||
<p>© 2024 WebServ Project. Built with passion for web technologies.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Display current timestamp
|
||||
document.getElementById('timestamp').textContent = new Date().toLocaleString();
|
||||
|
||||
// Update timestamp every second
|
||||
setInterval(() => {
|
||||
document.getElementById('timestamp').textContent = new Date().toLocaleString();
|
||||
}, 1000);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</footer>
|
||||
@ -115,7 +115,7 @@ std::vector<uint8_t> Client::getResponse() const
|
||||
|
||||
const Router &router = server_.getRouter();
|
||||
auto response = router.handleRequest(*httpRequest_);
|
||||
return response.toBytes();
|
||||
return response->toBytes();
|
||||
}
|
||||
|
||||
// void Client::setError(int statusCode)
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
#include <webserv/config/AConfig.hpp> // for AConfig
|
||||
|
||||
#include <webserv/config/directive/ADirective.hpp> // for ADirective
|
||||
#include <webserv/config/directive/DirectiveFactory.hpp> // for DirectiveFactory
|
||||
#include <webserv/config/utils.hpp> // for trim
|
||||
#include <webserv/log/Log.hpp> // for Log, LOCATION
|
||||
#include <webserv/utils/utils.hpp> // for trim
|
||||
|
||||
#include <sstream> // for basic_stringstream, stringstream
|
||||
#include <utility> // for move, pair
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
#include <webserv/config/directive/ADirective.hpp> // for ADirective
|
||||
#include <webserv/config/directive/DirectiveValue.hpp>
|
||||
|
||||
#include <map> // for map
|
||||
#include <memory> // for unique_ptr
|
||||
#include <optional>
|
||||
#include <string> // for string
|
||||
#include <vector> // for vector
|
||||
|
||||
@ -21,17 +21,17 @@ class AConfig
|
||||
virtual ~AConfig() = default;
|
||||
|
||||
void addDirective(const std::string &line);
|
||||
[[nodiscard]] const ADirective *getDirective(const std::string &name) const;
|
||||
[[nodiscard]] std::string getErrorPage(int statusCode) const;
|
||||
|
||||
[[nodiscard]] bool hasDirective(const std::string &name) const;
|
||||
|
||||
template <typename T> T getDirectiveValue(const std::string &name, const T &defaultValue = T{}) const
|
||||
template <typename T>
|
||||
std::optional<T> get(const std::string &name) const
|
||||
{
|
||||
const auto *directive = getDirective(name);
|
||||
if (!directive)
|
||||
{
|
||||
return defaultValue;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto value = directive->getValue();
|
||||
@ -39,10 +39,11 @@ class AConfig
|
||||
{
|
||||
return value.get<T>();
|
||||
}
|
||||
return defaultValue;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
protected:
|
||||
[[nodiscard]] const ADirective *getDirective(const std::string &name) const;
|
||||
virtual void parseBlock(const std::string &block) = 0;
|
||||
void parseDirectives(const std::string &declarations);
|
||||
std::vector<std::unique_ptr<ADirective>>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
#include <webserv/config/ConfigManager.hpp>
|
||||
|
||||
#include <webserv/config/GlobalConfig.hpp> // for GlobalConfig
|
||||
#include <webserv/config/utils.hpp> // for removeComments
|
||||
#include <webserv/log/Log.hpp> // for Log
|
||||
#include <webserv/utils/utils.hpp> // for removeComments
|
||||
|
||||
#include <fstream> // for basic_ifstream, basic_filebuf, basic_ostream::operator<<, ifstream, stringstream
|
||||
#include <sstream> // for basic_stringstream
|
||||
@ -86,8 +85,8 @@ ServerConfig *ConfigManager::getMatchingServerConfig(const std::string &host, in
|
||||
std::vector<ServerConfig *> serverConfigs = globalConfig_->getServerConfigs();
|
||||
for (ServerConfig *serverConfig : serverConfigs)
|
||||
{
|
||||
auto serverName = serverConfig->getDirectiveValue<std::string>("server_name", "");
|
||||
auto listenPorts = serverConfig->getDirectiveValue<int>("listen", 80);
|
||||
auto serverName = serverConfig->get<std::string>("server_name").value_or("");
|
||||
auto listenPorts = serverConfig->get<int>("listen").value_or(80);
|
||||
Log::debug("Checking server config: " + serverName + " on port " + std::to_string(listenPorts));
|
||||
if ((serverName == host) && (listenPorts == port))
|
||||
{
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#include <webserv/config/GlobalConfig.hpp>
|
||||
|
||||
#include <webserv/config/utils.hpp> // for findCorrespondingClosingBrace
|
||||
#include <webserv/log/Log.hpp> // for Log
|
||||
#include <webserv/utils/utils.hpp> // for findCorrespondingClosingBrace
|
||||
|
||||
#include <stdexcept> // for runtime_error
|
||||
#include <vector> // for vector
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#include <webserv/config/AConfig.hpp> // for AConfig
|
||||
#include <webserv/config/LocationConfig.hpp> // for LocationConfig
|
||||
#include <webserv/config/ServerConfig.hpp>
|
||||
#include <webserv/config/utils.hpp> // for findCorrespondingClosingBrace, trim
|
||||
#include <webserv/log/Log.hpp> // for Log, LOCATION
|
||||
#include <webserv/utils/utils.hpp> // for findCorrespondingClosingBrace, trim
|
||||
|
||||
#include <stdexcept> // for runtime_error
|
||||
#include <utility> // for pair
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
#include <webserv/config/directive/BoolDirective.hpp> // for IntDirective
|
||||
|
||||
#include <webserv/config/directive/ADirective.hpp> // for ADirective
|
||||
#include <webserv/config/directive/BoolDirective.hpp> // for IntDirective
|
||||
#include <webserv/config/directive/DirectiveValue.hpp> // for DirectiveValueType
|
||||
#include <webserv/config/utils.hpp> // for trim
|
||||
#include <webserv/utils/utils.hpp> // for trim
|
||||
|
||||
#include <algorithm> // for __transform_fn, transform
|
||||
#include <cctype> // for tolower
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
#include <webserv/config/directive/DirectiveFactory.hpp> // for DirectiveFactory
|
||||
|
||||
#include <webserv/config/directive/BoolDirective.hpp> // for BoolDirective
|
||||
#include <webserv/config/directive/DirectiveFactory.hpp> // for DirectiveFactory
|
||||
#include <webserv/config/directive/IntDirective.hpp> // for IntDirective
|
||||
#include <webserv/config/directive/IntStringDirective.hpp> // for IntStringDirective
|
||||
#include <webserv/config/directive/SizeDirective.hpp> // for SizeDirective
|
||||
#include <webserv/config/directive/StringDirective.hpp> // for StringDirective
|
||||
#include <webserv/config/directive/VectorDirective.hpp> // for VectorDirective
|
||||
#include <webserv/config/utils.hpp> // for trim, trimSemi
|
||||
#include <webserv/log/Log.hpp> // for LOCATION, Log
|
||||
#include <webserv/utils/utils.hpp> // for trim, trimSemi
|
||||
|
||||
#include <sstream> // for basic_stringstream, stringstream
|
||||
#include <stdexcept> // for invalid_argument
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
#include <webserv/config/directive/IntStringDirective.hpp> // for IntDirective
|
||||
|
||||
#include <webserv/config/directive/ADirective.hpp> // for ADirective
|
||||
#include <webserv/config/directive/DirectiveValue.hpp> // for DirectiveValueType
|
||||
#include <webserv/config/utils.hpp> // for trim
|
||||
#include <webserv/config/directive/IntStringDirective.hpp> // for IntDirective
|
||||
#include <webserv/utils/utils.hpp> // for trim
|
||||
|
||||
#include <sstream> // for basic_istringstream, basic_istream::operator>>, istringstream
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
#include <webserv/config/directive/SizeDirective.hpp> // for SizeDirective
|
||||
|
||||
#include <webserv/config/directive/ADirective.hpp> // for ADirective
|
||||
#include <webserv/config/directive/DirectiveValue.hpp> // for DirectiveValueType
|
||||
#include <webserv/config/utils.hpp> // for trim
|
||||
#include <webserv/config/directive/SizeDirective.hpp> // for SizeDirective
|
||||
#include <webserv/utils/utils.hpp> // for trim
|
||||
|
||||
#include <algorithm> // for __transform_fn, transform
|
||||
#include <cctype> // for tolower
|
||||
|
||||
@ -3,11 +3,12 @@
|
||||
#include "webserv/http/HttpResponse.hpp"
|
||||
|
||||
#include <webserv/config/AConfig.hpp>
|
||||
#include <memory>
|
||||
|
||||
class ErrorHandler
|
||||
{
|
||||
public:
|
||||
static HttpResponse getErrorResponse(int statusCode, AConfig *config = nullptr);
|
||||
static std::unique_ptr<HttpResponse> getErrorResponse(int statusCode, AConfig *config = nullptr);
|
||||
|
||||
private:
|
||||
static std::string generateErrorPage(int statusCode, AConfig *config = nullptr);
|
||||
|
||||
@ -8,18 +8,21 @@
|
||||
#include <webserv/log/Log.hpp> // for Log
|
||||
|
||||
#include <fstream> // for basic_ifstream, basic_filebuf, basic_ostream::operator<<, ifstream, stringstream
|
||||
#include <memory>
|
||||
#include <sstream> // for basic_stringstream
|
||||
#include <string> // for basic_string, operator+, allocator, char_traits, string, to_string
|
||||
|
||||
HttpResponse ErrorHandler::getErrorResponse(int statusCode, AConfig *config)
|
||||
std::unique_ptr<HttpResponse> ErrorHandler::getErrorResponse(int statusCode, AConfig *config)
|
||||
{
|
||||
HttpResponse response;
|
||||
std::string statusMessage = Http::getStatusCodeReason(statusCode);
|
||||
Log::warning("Generating error response: " + std::to_string(statusCode) + " " + statusMessage);
|
||||
|
||||
auto response = std::make_unique<HttpResponse>();
|
||||
std::string body = generateErrorPage(statusCode, config);
|
||||
response.appendBody(body);
|
||||
response.setStatus(statusCode);
|
||||
response.addHeader("Content-Type", "text/html");
|
||||
response.addHeader("Connection", "close");
|
||||
response->appendBody(body);
|
||||
response->setStatus(statusCode);
|
||||
response->addHeader("Content-Type", "text/html");
|
||||
response->addHeader("Connection", "close");
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -33,20 +36,20 @@ std::string ErrorHandler::generateErrorPage(int statusCode, AConfig *config)
|
||||
}
|
||||
if (config != nullptr)
|
||||
{
|
||||
Log::info("Checking for custom error page for status code: " + std::to_string(statusCode));
|
||||
Log::info("Checking for custom error page");
|
||||
std::string customPage = config->getErrorPage(statusCode);
|
||||
if (!customPage.empty())
|
||||
{
|
||||
return getErrorPageFile(customPage);
|
||||
}
|
||||
Log::warning("No custom error page found in config for status code: " + std::to_string(statusCode));
|
||||
Log::warning("No custom error page found");
|
||||
}
|
||||
return generateDefaultErrorPage(statusCode);
|
||||
}
|
||||
|
||||
std::string ErrorHandler::generateDefaultErrorPage(int statusCode)
|
||||
{
|
||||
Log::info("Generating default error page for status code: " + std::to_string(statusCode));
|
||||
Log::info("Generating default error page");
|
||||
std::string statusMessage = Http::getStatusCodeReason(statusCode);
|
||||
std::string html = "<html><head><title>" + std::to_string(statusCode) + " " + statusMessage +
|
||||
"</title></head><body><h1>" + std::to_string(statusCode) + " " + statusMessage +
|
||||
@ -56,7 +59,7 @@ std::string ErrorHandler::generateDefaultErrorPage(int statusCode)
|
||||
|
||||
std::string ErrorHandler::getErrorPageFile(const std::string &path)
|
||||
{
|
||||
Log::info("Loading custom error page from: " + path);
|
||||
Log::debug("Loading custom error page from: " + path);
|
||||
std::ifstream file(path.c_str());
|
||||
if (!file.is_open())
|
||||
{
|
||||
|
||||
125
webserv/handler/FileHandler.cpp
Normal file
125
webserv/handler/FileHandler.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
#include "webserv/handler/ErrorHandler.hpp"
|
||||
#include "webserv/http/HttpResponse.hpp"
|
||||
|
||||
#include <webserv/config/LocationConfig.hpp>
|
||||
#include <webserv/handler/FileHandler.hpp>
|
||||
#include <webserv/handler/MIMETypes.hpp>
|
||||
#include <webserv/handler/URIParser.hpp>
|
||||
#include <webserv/log/Log.hpp>
|
||||
#include <webserv/utils/FileUtils.hpp>
|
||||
|
||||
#include <cerrno> // for errno
|
||||
#include <cstring> // for strerror, strlen
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <ranges> // for views
|
||||
#include <string>
|
||||
#include <vector>
|
||||
// for FILE, fopen, fclose, fread, SEEK_END, SEEK
|
||||
#include <sys/stat.h> // for stat, S_ISREG, S_ISDIR
|
||||
|
||||
FileHandler::FileHandler(const LocationConfig *location, const URIParser &uriParser)
|
||||
: location_(location), uriParser_(uriParser)
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
}
|
||||
|
||||
std::vector<char> FileHandler::readBinaryFile(const std::string &filepath)
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
|
||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open())
|
||||
{
|
||||
Log::error("Failed to open file: " + filepath);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get file size
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
// Read entire file
|
||||
std::vector<char> buffer(size);
|
||||
if (!file.read(buffer.data(), size))
|
||||
{
|
||||
Log::error("Failed to read file: " + filepath);
|
||||
return {};
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string FileHandler::readFileAsString(const std::string &filepath)
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
|
||||
std::ifstream file(filepath, std::ios::binary);
|
||||
if (!file.is_open())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
std::unique_ptr<HttpResponse> FileHandler::getResponse() const
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
auto response = std::make_unique<HttpResponse>();
|
||||
response->setStatus(200);
|
||||
std::string filepath = uriParser_.getFilePath();
|
||||
if (uriParser_.isFile())
|
||||
{
|
||||
std::string extension = uriParser_.getExtension();
|
||||
std::string mimeType = MIMETypes().getType(extension);
|
||||
response->addHeader("Content-Type", mimeType);
|
||||
|
||||
std::vector<char> fileData = readBinaryFile(filepath);
|
||||
Log::debug("Serving file: " + filepath + " with MIME type: " + mimeType);
|
||||
if (fileData.empty())
|
||||
{
|
||||
return ErrorHandler::getErrorResponse(404);
|
||||
}
|
||||
response->setBody(std::string(fileData.begin(), fileData.end()));
|
||||
}
|
||||
else if (uriParser_.isDirectory() && location_->get<std::vector<std::string>>("index").has_value())
|
||||
{
|
||||
auto possible_indexes = location_->get<std::vector<std::string>>("index").value();
|
||||
std::string indexPath;
|
||||
for (auto &index : possible_indexes)
|
||||
{
|
||||
indexPath = FileUtils::joinPath(filepath, index);
|
||||
Log::debug("Checking for index file: " + indexPath);
|
||||
if (FileUtils::isFile(indexPath))
|
||||
{
|
||||
Log::debug("Found index file: " + indexPath);
|
||||
break;
|
||||
}
|
||||
indexPath.clear();
|
||||
}
|
||||
|
||||
std::vector<char> fileData = readBinaryFile(indexPath);
|
||||
Log::debug("Serving index file: " + indexPath);
|
||||
if (fileData.empty())
|
||||
{
|
||||
return ErrorHandler::getErrorResponse(404);
|
||||
}
|
||||
|
||||
std::string extension = FileUtils::getExtension(indexPath);
|
||||
std::string mimeType = MIMETypes().getType(extension);
|
||||
response->addHeader("Content-Type", mimeType);
|
||||
response->setBody(std::string(fileData.begin(), fileData.end()));
|
||||
}
|
||||
|
||||
else if (uriParser_.isDirectory() && location_->get<bool>("autoindex").value_or(false))
|
||||
{
|
||||
Log::debug("Requested path is a directory: " + filepath);
|
||||
return ErrorHandler::getErrorResponse(403);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ErrorHandler::getErrorResponse(404);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
@ -1 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "webserv/config/LocationConfig.hpp"
|
||||
#include "webserv/handler/URIParser.hpp"
|
||||
#include "webserv/http/HttpResponse.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
class FileHandler
|
||||
{
|
||||
public:
|
||||
FileHandler(const LocationConfig *location, const URIParser &uriParser);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<HttpResponse> getResponse() const;
|
||||
static std::vector<char> readBinaryFile(const std ::string &filepath);
|
||||
static std::string readFileAsString(const std::string &filepath);
|
||||
|
||||
private:
|
||||
// std::unique_ptr<HttpResponse> response_;
|
||||
const LocationConfig *location_;
|
||||
const URIParser &uriParser_;
|
||||
};
|
||||
37
webserv/handler/MIMETypes.cpp
Normal file
37
webserv/handler/MIMETypes.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
#include <webserv/handler/MIMETypes.hpp>
|
||||
|
||||
|
||||
|
||||
MIMETypes::MIMETypes() {
|
||||
initializeDefaults();
|
||||
}
|
||||
|
||||
std::string MIMETypes::getType(const std::string& extension) const {
|
||||
auto it = mimeMap.find(extension);
|
||||
if (it != mimeMap.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "application/octet-stream"; // Default MIME type
|
||||
}
|
||||
|
||||
void MIMETypes::setType(const std::string& extension, const std::string& mimeType) {
|
||||
mimeMap[extension] = mimeType;
|
||||
}
|
||||
|
||||
void MIMETypes::initializeDefaults() {
|
||||
mimeMap["html"] = "text/html";
|
||||
mimeMap["htm"] = "text/html";
|
||||
mimeMap["css"] = "text/css";
|
||||
mimeMap["js"] = "application/javascript";
|
||||
mimeMap["json"] = "application/json";
|
||||
mimeMap["png"] = "image/png";
|
||||
mimeMap["jpg"] = "image/jpeg";
|
||||
mimeMap["jpeg"] = "image/jpeg";
|
||||
mimeMap["gif"] = "image/gif";
|
||||
mimeMap["svg"] = "image/svg+xml";
|
||||
mimeMap["txt"] = "text/plain";
|
||||
mimeMap["xml"] = "application/xml";
|
||||
mimeMap["pdf"] = "application/pdf";
|
||||
// Add more default MIME types as needed
|
||||
}
|
||||
24
webserv/handler/MIMETypes.hpp
Normal file
24
webserv/handler/MIMETypes.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef MIMETYPES_HPP
|
||||
#define MIMETYPES_HPP
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
class MIMETypes {
|
||||
public:
|
||||
MIMETypes();
|
||||
|
||||
|
||||
// Returns the MIME type for a given file extension (e.g., "html" -> "text/html")
|
||||
[[nodiscard]] std::string getType(const std::string& extension) const;
|
||||
|
||||
// Adds or updates a MIME type mapping
|
||||
void setType(const std::string& extension, const std::string& mimeType);
|
||||
|
||||
private:
|
||||
std::map<std::string, std::string> mimeMap;
|
||||
|
||||
void initializeDefaults();
|
||||
};
|
||||
|
||||
#endif // MIMETYPES_HPP
|
||||
94
webserv/handler/URIParser.cpp
Normal file
94
webserv/handler/URIParser.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
#include <webserv/config/LocationConfig.hpp>
|
||||
#include <webserv/config/ServerConfig.hpp>
|
||||
#include <webserv/handler/URIParser.hpp>
|
||||
|
||||
#include <sys/stat.h> // for stat, S_ISREG, S_ISDIR
|
||||
|
||||
URIParser::URIParser(const std::string &uri, const ServerConfig &serverConfig) : _locationConfig(nullptr)
|
||||
{
|
||||
const auto &locations = serverConfig.getLocationPaths();
|
||||
size_t maxMatchLength = 0;
|
||||
|
||||
for (const auto &locationPath : locations)
|
||||
{
|
||||
if (uri.starts_with((locationPath == "/") ? locationPath : locationPath + "/"))
|
||||
{ // TODO HMHMMz why does it need to end on a /?
|
||||
if (locationPath.length() > maxMatchLength)
|
||||
{
|
||||
maxMatchLength = locationPath.length();
|
||||
_locationConfig = serverConfig.getLocation(locationPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root_ = _locationConfig != nullptr ? _locationConfig->get<std::string>("root").value_or("") : "";
|
||||
if (!root_.empty() && root_.back() == '/' )
|
||||
{
|
||||
root_.pop_back(); // Remove trailing slash to avoid double slashes in path
|
||||
}
|
||||
|
||||
relativePath_ = uri.substr(maxMatchLength);
|
||||
if (relativePath_.empty() || relativePath_[0] != '/')
|
||||
{
|
||||
relativePath_ = "/" + relativePath_;
|
||||
}
|
||||
}
|
||||
|
||||
std::string URIParser::getFilePath() const
|
||||
{
|
||||
return root_ + relativePath_;
|
||||
}
|
||||
|
||||
std::string URIParser::getFilename() const
|
||||
{
|
||||
size_t lastSlash = relativePath_.find_last_of('/');
|
||||
if (lastSlash == std::string::npos)
|
||||
{
|
||||
return relativePath_; // No slashes, return the whole path
|
||||
}
|
||||
return relativePath_.substr(lastSlash + 1);
|
||||
}
|
||||
|
||||
std::string URIParser::getExtension() const
|
||||
{
|
||||
std::string filename = getFilename();
|
||||
size_t lastDot = filename.find_last_of('.');
|
||||
if (lastDot == std::string::npos || lastDot == 0 || lastDot == filename.length() - 1)
|
||||
{
|
||||
return ""; // No extension found or dot is at start/end
|
||||
}
|
||||
return filename.substr(lastDot + 1);
|
||||
}
|
||||
|
||||
|
||||
LocationConfig const *URIParser::getLocation() const
|
||||
{
|
||||
return _locationConfig;
|
||||
}
|
||||
|
||||
bool URIParser::isFile() const
|
||||
{
|
||||
struct stat pathStat{};
|
||||
if (stat(getFilePath().c_str(), &pathStat) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return S_ISREG(pathStat.st_mode);
|
||||
}
|
||||
|
||||
bool URIParser::isDirectory() const
|
||||
{
|
||||
struct stat pathStat{};
|
||||
if (stat(getFilePath().c_str(), &pathStat) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return S_ISDIR(pathStat.st_mode);
|
||||
}
|
||||
|
||||
bool URIParser::isValid() const
|
||||
{
|
||||
struct stat pathStat{};
|
||||
return stat(getFilePath().c_str(), &pathStat) == 0;
|
||||
}
|
||||
|
||||
29
webserv/handler/URIParser.hpp
Normal file
29
webserv/handler/URIParser.hpp
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "webserv/config/LocationConfig.hpp"
|
||||
#include "webserv/config/ServerConfig.hpp"
|
||||
#include "webserv/server/Server.hpp"
|
||||
|
||||
class URIParser
|
||||
{
|
||||
public:
|
||||
URIParser(const std::string &uri, const ServerConfig &serverConfig);
|
||||
|
||||
|
||||
[[nodiscard]] std::string getFilePath() const;
|
||||
[[nodiscard]] std::string getFilename() const;
|
||||
[[nodiscard]] std::string getExtension() const;
|
||||
|
||||
|
||||
[[nodiscard]] const LocationConfig *getLocation() const ;
|
||||
|
||||
[[nodiscard]] bool isFile() const;
|
||||
|
||||
[[nodiscard]] bool isDirectory() const;
|
||||
[[nodiscard]] bool isValid() const;
|
||||
|
||||
private:
|
||||
const LocationConfig *_locationConfig;
|
||||
std::string relativePath_;
|
||||
std::string root_;
|
||||
};
|
||||
@ -1,8 +1,7 @@
|
||||
#include <webserv/http/HttpHeaders.hpp> // for HttpHeaders
|
||||
|
||||
#include <webserv/config/utils.hpp> // for trim
|
||||
#include <webserv/http/HttpConstants.hpp> // for CRLF
|
||||
#include <webserv/http/HttpHeaders.hpp> // for HttpHeaders
|
||||
#include <webserv/log/Log.hpp>
|
||||
#include <webserv/utils/utils.hpp> // for trim
|
||||
|
||||
#include <algorithm> // for __transform_fn, transform
|
||||
#include <cctype> // for tolower
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
#include <webserv/http/HttpRequest.hpp>
|
||||
|
||||
#include <webserv/client/Client.hpp> // for Client
|
||||
#include <webserv/config/utils.hpp> // for stoul
|
||||
#include <webserv/http/HttpConstants.hpp> // for CRLF, DOUBLE_CRLF, BAD_REQUEST
|
||||
#include <webserv/http/HttpRequest.hpp>
|
||||
#include <webserv/log/Log.hpp> // for Log, LOCATION
|
||||
#include <webserv/utils/utils.hpp> // for stoul
|
||||
|
||||
#include <map> // for map
|
||||
#include <optional> // for optional
|
||||
|
||||
@ -15,8 +15,8 @@ int main(int argc, char **argv)
|
||||
std::cerr << "Usage: " << argv[0] << " <config_file_path>\n"; // NOLINT
|
||||
return 1;
|
||||
}
|
||||
Log::setFileChannel("webserv.log", std::ios_base::app, Log::Level::Info);
|
||||
Log::setStdoutChannel(Log::Level::Info);
|
||||
Log::setFileChannel("webserv.log", std::ios_base::trunc, Log::Level::Info);
|
||||
Log::setStdoutChannel(Log::Level::Trace);
|
||||
Log::info("\n======================\nStarting webserv...\n======================\n");
|
||||
ConfigManager::getInstance().init(argv[1]); // NOLINT
|
||||
ConfigManager &configManager = ConfigManager::getInstance();
|
||||
|
||||
@ -2,80 +2,46 @@
|
||||
#include "webserv/config/ConfigManager.hpp"
|
||||
#include "webserv/config/ServerConfig.hpp"
|
||||
#include "webserv/handler/ErrorHandler.hpp"
|
||||
#include "webserv/handler/FileHandler.hpp"
|
||||
#include "webserv/handler/URIParser.hpp"
|
||||
#include "webserv/http/HttpResponse.hpp"
|
||||
#include "webserv/log/Log.hpp"
|
||||
|
||||
#include <webserv/router/Router.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
Router::Router() {}
|
||||
|
||||
HttpResponse Router::handleRequest(const HttpRequest &request) const
|
||||
std::unique_ptr<HttpResponse> Router::handleRequest(const HttpRequest &request) const
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
|
||||
ServerConfig *config =
|
||||
ConfigManager::getInstance().getMatchingServerConfig(request.getHeaders().getHost().value_or(""));
|
||||
HttpResponse response;
|
||||
|
||||
if (config == nullptr)
|
||||
{
|
||||
Log::warning("No matching server config found");
|
||||
handleError(400, response);
|
||||
return response;
|
||||
return ErrorHandler::getErrorResponse(400);
|
||||
}
|
||||
URIParser uriParser{request.getTarget(), *config};
|
||||
|
||||
const std::string &target = request.getTarget();
|
||||
const std::string &method = request.getMethod();
|
||||
|
||||
const LocationConfig *location = getLocation(target, *config);
|
||||
const LocationConfig *location = uriParser.getLocation();
|
||||
if (location == nullptr)
|
||||
{
|
||||
Log::warning("No matching location found for target: " + target);
|
||||
handleError(404, response, config);
|
||||
return response;
|
||||
return ErrorHandler::getErrorResponse(404);
|
||||
}
|
||||
|
||||
response.setStatus(200);
|
||||
response.addHeader("Content-Type", "text/plain");
|
||||
response.setBody("Hello, World! You requested " + target + " on " + request.getHeaders().getHost().value_or("") +
|
||||
". Current resource is " + location->getPath());
|
||||
response.setComplete();
|
||||
return response;
|
||||
FileHandler fileHandler(location, uriParser);
|
||||
return fileHandler.getResponse();
|
||||
}
|
||||
|
||||
void Router::handleError(int statusCode, HttpResponse &response, AConfig *config) const
|
||||
{
|
||||
response = ErrorHandler::getErrorResponse(statusCode, config);
|
||||
response.setComplete();
|
||||
}
|
||||
|
||||
const LocationConfig *Router::getLocation(const std::string &path, const ServerConfig &serverConfig) const
|
||||
{
|
||||
const LocationConfig *bestMatch = nullptr;
|
||||
size_t maxSize = 0;
|
||||
|
||||
std::vector<std::string> locations = serverConfig.getLocationPaths();
|
||||
|
||||
for (const auto &locPath : locations)
|
||||
{
|
||||
if (!path.starts_with(locPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (locPath.length() > maxSize &&
|
||||
(path.length() == locPath.length() || locPath.back() == '/' || path[locPath.length()] == '/'))
|
||||
{
|
||||
maxSize = locPath.length();
|
||||
bestMatch = serverConfig.getLocation(locPath);
|
||||
Log::debug("Found new best match: " + locPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch == nullptr)
|
||||
{
|
||||
Log::warning("No location matched for path: " + path);
|
||||
}
|
||||
return bestMatch;
|
||||
}
|
||||
// void Router::handleError(int statusCode, HttpResponse &response, AConfig *config) const
|
||||
// {
|
||||
// response = ErrorHandler::getErrorResponse(statusCode, config);
|
||||
// response.setComplete();
|
||||
// }
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <webserv/http/HttpRequest.hpp>
|
||||
#include <webserv/http/HttpResponse.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Router
|
||||
@ -12,9 +13,9 @@ class Router
|
||||
public:
|
||||
Router();
|
||||
|
||||
[[nodiscard]] HttpResponse handleRequest(const HttpRequest &request) const;
|
||||
[[nodiscard]] std::unique_ptr<HttpResponse> handleRequest(const HttpRequest &request) const;
|
||||
|
||||
private:
|
||||
void handleError(int statusCode, HttpResponse &response, AConfig *config = nullptr) const;
|
||||
// void handleError(int statusCode, HttpResponse &response, AConfig *config = nullptr) const;
|
||||
[[nodiscard]] const LocationConfig *getLocation(const std::string &path, const ServerConfig &serverConfig) const;
|
||||
};
|
||||
@ -102,8 +102,9 @@ void Server::setupServerSocket(const ServerConfig &config)
|
||||
Log::trace(LOCATION);
|
||||
try
|
||||
{
|
||||
auto host = config.getDirectiveValue<std::string>("host");
|
||||
auto port = config.getDirectiveValue<int>("listen");
|
||||
auto host = config.get<std::string>("host").value_or(std::string()); // TODO should not be a default host
|
||||
|
||||
auto port = config.get<int>("listen").value_or(0); // TODO should not be a default port
|
||||
std::unique_ptr<Socket> serverSocket = std::make_unique<Socket>();
|
||||
serverSocket->bind(host, port);
|
||||
serverSocket->listen(SOMAXCONN);
|
||||
|
||||
61
webserv/utils/FileUtils.cpp
Normal file
61
webserv/utils/FileUtils.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include <webserv/utils/FileUtils.hpp>
|
||||
|
||||
#include <cstring> // for strlen
|
||||
#include <string> // for string
|
||||
|
||||
#include <sys/stat.h> // for stat, S_ISREG, S_ISDIR
|
||||
|
||||
namespace FileUtils
|
||||
{
|
||||
bool isDirectory(const std::string &path)
|
||||
{
|
||||
struct stat pathStat{};
|
||||
if (stat(path.c_str(), &pathStat) != 0)
|
||||
{
|
||||
return false; // Could not access path
|
||||
}
|
||||
return S_ISDIR(pathStat.st_mode);
|
||||
}
|
||||
|
||||
bool isFile(const std::string &path)
|
||||
{
|
||||
struct stat pathStat{};
|
||||
if (stat(path.c_str(), &pathStat) != 0)
|
||||
{
|
||||
return false; // Could not access path
|
||||
}
|
||||
return S_ISREG(pathStat.st_mode);
|
||||
}
|
||||
|
||||
std::string getExtension(const std::string &filename)
|
||||
{
|
||||
size_t dotPos = filename.find_last_of('.');
|
||||
if (dotPos == std::string::npos || dotPos == filename.length() - 1)
|
||||
{
|
||||
return ""; // No extension found
|
||||
}
|
||||
return filename.substr(dotPos + 1);
|
||||
}
|
||||
|
||||
std::string joinPath(const std::string &base, const std::string &addition) // NOLINT
|
||||
{
|
||||
std::string result = base;
|
||||
if (result.empty())
|
||||
{
|
||||
result = addition;
|
||||
}
|
||||
else if (result.back() == '/' && addition.front() == '/')
|
||||
{
|
||||
result += addition.substr(1);
|
||||
}
|
||||
else if (result.back() != '/' && addition.front() != '/')
|
||||
{
|
||||
result += '/' + addition;
|
||||
}
|
||||
else
|
||||
{
|
||||
result += addition;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace FileUtils
|
||||
11
webserv/utils/FileUtils.hpp
Normal file
11
webserv/utils/FileUtils.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace FileUtils
|
||||
{
|
||||
bool isDirectory(const std::string &path);
|
||||
bool isFile(const std::string &path);
|
||||
std::string getExtension(const std::string &filename);
|
||||
std::string joinPath(const std::string &base, const std::string &addition);
|
||||
} // namespace FileUtils
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
#include <webserv/config/utils.hpp>
|
||||
#include <webserv/utils/utils.hpp>
|
||||
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
Loading…
Reference in New Issue
Block a user