diff --git a/Makefile b/Makefile index f094c19..ae060a6 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ SRCS := src/Log.cpp src/main.cpp src/Server.cpp \ src/config/Config.cpp src/config/ConfigurationParser.cpp src/config/LocationConfig.cpp src/config/ServerConfig.cpp \ src/event/Epoll.cpp src/event/EventToken.cpp \ src/net/ByteBuffer.cpp src/net/Connection.cpp src/net/FileDescriptor.cpp src/net/Listener.cpp src/net/ListenerPlan.cpp src/net/PauseMask.cpp src/net/SlotPool.cpp \ - src/http/Request.cpp src/http/RequestParser.cpp + src/http/Request.cpp src/http/RequestParser.cpp src/http/Response.cpp OBJS := $(SRCS:$(SRC_DIR)/%.cpp=$(OBJ_DIR)/%.o) @@ -66,7 +66,7 @@ SRCS := src/Log.cpp src/main.cpp src/Server.cpp \ DEPS := $(OBJS:.o=.d) HDRS := include/config/Config.hpp include/config/ConfigurationParser.hpp include/config/LocationConfig.hpp include/config/ServerConfig.hpp \ include/event/Epoll.hpp include/event/EventToken.hpp \ - include/http/HttpStatus.hpp include/http/Request.hpp include/http/RequestParser.hpp \ + include/http/HttpStatus.hpp include/http/Request.hpp include/http/RequestParser.hpp include/http/Response.hpp \ include/net/ByteBuffer.hpp include/net/Connection.hpp include/net/FileDescriptor.hpp include/net/Listener.hpp include/net/ListenerPlan.hpp include/net/PauseMask.hpp include/net/SlotPool.hpp \ include/Log.hpp include/Result.hpp include/Server.hpp \ diff --git a/include/http/Response.hpp b/include/http/Response.hpp new file mode 100644 index 0000000..6184aaf --- /dev/null +++ b/include/http/Response.hpp @@ -0,0 +1,30 @@ +/*********************************/ +/* */ +/* o.riabenkyi@gmail.com */ +/* */ +/*********************************/ + +#ifndef RESPONSE_HPP +#define RESPONSE_HPP + +#include +#include +#include + +#include "http/HttpStatus.hpp" + +namespace webserv { + +const char* reasonPhrase(HttpStatus status) noexcept; + +struct Response { + HttpStatus status; + std::vector> headers; + std::vector body; +}; + +std::vector serialize(const Response& response); + +} // namespace webserv + +#endif diff --git a/include/net/Connection.hpp b/include/net/Connection.hpp index 47a487c..a20ced2 100644 --- a/include/net/Connection.hpp +++ b/include/net/Connection.hpp @@ -29,15 +29,9 @@ class Connection final { std::uint32_t desiredEvents; }; - // ByteBuffer::CAPACITY is 64 KiB. - // At most 16 KiB are copied per 48 KiB appended: 16 / 48 = 1 / 3. - // A lower threshold reduces copying but leaves less unread work available. - // The 16 KiB value is a fixed compromise, not a measured optimum. static constexpr std::size_t LOW_WATERMARK = 16uz * 1024; explicit Connection(FileDescriptor socket); - - // Each Connection has a unique identity and remains in its original slot. Connection(const Connection&) = delete; Connection& operator=(const Connection&) = delete; Connection(Connection&&) = delete; @@ -52,14 +46,8 @@ class Connection final { void close() noexcept { socket_.reset(); } private: - // recv() returning zero means the client has finished sending data. - // readClosed_ records this. receive() still returns true because this - // is not an error and the socket may still send its buffered response. - // Only a recv() error returns false. bool receive(); bool transmit(); - // TEMP echo path: queues received bytes unchanged for sending back to - // the client. void forwardReceivedBytes(); FileDescriptor socket_; diff --git a/src/http/Response.cpp b/src/http/Response.cpp new file mode 100644 index 0000000..e689bcf --- /dev/null +++ b/src/http/Response.cpp @@ -0,0 +1,78 @@ +/*********************************/ +/* */ +/* o.riabenkyi@gmail.com */ +/* */ +/*********************************/ + +#include "http/Response.hpp" + +#include + +namespace webserv { + +const char* reasonPhrase(HttpStatus status) noexcept { + switch (status) { + case HttpStatus::Ok: + return "OK"; + case HttpStatus::Created: + return "Created"; + case HttpStatus::NoContent: + return "No Content"; + case HttpStatus::MovedPermanently: + return "Moved Permanently"; + case HttpStatus::Found: + return "Found"; + case HttpStatus::SeeOther: + return "See Other"; + case HttpStatus::TemporaryRedirect: + return "Temporary Redirect"; + case HttpStatus::PermanentRedirect: + return "Permanent Redirect"; + case HttpStatus::BadRequest: + return "Bad Request"; + case HttpStatus::Forbidden: + return "Forbidden"; + case HttpStatus::NotFound: + return "Not Found"; + case HttpStatus::MethodNotAllowed: + return "Method Not Allowed"; + case HttpStatus::RequestTimeout: + return "Request Timeout"; + case HttpStatus::LengthRequired: + return "Length Required"; + case HttpStatus::ContentTooLarge: + return "Content Too Large"; + case HttpStatus::UriTooLong: + return "URI Too Long"; + case HttpStatus::RequestHeaderFieldsTooLarge: + return "Request Header Fields Too Large"; + case HttpStatus::InternalServerError: + return "Internal Server Error"; + case HttpStatus::NotImplemented: + return "Not Implemented"; + case HttpStatus::BadGateway: + return "Bad Gateway"; + case HttpStatus::ServiceUnavailable: + return "Service Unavailable"; + case HttpStatus::GatewayTimeout: + return "Gateway Timeout"; + case HttpStatus::HttpVersionNotSupported: + return "HTTP Version Not Supported"; + } + return "Unknown Status"; +} + +std::vector serialize(const Response& response) { + std::string head = std::format("HTTP/1.1 {} {}\r\n", static_cast(response.status), + reasonPhrase(response.status)); + for (const auto& [name, value] : response.headers) { + head += std::format("{}: {}\r\n", name, value); + } + head += std::format("Content-Length: {}\r\n\r\n", response.body.size()); + + std::vector bytes(head.begin(), head.end()); + bytes.insert(bytes.end(), response.body.begin(), response.body.end()); + return bytes; +} + +} // namespace webserv diff --git a/src/net/Connection.cpp b/src/net/Connection.cpp index a8b1912..9b7f128 100644 --- a/src/net/Connection.cpp +++ b/src/net/Connection.cpp @@ -24,16 +24,6 @@ namespace webserv { namespace { -// Writable tail still has space -> keep using it -// -// Writable tail is exhausted, but more than 16 KiB are still live -// -> wait for more bytes to be consumed because moving them is not worth it -// -// Writable tail is exhausted and at most 16 KiB are still live -// -> move the live bytes to the start and recover the writable tail -// -// Before: [ 48 KiB consumed ][ 16 KiB live ] -// After: [ 16 KiB live ][ 48 KiB writable tail ] void compactIfDrained(ByteBuffer& buffer) { if (buffer.writableTail().empty() && buffer.readable().size() <= Connection::LOW_WATERMARK) { @@ -45,10 +35,6 @@ void compactIfDrained(ByteBuffer& buffer) { Connection::Connection(FileDescriptor socket) : socket_(std::move(socket)) {} -// Client can still send data -> Alive -// Read side closed, buffered data remains -> Alive -// Read side closed, both buffers empty -> Done -// recv() or send() failed -> Fatal Connection::Outcome Connection::onClientEvent(std::uint32_t events) { // 1) Interpret the readiness reported by epoll. // EPOLLIN says recv() may progress.