works?
This commit is contained in:
parent
a52f27d6b7
commit
5d4265eab3
@ -83,6 +83,8 @@ server {
|
||||
|
||||
cgi_enabled yes;
|
||||
cgi_handler .php /usr/bin/php-cgi;
|
||||
# cgi_handler .py /usr/bin/python3;
|
||||
# cgi_handler .sh;
|
||||
client_max_body_size 10M;
|
||||
}
|
||||
|
||||
|
||||
@ -2,38 +2,35 @@
|
||||
<?php
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', __DIR__ . '/error_log.txt');
|
||||
|
||||
|
||||
$target_dir = "uploads/";
|
||||
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
|
||||
$uploadOk = 1;
|
||||
|
||||
function handle_upload($file) {
|
||||
global $target_dir;
|
||||
$target_file = $target_dir . basename($file["name"]);
|
||||
$imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
|
||||
|
||||
// add debugging output to help trace issues
|
||||
|
||||
echo "\n=== POST Data ===\n";
|
||||
$input = file_get_contents('php://input');
|
||||
echo "Raw input length: " . strlen($input) . "\n";
|
||||
echo "Raw input (first 200 chars): " . substr($input, 0, 200) . "\n";
|
||||
|
||||
echo "\n=== $_FILES ===\n";
|
||||
var_dump($_FILES);
|
||||
|
||||
echo "\n=== $_POST ===\n";
|
||||
var_dump($_POST);
|
||||
|
||||
// Check if $uploadOk is set to 0 by an error
|
||||
if ($uploadOk == 0) {
|
||||
echo "Sorry, your file was not uploaded.";
|
||||
// if everything is ok, try to upload file
|
||||
if (move_uploaded_file($file["tmp_name"], $target_file)) {
|
||||
echo "Uploaded: " . htmlspecialchars($file["name"]) . "\n";
|
||||
} else {
|
||||
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
|
||||
echo "The file ". htmlspecialchars( basename( $_FILES["fileToUpload"]["name"])). " has been uploaded.";
|
||||
} else {
|
||||
echo "Sorry, there was an error uploading your file.";
|
||||
echo "Error uploading: " . htmlspecialchars($file["name"]) . "\n";
|
||||
var_dump(error_get_last());
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_FILES["fileToUpload"]["name"])) {
|
||||
// Single or multiple file handling
|
||||
if (is_array($_FILES["fileToUpload"]["name"])) {
|
||||
foreach ($_FILES["fileToUpload"]["name"] as $i => $name) {
|
||||
handle_upload([
|
||||
"name" => $_FILES["fileToUpload"]["name"][$i],
|
||||
"tmp_name" => $_FILES["fileToUpload"]["tmp_name"][$i]
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
handle_upload($_FILES["fileToUpload"]);
|
||||
}
|
||||
} else {
|
||||
echo "No files received.\n";
|
||||
}
|
||||
?>
|
||||
|
||||
35
htdocs/site-1/upload.py
Normal file
35
htdocs/site-1/upload.py
Normal file
@ -0,0 +1,35 @@
|
||||
## CGI Script to handle file uploads
|
||||
|
||||
import cgi
|
||||
import os
|
||||
import sys
|
||||
|
||||
print("Content-Type: text/plain")
|
||||
print() # End of headers
|
||||
|
||||
def main():
|
||||
form = cgi.FieldStorage()
|
||||
|
||||
# Directory to save uploaded files
|
||||
upload_dir = "/home/qmennen/Documents/webserv/htdocs/site-1/uploads"
|
||||
if not os.path.exists(upload_dir):
|
||||
os.makedirs(upload_dir)
|
||||
|
||||
# Process each file in the form
|
||||
for field in form.keys():
|
||||
field_item = form[field]
|
||||
if field_item.filename:
|
||||
# Get the filename and file data
|
||||
filename = os.path.basename(field_item.filename)
|
||||
file_data = field_item.file.read()
|
||||
|
||||
# Save the file to the upload directory
|
||||
with open(os.path.join(upload_dir, filename), 'wb') as f:
|
||||
f.write(file_data)
|
||||
|
||||
print(f"Uploaded file: {filename} ({len(file_data)} bytes) to {upload_dir}")
|
||||
else:
|
||||
print(f"No file uploaded for field: {field}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
4
htdocs/site-1/upload.sh
Executable file
4
htdocs/site-1/upload.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
cat > ./uploaded_file.txt
|
||||
echo "File uploaded successfully."
|
||||
@ -74,7 +74,7 @@ void Client::request()
|
||||
}
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
Log::info("Client closed connection, fd: " + std::to_string(clientSocket_->getFd()));
|
||||
Log::info("Client closed connection: " + clientSocket_->toString());
|
||||
server_.disconnect(*this); // CRITICAL: RETURN IMMEDIATELY
|
||||
return;
|
||||
}
|
||||
@ -168,7 +168,7 @@ void Client::poll() const
|
||||
}
|
||||
if (httpResponse_->isComplete() && clientSocket_->getEvent() != ASocket::IoState::WRITE)
|
||||
{
|
||||
Log::info("Response is ready to be sent to client, fd: " + std::to_string(clientSocket_->getFd()));
|
||||
Log::info("Response is ready to be sent to client: " + clientSocket_->toString());
|
||||
clientSocket_->setCallback([this]() { respond(); });
|
||||
// server_.writable(clientSocket_->getFd());
|
||||
clientSocket_->setIOState(ASocket::IoState::WRITE);
|
||||
@ -181,11 +181,11 @@ void Client::respond() const
|
||||
ssize_t bytesSent = send(clientSocket_->getFd(), payload.data(), payload.size(), 0);
|
||||
if (bytesSent < 0)
|
||||
{
|
||||
Log::error("Send failed for fd: " + std::to_string(clientSocket_->getFd()));
|
||||
Log::error("Send failed for: " + clientSocket_->toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::debug("Sent " + std::to_string(bytesSent) + " bytes to fd: " + std::to_string(clientSocket_->getFd()));
|
||||
Log::debug("Sent " + std::to_string(bytesSent) + " bytes to: " + clientSocket_->toString());
|
||||
}
|
||||
server_.disconnect(*this); // ! CRITICAL: RETURN IMMEDIATELY
|
||||
}
|
||||
|
||||
@ -76,6 +76,16 @@ char **CgiEnvironment::toEnvp() const
|
||||
return envp;
|
||||
}
|
||||
|
||||
std::string CgiEnvironment::get(const std::string &key) const
|
||||
{
|
||||
auto it = env_.find(key);
|
||||
if (it != env_.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void CgiEnvironment::appendCustomHeaders(const HttpHeaders &headers)
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
|
||||
@ -15,6 +15,7 @@ class CgiEnvironment
|
||||
public:
|
||||
CgiEnvironment(const URI &uri, const HttpRequest &request);
|
||||
|
||||
std::string get(const std::string &key) const;
|
||||
[[nodiscard]] char **toEnvp() const;
|
||||
|
||||
private:
|
||||
|
||||
@ -72,34 +72,26 @@ void CgiHandler::write()
|
||||
return;
|
||||
}
|
||||
const std::string &body = request_.getBody();
|
||||
size_t before = writeOffset_;
|
||||
while (writeOffset_ < body.size())
|
||||
|
||||
if (writeOffset_ < body.size())
|
||||
{
|
||||
const char *data = body.data() + writeOffset_; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||
size_t remaining = body.size() - writeOffset_;
|
||||
size_t chunk = remaining > CHUNK_SIZE ? CHUNK_SIZE : remaining;
|
||||
ssize_t bytesRead = cgiStdIn_->write(data, chunk);
|
||||
if (bytesRead > 0)
|
||||
ssize_t bytesWritten = cgiStdIn_->write(data, chunk);
|
||||
if (bytesWritten > 0)
|
||||
{
|
||||
writeOffset_ += static_cast<size_t>(bytesRead);
|
||||
}
|
||||
else
|
||||
{
|
||||
break; // would block or peer closed; try again on next EPOLLOUT
|
||||
writeOffset_ += static_cast<size_t>(bytesWritten);
|
||||
Log::debug("Wrote " + std::to_string(bytesWritten) + " bytes, write offset " + std::to_string(writeOffset_)
|
||||
+ "/ " + std::to_string(body.size()));
|
||||
}
|
||||
}
|
||||
|
||||
if (writeOffset_ >= body.size())
|
||||
{
|
||||
Log::debug("CGI stdin sent " + std::to_string(body.size()) + " bytes, closing write end");
|
||||
request_.getClient().removeSocket(cgiStdIn_.get());
|
||||
cgiStdIn_.reset();
|
||||
bodyWriteCompleted_ = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::debug("Wrote " + std::to_string(writeOffset_ - before) + " bytes, write offset "
|
||||
+ std::to_string(writeOffset_) + "/ " + std::to_string(body.size()));
|
||||
// Log::debug("CGI stdin progress " + std::to_string(before) + "→" + std::to_string(writeOffset_));
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +139,7 @@ void CgiHandler::read()
|
||||
responseComplete = (buffer_.size() >= contentLength_.value());
|
||||
}
|
||||
|
||||
if (bodyWriteCompleted_ && responseComplete)
|
||||
if (responseComplete)
|
||||
{
|
||||
Log::debug("Response complete: headers parsed and content received");
|
||||
request_.getClient().removeSocket(cgiStdOut_.get());
|
||||
@ -181,37 +173,17 @@ void CgiHandler::read()
|
||||
}
|
||||
|
||||
// Only finalize if we've finished writing the request body
|
||||
if (bodyWriteCompleted_)
|
||||
{
|
||||
finalizeCgiResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::warning("CGI process closed stdout before request body was completely written");
|
||||
// Set error response but don't finalize until write is complete
|
||||
// ErrorHandler::createErrorResponse(500, response_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytesRead < 0)
|
||||
{
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
{
|
||||
Log::debug("CGI stdout would block, will retry on next EPOLLIN");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Log::error("Error reading from CGI stdout: " + std::string(strerror(errno)));
|
||||
// Only finalize if write is complete
|
||||
if (bodyWriteCompleted_)
|
||||
{
|
||||
finalizeCgiResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CgiHandler::error()
|
||||
{
|
||||
|
||||
@ -37,7 +37,7 @@ class CgiHandler : public AHandler
|
||||
void handleTimeout() override;
|
||||
|
||||
private:
|
||||
constexpr static size_t CHUNK_SIZE = 32768;
|
||||
constexpr static size_t CHUNK_SIZE = 65536; // 64kb
|
||||
constexpr static size_t bufferSize_ = 8192; // TODO: remove duplicate definition and move to configmanager
|
||||
std::vector<uint8_t> buffer_;
|
||||
|
||||
@ -54,7 +54,6 @@ class CgiHandler : public AHandler
|
||||
int pid_ = -1;
|
||||
size_t writeOffset_ = 0;
|
||||
bool headersParsed_ = false;
|
||||
bool bodyWriteCompleted_ = false;
|
||||
std::optional<size_t> contentLength_;
|
||||
|
||||
void write();
|
||||
|
||||
@ -1,254 +0,0 @@
|
||||
CgiHandler::CgiHandler(const HttpRequest &request, HttpResponse &response)
|
||||
: AHandler(request, response), cgiProcess_(nullptr), cgiStdIn_(nullptr), cgiStdOut_(nullptr)
|
||||
{
|
||||
Log::debug("CgiHandler constructed");
|
||||
bodyWriteOffset_ = 0;
|
||||
headersParsed_ = false;
|
||||
expectedBody_.reset();
|
||||
}
|
||||
|
||||
void CgiHandler::setCgiSockets(std::unique_ptr<CgiSocket> cgiStdIn, std::unique_ptr<CgiSocket> cgiStdOut,
|
||||
std::unique_ptr<CgiSocket> cgiStdErr)
|
||||
{
|
||||
cgiStdIn->setCallback([this]() { write(); });
|
||||
cgiStdOut->setCallback([this]() { read(); });
|
||||
cgiStdErr->setCallback([this]() { error(); });
|
||||
|
||||
cgiStdOut_ = std::move(cgiStdOut);
|
||||
cgiStdIn_ = std::move(cgiStdIn);
|
||||
cgiStdErr_ = std::move(cgiStdErr);
|
||||
|
||||
request_.getClient().addSocket(cgiStdIn_.get());
|
||||
request_.getClient().addSocket(cgiStdOut_.get());
|
||||
request_.getClient().addSocket(cgiStdErr_.get());
|
||||
// Ensure stdout/stderr are set to READ interest; stdin to WRITE only if there's a body
|
||||
cgiStdOut_->setIOState(ASocket::IoState::READ);
|
||||
cgiStdOut_->markDirty();
|
||||
cgiStdErr_->setIOState(ASocket::IoState::READ);
|
||||
cgiStdErr_->markDirty();
|
||||
if (!request_.getBody().empty())
|
||||
{
|
||||
cgiStdIn_->setIOState(ASocket::IoState::WRITE);
|
||||
cgiStdIn_->markDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
request_.getClient().removeSocket(cgiStdIn_.get());
|
||||
cgiStdIn_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void CgiHandler::write()
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
if (!cgiStdIn_) return;
|
||||
const std::string &body = request_.getBody();
|
||||
if (bodyWriteOffset_ >= body.size())
|
||||
{
|
||||
request_.getClient().removeSocket(cgiStdIn_.get());
|
||||
cgiStdIn_.reset();
|
||||
return;
|
||||
}
|
||||
// Stream body until pipe stops accepting data; no errno checks needed (level-triggered epoll)
|
||||
size_t before = bodyWriteOffset_;
|
||||
while (bodyWriteOffset_ < body.size())
|
||||
{
|
||||
const char *data = body.data() + bodyWriteOffset_;
|
||||
size_t remain = body.size() - bodyWriteOffset_;
|
||||
size_t chunk = remain > 32768 ? 32768 : remain;
|
||||
ssize_t n = cgiStdIn_->write(data, chunk);
|
||||
if (n > 0)
|
||||
{
|
||||
bodyWriteOffset_ += static_cast<size_t>(n);
|
||||
}
|
||||
else
|
||||
{
|
||||
break; // would block or peer closed; try again on next EPOLLOUT
|
||||
}
|
||||
}
|
||||
if (bodyWriteOffset_ >= body.size())
|
||||
{
|
||||
Log::debug("CGI stdin sent " + std::to_string(body.size()) + " bytes, closing write end");
|
||||
request_.getClient().removeSocket(cgiStdIn_.get());
|
||||
cgiStdIn_.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::debug("CGI stdin progress " + std::to_string(before) + "→" + std::to_string(bodyWriteOffset_));
|
||||
cgiStdIn_->setIOState(ASocket::IoState::WRITE);
|
||||
cgiStdIn_->markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool findHeaderEnd(const std::string &s, size_t &pos, long &sepSize)
|
||||
{
|
||||
size_t a = s.find("\r\n\r\n");
|
||||
size_t b = s.find("\n\n");
|
||||
size_t c = s.find("\r\r");
|
||||
size_t end = std::min({a, b, c});
|
||||
if (end == std::string::npos) return false;
|
||||
sepSize = (end == a) ? 4 : 2;
|
||||
pos = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CgiHandler::read()
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
if (!cgiStdOut_) return;
|
||||
// Drain as much as available this callback
|
||||
for (;;)
|
||||
{
|
||||
char buffer[bufferSize_] = {};
|
||||
ssize_t n = cgiStdOut_->read(buffer, sizeof(buffer));
|
||||
if (n > 0)
|
||||
{
|
||||
appendToBuffer(buffer, static_cast<size_t>(n));
|
||||
// Parse headers once, as soon as we have them
|
||||
if (!headersParsed_)
|
||||
{
|
||||
size_t headerEnd = 0;
|
||||
long sepSize = 0;
|
||||
std::string snapshot(buffer_.begin(), buffer_.end());
|
||||
if (findHeaderEnd(snapshot, headerEnd, sepSize))
|
||||
{
|
||||
std::string headers(snapshot.begin(), snapshot.begin() + static_cast<long>(headerEnd));
|
||||
parseCgiHeaders(headers);
|
||||
// After headers parsed, remove them from buffer_ so it contains only body
|
||||
buffer_.erase(buffer_.begin(), buffer_.begin() + static_cast<long>(headerEnd) + sepSize);
|
||||
headersParsed_ = true;
|
||||
// Capture expected body size if provided
|
||||
std::string cl = response_.getHeaders().get("Content-Length");
|
||||
if (!cl.empty())
|
||||
{
|
||||
expectedBody_ = static_cast<size_t>(std::strtoul(cl.c_str(), nullptr, 10));
|
||||
}
|
||||
else
|
||||
{
|
||||
expectedBody_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we know Content-Length and already have it, finalize
|
||||
if (headersParsed_ && expectedBody_.has_value() && buffer_.size() >= expectedBody_.value())
|
||||
{
|
||||
request_.getClient().removeSocket(cgiStdOut_.get());
|
||||
cgiStdOut_.reset();
|
||||
finalizeCgiResponse();
|
||||
return;
|
||||
}
|
||||
continue; // try to read more this tick
|
||||
}
|
||||
else if (n == 0)
|
||||
{
|
||||
// EOF → finalize with whatever body we have
|
||||
Log::info("CGI process closed stdout, fd: " + std::to_string(cgiStdOut_->getFd()));
|
||||
request_.getClient().removeSocket(cgiStdOut_.get());
|
||||
cgiStdOut_.reset();
|
||||
// If headers not parsed yet, try once more (some scripts may end with only headers)
|
||||
if (!headersParsed_)
|
||||
{
|
||||
size_t headerEnd = 0;
|
||||
long sep = 0;
|
||||
std::string snap(buffer_.begin(), buffer_.end());
|
||||
if (findHeaderEnd(snap, headerEnd, sep))
|
||||
{
|
||||
std::string headers(snap.begin(), snap.begin() + static_cast<long>(headerEnd));
|
||||
parseCgiHeaders(headers);
|
||||
buffer_.erase(buffer_.begin(), buffer_.begin() + static_cast<long>(headerEnd) + sep);
|
||||
headersParsed_ = true;
|
||||
}
|
||||
}
|
||||
finalizeCgiResponse();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Would block or transient error; wait for next EPOLLIN
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CgiHandler::error()
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
if (!cgiStdErr_) return;
|
||||
for (;;)
|
||||
{
|
||||
char buffer[bufferSize_] = {};
|
||||
ssize_t n = cgiStdErr_->read(buffer, sizeof(buffer));
|
||||
if (n > 0)
|
||||
{
|
||||
appendToBuffer(buffer, static_cast<size_t>(n));
|
||||
Log::error("CGI stderr output (fd: " + std::to_string(cgiStdErr_->getFd())
|
||||
+ "): " + std::string(buffer, static_cast<size_t>(n)));
|
||||
continue;
|
||||
}
|
||||
else if (n == 0)
|
||||
{
|
||||
Log::info("CGI process closed stderr, fd: " + std::to_string(cgiStdErr_->getFd()));
|
||||
request_.getClient().removeSocket(cgiStdErr_.get());
|
||||
cgiStdErr_.reset();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CgiHandler::parseCgiOutput()
|
||||
{
|
||||
// Changed: only parse headers if present; do NOT finalize here
|
||||
Log::trace(LOCATION);
|
||||
if (headersParsed_) return;
|
||||
size_t headerEnd = 0;
|
||||
long sepSize = 0;
|
||||
std::string header(buffer_.begin(), buffer_.end());
|
||||
if (!findHeaderEnd(header, headerEnd, sepSize))
|
||||
{
|
||||
Log::debug("CGI output headers not complete yet");
|
||||
return;
|
||||
}
|
||||
std::string headers(header.begin(), header.begin() + static_cast<long>(headerEnd));
|
||||
Log::debug("CGI output headers: " + headers);
|
||||
parseCgiHeaders(headers);
|
||||
buffer_.erase(buffer_.begin(), buffer_.begin() + static_cast<long>(headerEnd) + sepSize);
|
||||
headersParsed_ = true;
|
||||
std::string cl = response_.getHeaders().get("Content-Length");
|
||||
if (!cl.empty())
|
||||
expectedBody_ = static_cast<size_t>(std::strtoul(cl.c_str(), nullptr, 10));
|
||||
else
|
||||
expectedBody_.reset();
|
||||
}
|
||||
|
||||
void CgiHandler::parseCgiHeaders(std::string &headers)
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
// ...existing code...
|
||||
// At end, capture Content-Length if present
|
||||
std::string cl = response_.getHeaders().get("Content-Length");
|
||||
if (!cl.empty()) expectedBody_ = static_cast<size_t>(std::strtoul(cl.c_str(), nullptr, 10));
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
void CgiHandler::finalizeCgiResponse()
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
auto status = response_.getHeaders().get("Status");
|
||||
wait();
|
||||
if (cgiProcess_ && cgiProcess_->getExitCode() > 0 && status.empty())
|
||||
{
|
||||
response_.setStatus(500);
|
||||
}
|
||||
else if (!status.empty())
|
||||
{
|
||||
response_.setStatus(std::atoi(status.c_str()));
|
||||
}
|
||||
// Append only the body (headers already stripped)
|
||||
response_.appendBody(buffer_);
|
||||
response_.setComplete();
|
||||
buffer_.clear();
|
||||
}
|
||||
@ -98,9 +98,9 @@ void CgiProcess::spawn()
|
||||
else
|
||||
{
|
||||
// Parent process
|
||||
auto cgiStdIn = std::make_unique<CgiSocket>(pipeStdin[1], ASocket::IoState::WRITE);
|
||||
auto cgiStdOut = std::make_unique<CgiSocket>(pipeStdout[0], ASocket::IoState::READ);
|
||||
auto cgiStdErr = std::make_unique<CgiSocket>(pipeStderr[0], ASocket::IoState::READ);
|
||||
auto cgiStdIn = std::make_unique<CgiSocket>(pipeStdin[1], ASocket::IoState::WRITE, "stdin");
|
||||
auto cgiStdOut = std::make_unique<CgiSocket>(pipeStdout[0], ASocket::IoState::READ, "stdout");
|
||||
auto cgiStdErr = std::make_unique<CgiSocket>(pipeStderr[0], ASocket::IoState::READ, "stderr");
|
||||
|
||||
close(pipeStdin[0]);
|
||||
close(pipeStdout[1]);
|
||||
@ -108,8 +108,6 @@ void CgiProcess::spawn()
|
||||
|
||||
Log::debug("CGI process forked with PID: " + std::to_string(pid_));
|
||||
|
||||
// request_.getClient().setCgiSockets(std::move(cgiStdIn), std::move(cgiStdOut)); // move the sockets to the
|
||||
// client
|
||||
handler_.setCgiSockets(std::move(cgiStdIn), std::move(cgiStdOut), std::move(cgiStdErr));
|
||||
|
||||
handler_.setPid(pid_);
|
||||
|
||||
@ -49,7 +49,7 @@ class Log
|
||||
|
||||
void log(Level level, const std::string &message, const std::map<std::string, std::string> &context);
|
||||
|
||||
static constexpr Log::Level COMPILE_TIME_LOG_LEVEL = Log::Level::Trace;
|
||||
static constexpr Log::Level COMPILE_TIME_LOG_LEVEL = Log::Level::Debug;
|
||||
|
||||
static void setFileChannel(const std::string &filename);
|
||||
static void setStdoutChannel();
|
||||
|
||||
@ -35,6 +35,7 @@ int main(int argc, char **argv)
|
||||
|
||||
Log::setFileChannel("logs/webserv.log");
|
||||
Log::setStdoutChannel();
|
||||
// ::signal(SIGPIPE, SIG_IGN); // Ignore SIGPIPE globally
|
||||
printHeader();
|
||||
ConfigManager &configManager = ConfigManager::getInstance();
|
||||
configManager.init(configPath); // NOLINT
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
#include <cerrno> // for errno, EBADF, ENOENT, EINTR
|
||||
#include <csignal> // for SIGINT, sig_atomic_t
|
||||
#include <cstdio>
|
||||
#include <cstring> // for strerror
|
||||
#include <exception> // for exception
|
||||
#include <memory> // for unique_ptr, allocator, make_unique
|
||||
@ -76,10 +77,10 @@ void Server::add(ASocket &socket, Client *client)
|
||||
event.data.fd = fd;
|
||||
if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &event) == -1)
|
||||
{
|
||||
Log::error("epoll_ctl ADD failed for fd: " + std::to_string(fd) + " with error: " + std::strerror(errno));
|
||||
Log::error(socket.toString() + ": epoll_ctl ADD failed with error: " + std::strerror(errno));
|
||||
throw std::runtime_error("epoll_ctl ADD failed");
|
||||
}
|
||||
Log::debug("Socket added to epoll, fd: " + std::to_string(fd));
|
||||
Log::debug(socket.toString() + ": added to epoll");
|
||||
socketToClient_[fd] = client;
|
||||
sockets_.insert(&socket);
|
||||
}
|
||||
@ -92,10 +93,10 @@ void Server::remove(ASocket &socket)
|
||||
{
|
||||
if (errno == EBADF || errno == ENOENT)
|
||||
{
|
||||
Log::debug("Socket fd " + std::to_string(fd) + " was already closed or removed from epoll");
|
||||
Log::debug(socket.toString() + " was already closed or removed from epoll");
|
||||
return;
|
||||
}
|
||||
Log::error("epoll_ctl DEL failed for fd: " + std::to_string(fd) + " with error: " + std::strerror(errno));
|
||||
Log::error(socket.toString() + ": epoll_ctl DEL failed with error: " + std::string(std::strerror(errno)));
|
||||
throw std::runtime_error("epoll_ctl DEL failed");
|
||||
}
|
||||
socketToClient_.erase(fd);
|
||||
@ -181,13 +182,15 @@ void Server::handleRequest(struct epoll_event *event) const
|
||||
void Server::writable(int client_fd) const
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
Log::debug("Response ready for client fd: " + std::to_string(client_fd));
|
||||
Client &client = getClient(client_fd);
|
||||
ASocket &socket = client.getSocket(client_fd);
|
||||
Log::debug(socket.toString() + ": response ready");
|
||||
struct epoll_event ev{};
|
||||
ev.events = EPOLLOUT;
|
||||
ev.data.fd = client_fd;
|
||||
if (epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, client_fd, &ev) == -1)
|
||||
{
|
||||
Log::error("epoll_ctl MOD failed for fd: " + std::to_string(client_fd));
|
||||
Log::error(socket.toString() + ": epoll_ctl MOD failed with error: " + std::string(std::strerror(errno)));
|
||||
throw std::runtime_error("epoll_ctl MOD failed");
|
||||
}
|
||||
}
|
||||
@ -198,7 +201,7 @@ void Server::update(const ASocket &socket) const
|
||||
|
||||
int socketFd = socket.getFd();
|
||||
uint32_t events = utils::stateToEpoll(socket.getEvent());
|
||||
Log::debug("Socket (" + std::to_string(socket.getFd()) + ") is being updated");
|
||||
Log::debug(socket.toString() + ": is being updated");
|
||||
struct epoll_event evt{};
|
||||
evt.events = events;
|
||||
evt.data.fd = socketFd;
|
||||
@ -206,10 +209,10 @@ void Server::update(const ASocket &socket) const
|
||||
{
|
||||
if (errno == EBADF || errno == ENOENT)
|
||||
{
|
||||
Log::debug("Socket fd " + std::to_string(socketFd) + " was already closed or removed from epoll");
|
||||
Log::debug(socket.toString() + ": was already closed or removed from epoll");
|
||||
return;
|
||||
}
|
||||
Log::error("epoll_ctl MOD failed for fd: " + std::to_string(socketFd) + " with error: " + std::strerror(errno));
|
||||
Log::error(socket.toString() + ": epoll_ctl MOD failed with error: " + std::string(std::strerror(errno)));
|
||||
throw std::runtime_error("epoll_ctl MOD failed");
|
||||
}
|
||||
}
|
||||
@ -217,23 +220,24 @@ void Server::update(const ASocket &socket) const
|
||||
void Server::handleResponse(struct epoll_event *event) const
|
||||
{
|
||||
int socket_fd = event->data.fd;
|
||||
Log::debug("Attempting to send data to fd: " + std::to_string(socket_fd));
|
||||
Client &client = getClient(socket_fd);
|
||||
client.getSocket(socket_fd).callback();
|
||||
ASocket &socket = client.getSocket(socket_fd);
|
||||
Log::debug(socket.toString() + ": attempting to send data");
|
||||
socket.callback();
|
||||
// disconnect(client);
|
||||
}
|
||||
|
||||
void Server::handleEpollHangUp(struct epoll_event *event) const
|
||||
void Server::handleEpollHangUp(struct epoll_event *event)
|
||||
{
|
||||
Client &client = getClient(event->data.fd);
|
||||
ASocket &socket = client.getSocket(event->data.fd);
|
||||
if (socket.getType() == ASocket::Type::CGI_SOCKET)
|
||||
{
|
||||
Log::info("CGI socket hang up on fd " + std::to_string(event->data.fd));
|
||||
Log::info(socket.toString() + ": CGI socket hang up");
|
||||
socket.callback();
|
||||
return;
|
||||
}
|
||||
Log::warning("Epoll hang up on fd " + std::to_string(event->data.fd) + ": " + std::strerror(errno));
|
||||
Log::warning(socket.toString() + ": Epoll hang up with error: " + std::string(std::strerror(errno)));
|
||||
}
|
||||
|
||||
void Server::handleEvent(struct epoll_event *event)
|
||||
@ -266,17 +270,18 @@ void Server::handleEvent(struct epoll_event *event)
|
||||
ASocket &socket = client.getSocket(fd);
|
||||
if (socket.getType() == ASocket::Type::CGI_SOCKET)
|
||||
{
|
||||
Log::info("EPOLLERR on CGI socket fd " + std::to_string(fd) + ", invoking callback");
|
||||
socket.callback();
|
||||
Log::info(socket.toString() + ": EPOLLERR invoking callback");
|
||||
remove(socket);
|
||||
close(fd);
|
||||
}
|
||||
else if (socket.getType() == ASocket::Type::CLIENT_SOCKET)
|
||||
{
|
||||
Log::warning("EPOLLERR on client socket fd " + std::to_string(fd) + ", disconnecting client");
|
||||
Log::warning(socket.toString() + ": EPOLLERR disconnecting client");
|
||||
disconnect(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::warning("EPOLLERR on auxiliary socket fd " + std::to_string(fd) + ", removing");
|
||||
Log::warning(socket.toString() + ": EPOLLERR removing auxiliary socket");
|
||||
remove(socket);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ void ASocket::setNonBlocking() const
|
||||
}
|
||||
flagStr += ")";
|
||||
|
||||
Log::debug("ASocket: FD " + std::to_string(fd_) + " configured. Flags: " + flagStr);
|
||||
Log::debug(this->toString() + " configured. Flags: " + flagStr);
|
||||
}
|
||||
|
||||
int ASocket::getFd() const noexcept
|
||||
@ -127,7 +127,7 @@ void ASocket::setIOState(IoState event)
|
||||
|
||||
void ASocket::processed()
|
||||
{
|
||||
Log::debug("Socket " + std::to_string(fd_) + " processed");
|
||||
Log::debug(this->toString() + " processed");
|
||||
dirty_ = false;
|
||||
}
|
||||
|
||||
@ -148,3 +148,8 @@ void ASocket::setCallback(std::function<void()> callback)
|
||||
{
|
||||
callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
std::string ASocket::toString() const
|
||||
{
|
||||
return "ASocket(fd=" + std::to_string(fd_) + ")";
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint>
|
||||
#include <functional> // for function
|
||||
#include <string>
|
||||
|
||||
#include <sys/types.h> // for ssize_t
|
||||
|
||||
@ -48,6 +49,8 @@ class ASocket
|
||||
void setIOState(IoState event);
|
||||
void processed();
|
||||
|
||||
[[nodiscard]] virtual std::string toString() const;
|
||||
|
||||
protected:
|
||||
void setNonBlocking() const;
|
||||
void setFd(int fd);
|
||||
|
||||
@ -3,12 +3,13 @@
|
||||
#include <webserv/log/Log.hpp> // for LOCATION, Log
|
||||
#include <webserv/socket/ASocket.hpp> // for ASocket
|
||||
|
||||
#include <string>
|
||||
#include <system_error> // for generic_category, system_error
|
||||
|
||||
#include <errno.h> // for errno
|
||||
#include <unistd.h> // for read, write
|
||||
|
||||
CgiSocket::CgiSocket(int fd, ASocket::IoState event) : ASocket(fd, event)
|
||||
CgiSocket::CgiSocket(int fd, ASocket::IoState event, std::string stream) : ASocket(fd, event), stream_(std::move(stream))
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
}
|
||||
@ -31,3 +32,8 @@ ssize_t CgiSocket::write(const void *buf, size_t len) const
|
||||
ssize_t bytesSent = ::write(getFd(), buf, len);
|
||||
return bytesSent;
|
||||
}
|
||||
|
||||
std::string CgiSocket::toString() const
|
||||
{
|
||||
return "CgiSocket(fd=" + std::to_string(getFd()) + "), stream (" + stream_ + ")";
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <webserv/socket/ASocket.hpp> // for ASocket
|
||||
#include <string>
|
||||
|
||||
#include <stddef.h> // for size_t
|
||||
#include <sys/socket.h>
|
||||
@ -10,10 +11,14 @@
|
||||
class CgiSocket : public ASocket
|
||||
{
|
||||
public:
|
||||
explicit CgiSocket(int fd, ASocket::IoState event);
|
||||
explicit CgiSocket(int fd, ASocket::IoState event, std::string stream);
|
||||
|
||||
[[nodiscard]] ASocket::Type getType() const noexcept override;
|
||||
|
||||
ssize_t read(void *buf, size_t len) const override;
|
||||
ssize_t write(const void *buf, size_t len) const override;
|
||||
|
||||
[[nodiscard]] std::string toString() const override;
|
||||
private:
|
||||
std::string stream_;
|
||||
};
|
||||
@ -17,3 +17,8 @@ const struct sockaddr *ClientSocket::getAddress() const noexcept
|
||||
{
|
||||
return &address_;
|
||||
}
|
||||
|
||||
std::string ClientSocket::toString() const
|
||||
{
|
||||
return "ClientSocket(fd=" + std::to_string(getFd()) + ")";
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ class ClientSocket : public ASocket
|
||||
|
||||
[[nodiscard]] ASocket::Type getType() const noexcept override;
|
||||
[[nodiscard]] const struct sockaddr *getAddress() const noexcept;
|
||||
|
||||
[[nodiscard]] std::string toString() const override;
|
||||
private:
|
||||
struct sockaddr address_;
|
||||
};
|
||||
@ -81,3 +81,8 @@ std::unique_ptr<ClientSocket> ServerSocket::accept() const
|
||||
}
|
||||
return std::make_unique<ClientSocket>(client_fd, client_address);
|
||||
}
|
||||
|
||||
std::string ServerSocket::toString() const
|
||||
{
|
||||
return "ServerSocket(fd=" + std::to_string(getFd()) + ")";
|
||||
}
|
||||
@ -17,4 +17,6 @@ class ServerSocket : public ASocket
|
||||
|
||||
[[nodiscard]] ASocket::Type getType() const noexcept override;
|
||||
[[nodiscard]] std::unique_ptr<ClientSocket> accept() const;
|
||||
|
||||
[[nodiscard]] std::string toString() const override;
|
||||
};
|
||||
@ -64,3 +64,8 @@ ssize_t TimerSocket::write(const void *buf, size_t len) const
|
||||
|
||||
return bytesSent;
|
||||
}
|
||||
|
||||
std::string TimerSocket::toString() const
|
||||
{
|
||||
return "TimerSocket(fd=" + std::to_string(getFd()) + "), active (" + (active_ ? "true" : "false") + "), timeout (" + std::to_string(timeout_.count()) + "ms)";
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
#include <webserv/socket/ASocket.hpp> // for ASocket
|
||||
|
||||
#include <chrono> // for milliseconds
|
||||
#include <string>
|
||||
|
||||
#include <stddef.h> // for size_t
|
||||
#include <sys/types.h> // for ssize_t
|
||||
@ -20,6 +21,8 @@ class TimerSocket : public ASocket
|
||||
|
||||
void activate();
|
||||
|
||||
[[nodiscard]] std::string toString() const override;
|
||||
|
||||
private:
|
||||
bool active_ = false;
|
||||
std::chrono::milliseconds timeout_;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user