feat(http): add headers class and allow for chunked data
This commit is contained in:
parent
dfc4bc7dd0
commit
221cc710a8
80
webserv/http/HttpHeaders.cpp
Normal file
80
webserv/http/HttpHeaders.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include "webserv/http/HttpHeaders.hpp"
|
||||
|
||||
#include "webserv/config/utils.hpp"
|
||||
#include "webserv/http/HttpConstants.hpp"
|
||||
|
||||
std::optional<size_t> HttpHeaders::getContentLength() const
|
||||
{
|
||||
const auto &value = get("Content-Length");
|
||||
if (value.empty())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
try
|
||||
{
|
||||
return std::stoul(value);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpHeaders::add(const std::string &name, const std::string &value)
|
||||
{
|
||||
headers_[name] = value;
|
||||
}
|
||||
|
||||
void HttpHeaders::remove(const std::string &name)
|
||||
{
|
||||
headers_.erase(name);
|
||||
}
|
||||
|
||||
const std::string &HttpHeaders::get(const std::string &name) const
|
||||
{
|
||||
auto it = headers_.find(name);
|
||||
if (it != headers_.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
static const std::string empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
bool HttpHeaders::has(const std::string &name) const
|
||||
{
|
||||
return headers_.contains(name);
|
||||
}
|
||||
|
||||
void HttpHeaders::parse(const std::string &rawHeaders)
|
||||
{
|
||||
size_t start = 0;
|
||||
size_t end = rawHeaders.find(Http::Protocol::CRLF);
|
||||
|
||||
while (end != std::string::npos)
|
||||
{
|
||||
std::string line = rawHeaders.substr(start, end - start);
|
||||
size_t col = line.find(':');
|
||||
if (col != std::string::npos)
|
||||
{
|
||||
std::string name = line.substr(0, col);
|
||||
std::string value = line.substr(col + 1);
|
||||
name = trim(name);
|
||||
value = trim(value);
|
||||
this->add(name, value);
|
||||
}
|
||||
start = end + Http::Protocol::CRLF.size();
|
||||
end = rawHeaders.find(Http::Protocol::CRLF, start);
|
||||
}
|
||||
}
|
||||
|
||||
std::string HttpHeaders::toString() const
|
||||
{
|
||||
std::string result;
|
||||
for (const auto &pair : headers_)
|
||||
{
|
||||
result += pair.first + ": " + pair.second + "\r\n";
|
||||
}
|
||||
result += "\r\n";
|
||||
return result;
|
||||
}
|
||||
42
webserv/http/HttpHeaders.hpp
Normal file
42
webserv/http/HttpHeaders.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* @file HttpHeaders.hpp
|
||||
* @brief Declaration of the HttpHeaders class for managing HTTP headers.
|
||||
*
|
||||
* This class provides functionality to store, retrieve, and manage HTTP headers
|
||||
* in a structured manner.
|
||||
*
|
||||
* Without this class the HttpRequest and Response classes would become too bloated, and we'd end up adding members
|
||||
* to those classes for every new header we want to support.
|
||||
*/
|
||||
class HttpHeaders
|
||||
{
|
||||
public:
|
||||
HttpHeaders() = default;
|
||||
|
||||
HttpHeaders(const HttpHeaders &other) = delete;
|
||||
HttpHeaders(HttpHeaders &&other) noexcept = delete;
|
||||
HttpHeaders &operator=(const HttpHeaders &other) = delete;
|
||||
HttpHeaders &operator=(HttpHeaders &&other) noexcept = delete;
|
||||
|
||||
~HttpHeaders() = default;
|
||||
|
||||
const std::string &get(const std::string &name) const;
|
||||
bool has(const std::string &name) const;
|
||||
|
||||
void parse(const std::string &rawHeaders);
|
||||
void add(const std::string &name, const std::string &value);
|
||||
void remove(const std::string &name);
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
std::optional<size_t> getContentLength() const;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::string> headers_;
|
||||
};
|
||||
@ -4,24 +4,27 @@
|
||||
#include <webserv/http/HttpRequest.hpp>
|
||||
#include <webserv/log/Log.hpp>
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
HttpRequest::HttpRequest(const ServerConfig *serverConfig, const Client *client)
|
||||
: serverConfig_(serverConfig), client_(client)
|
||||
{
|
||||
Log::trace("HttpRequest constructor called");
|
||||
Log::trace(LOCATION);
|
||||
}
|
||||
|
||||
HttpRequest::~HttpRequest()
|
||||
{
|
||||
Log::trace("HttpRequest destructor called");
|
||||
Log::trace(LOCATION);
|
||||
}
|
||||
|
||||
HttpRequest::State HttpRequest::getState() const
|
||||
{
|
||||
Log::trace("HttpRequest::getState() called");
|
||||
Log::trace(LOCATION);
|
||||
return state_;
|
||||
}
|
||||
|
||||
const std::string &HttpRequest::getHeaders() const
|
||||
const HttpHeaders &HttpRequest::getHeaders() const
|
||||
{
|
||||
return headers_;
|
||||
}
|
||||
@ -31,39 +34,13 @@ const std::string &HttpRequest::getBody() const
|
||||
return body_;
|
||||
}
|
||||
|
||||
size_t HttpRequest::getContentLength() const
|
||||
{
|
||||
return contentLength_;
|
||||
}
|
||||
|
||||
void HttpRequest::receiveData(const char *data, size_t length)
|
||||
{
|
||||
Log::trace("HttpRequest::receiveData() called");
|
||||
Log::trace(LOCATION);
|
||||
buffer_.append(data, length);
|
||||
parseBuffer();
|
||||
}
|
||||
|
||||
void HttpRequest::parseContentLength()
|
||||
{
|
||||
// Parse headers to find Content-Length
|
||||
size_t pos = headers_.find(Http::Header::CONTENT_LENGTH);
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
pos += Http::Header::CONTENT_LENGTH.size() + Http::Protocol::HEADER_SEPARATOR.size();
|
||||
while (pos < headers_.size() && (headers_[pos] == ' ' || headers_[pos] == '\t'))
|
||||
{
|
||||
++pos; // Skip whitespace
|
||||
}
|
||||
size_t endPos = headers_.find(Http::Protocol::CRLF, pos);
|
||||
std::string contentLengthValue = headers_.substr(pos, endPos - pos);
|
||||
contentLength_ = std::stoul(contentLengthValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
contentLength_ = 0; // No body expected
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::parseBuffer()
|
||||
{
|
||||
while (true)
|
||||
@ -88,6 +65,12 @@ void HttpRequest::parseBuffer()
|
||||
return; // Wait for more data
|
||||
}
|
||||
break;
|
||||
case State::Chunked:
|
||||
if (!parseBufferforChunkedBody())
|
||||
{
|
||||
return; // Wait for more data
|
||||
}
|
||||
break;
|
||||
case State::Complete:
|
||||
Log::debug("HttpRequest::parseBuffer() request is complete");
|
||||
return; // Request is complete
|
||||
@ -97,57 +80,125 @@ void HttpRequest::parseBuffer()
|
||||
|
||||
bool HttpRequest::parseBufferforRequestLine()
|
||||
{
|
||||
Log::trace("HttpRequest::parseBufferforRequestLine() called");
|
||||
Log::trace(LOCATION);
|
||||
size_t pos = buffer_.find(Http::Protocol::CRLF);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
Log::debug("HttpRequest::parseBuffer() in state RequestLine waiting for more data");
|
||||
return false; // Wait for more data
|
||||
Log::debug("RequestLine waiting for more data : " + LOCATION);
|
||||
return false;
|
||||
}
|
||||
requestLine_ = buffer_.substr(0, pos);
|
||||
std::string requestLine_ = buffer_.substr(0, pos);
|
||||
|
||||
buffer_.erase(0, pos + Http::Protocol::CRLF.size());
|
||||
state_ = State::Headers;
|
||||
|
||||
std::vector<std::string> parts;
|
||||
std::string part;
|
||||
std::stringstream ss(requestLine_);
|
||||
while (ss >> part)
|
||||
{
|
||||
parts.push_back(part);
|
||||
}
|
||||
if (parts.size() != 3)
|
||||
{
|
||||
Log::warning("Invalid request line: " + requestLine_);
|
||||
state_ = State::Complete; // Mark as complete to avoid further processing
|
||||
return true;
|
||||
}
|
||||
method_ = parts[0];
|
||||
target_ = parts[1];
|
||||
httpVersion_ = parts[2];
|
||||
Log::debug("Parsed Request Line: Method=" + method_ + " Target=" + target_ + " Version=" + httpVersion_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpRequest::parseBufferforHeaders()
|
||||
{
|
||||
Log::trace("HttpRequest::parseBufferforHeaders() called");
|
||||
Log::trace(LOCATION);
|
||||
size_t pos = buffer_.find(Http::Protocol::DOUBLE_CRLF);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
Log::debug("HttpRequest::parseBuffer() in state Headers waiting for more data");
|
||||
Log::debug("Headers waiting for more data: " + LOCATION);
|
||||
return false; // Wait for more data
|
||||
}
|
||||
headers_ = buffer_.substr(0, pos + Http::Protocol::CRLF.size()); // Include the last \r\n
|
||||
// headers_ = buffer_.substr(0, pos + Http::Protocol::CRLF.size()); // Include the last \r\n
|
||||
headers_.parse(buffer_.substr(0, pos + Http::Protocol::CRLF.size()));
|
||||
buffer_.erase(0, pos + Http::Protocol::DOUBLE_CRLF.size());
|
||||
parseContentLength();
|
||||
// parseContentLength();
|
||||
|
||||
if (contentLength_ > 0)
|
||||
if (this->headers_.getContentLength() > 0)
|
||||
{
|
||||
state_ = State::Body;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (this->headers_.has("Transfer-Encoding") && this->headers_.get("Transfer-Encoding") == "chunked")
|
||||
{
|
||||
state_ = State::Chunked;
|
||||
return true;
|
||||
}
|
||||
|
||||
Log::debug("HttpRequest::parseBuffer() in state Headers no body to read");
|
||||
state_ = State::Complete;
|
||||
return false; // No body to read
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpRequest::parseBufferforChunkedBody()
|
||||
{
|
||||
Log::trace(LOCATION);
|
||||
while (true)
|
||||
{
|
||||
size_t pos = buffer_.find(Http::Protocol::CRLF);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
Log::debug("Chunked body waiting for more data: " + LOCATION);
|
||||
return false;
|
||||
}
|
||||
std::string chunkSizeStr = buffer_.substr(0, pos);
|
||||
size_t chunkSize = 0;
|
||||
try
|
||||
{
|
||||
chunkSize = std::stoul(chunkSizeStr, nullptr, 16);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Log::warning("Invalid chunk size: " + chunkSizeStr + " (" + e.what() + ")");
|
||||
state_ = State::Complete; // Mark as complete to avoid further processing
|
||||
return true;
|
||||
}
|
||||
if (chunkSize == 0)
|
||||
{
|
||||
state_ = State::Complete; // Last chunk
|
||||
buffer_.erase(0, pos + Http::Protocol::CRLF.size());
|
||||
return true;
|
||||
}
|
||||
// TODO: Copilot added this but I don't understand why it's needed
|
||||
// if (buffer_.size() < pos + Http::Protocol::CRLF.size() + chunkSize + Http::Protocol::CRLF.size())
|
||||
// {
|
||||
// Log::debug("Chunked body waiting for more data: " + LOCATION);
|
||||
// return false; // Wait for more data
|
||||
// }
|
||||
body_ += buffer_.substr(pos + Http::Protocol::CRLF.size(), chunkSize);
|
||||
buffer_.erase(0, pos + Http::Protocol::CRLF.size() + chunkSize + Http::Protocol::CRLF.size());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpRequest::parseBufferforBody()
|
||||
{
|
||||
Log::trace("HttpRequest::parseBufferforBody() called");
|
||||
if (buffer_.size() < contentLength_)
|
||||
if (!headers_.getContentLength().has_value())
|
||||
{
|
||||
Log::debug("HttpRequest::parseBuffer() in state Body waiting for more data");
|
||||
Log::warning("HttpRequest::parseBuffer() in state Body but no Content-Length header found");
|
||||
state_ = State::Complete;
|
||||
return true;
|
||||
}
|
||||
Log::trace(LOCATION);
|
||||
if (buffer_.size() < *headers_.getContentLength())
|
||||
{
|
||||
Log::debug("Body waiting for more data: " + LOCATION);
|
||||
return false; // Wait for more data
|
||||
}
|
||||
body_ = buffer_.substr(0, contentLength_);
|
||||
buffer_.erase(0, contentLength_);
|
||||
body_ = buffer_.substr(0, *headers_.getContentLength());
|
||||
buffer_.erase(0, *headers_.getContentLength());
|
||||
state_ = State::Complete;
|
||||
|
||||
return true;
|
||||
@ -155,11 +206,11 @@ bool HttpRequest::parseBufferforBody()
|
||||
|
||||
void HttpRequest::reset()
|
||||
{
|
||||
Log::trace("HttpRequest::reset() called");
|
||||
Log::trace(LOCATION);
|
||||
state_ = State::RequestLine;
|
||||
buffer_.clear();
|
||||
requestLine_.clear();
|
||||
headers_.clear();
|
||||
// requestLine_.clear();
|
||||
// headers_.clear();
|
||||
body_.clear();
|
||||
contentLength_ = 0;
|
||||
// contentLength_ = 0;
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "webserv/http/HttpHeaders.hpp"
|
||||
|
||||
#include <webserv/config/ServerConfig.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
@ -16,6 +18,7 @@ class HttpRequest
|
||||
RequestLine,
|
||||
Headers,
|
||||
Body,
|
||||
Chunked,
|
||||
Complete
|
||||
};
|
||||
|
||||
@ -28,9 +31,12 @@ class HttpRequest
|
||||
~HttpRequest();
|
||||
|
||||
[[nodiscard]] State getState() const;
|
||||
[[nodiscard]] const std::string &getHeaders() const;
|
||||
[[nodiscard]] const HttpHeaders &getHeaders() const;
|
||||
[[nodiscard]] const std::string &getBody() const;
|
||||
[[nodiscard]] size_t getContentLength() const;
|
||||
|
||||
[[nodiscard]] const std::string &getMethod() const { return method_; }
|
||||
[[nodiscard]] const std::string &getTarget() const { return target_; }
|
||||
[[nodiscard]] const std::string &getHttpVersion() const { return httpVersion_; }
|
||||
|
||||
void receiveData(const char *data, size_t length);
|
||||
void reset();
|
||||
@ -39,7 +45,9 @@ class HttpRequest
|
||||
void parseBuffer();
|
||||
[[nodiscard]] bool parseBufferforRequestLine();
|
||||
[[nodiscard]] bool parseBufferforHeaders();
|
||||
[[nodiscard]] bool parseHeaderLine();
|
||||
[[nodiscard]] bool parseBufferforBody();
|
||||
[[nodiscard]] bool parseBufferforChunkedBody();
|
||||
|
||||
void parseContentLength();
|
||||
|
||||
@ -48,10 +56,14 @@ class HttpRequest
|
||||
|
||||
State state_ = State::RequestLine;
|
||||
|
||||
std::string buffer_;
|
||||
HttpHeaders headers_;
|
||||
|
||||
std::string requestLine_;
|
||||
std::string headers_;
|
||||
std::string buffer_;
|
||||
std::string body_;
|
||||
size_t contentLength_ = 0;
|
||||
std::string method_;
|
||||
std::string target_;
|
||||
std::string httpVersion_;
|
||||
// std::string requestLine_;
|
||||
// std::string headers_;
|
||||
// size_t contentLength_ = 0;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user