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/http/HttpRequest.hpp>
|
||||||
#include <webserv/log/Log.hpp>
|
#include <webserv/log/Log.hpp>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
HttpRequest::HttpRequest(const ServerConfig *serverConfig, const Client *client)
|
HttpRequest::HttpRequest(const ServerConfig *serverConfig, const Client *client)
|
||||||
: serverConfig_(serverConfig), client_(client)
|
: serverConfig_(serverConfig), client_(client)
|
||||||
{
|
{
|
||||||
Log::trace("HttpRequest constructor called");
|
Log::trace(LOCATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpRequest::~HttpRequest()
|
HttpRequest::~HttpRequest()
|
||||||
{
|
{
|
||||||
Log::trace("HttpRequest destructor called");
|
Log::trace(LOCATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpRequest::State HttpRequest::getState() const
|
HttpRequest::State HttpRequest::getState() const
|
||||||
{
|
{
|
||||||
Log::trace("HttpRequest::getState() called");
|
Log::trace(LOCATION);
|
||||||
return state_;
|
return state_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &HttpRequest::getHeaders() const
|
const HttpHeaders &HttpRequest::getHeaders() const
|
||||||
{
|
{
|
||||||
return headers_;
|
return headers_;
|
||||||
}
|
}
|
||||||
@ -31,39 +34,13 @@ const std::string &HttpRequest::getBody() const
|
|||||||
return body_;
|
return body_;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t HttpRequest::getContentLength() const
|
|
||||||
{
|
|
||||||
return contentLength_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequest::receiveData(const char *data, size_t length)
|
void HttpRequest::receiveData(const char *data, size_t length)
|
||||||
{
|
{
|
||||||
Log::trace("HttpRequest::receiveData() called");
|
Log::trace(LOCATION);
|
||||||
buffer_.append(data, length);
|
buffer_.append(data, length);
|
||||||
parseBuffer();
|
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()
|
void HttpRequest::parseBuffer()
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
@ -88,6 +65,12 @@ void HttpRequest::parseBuffer()
|
|||||||
return; // Wait for more data
|
return; // Wait for more data
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case State::Chunked:
|
||||||
|
if (!parseBufferforChunkedBody())
|
||||||
|
{
|
||||||
|
return; // Wait for more data
|
||||||
|
}
|
||||||
|
break;
|
||||||
case State::Complete:
|
case State::Complete:
|
||||||
Log::debug("HttpRequest::parseBuffer() request is complete");
|
Log::debug("HttpRequest::parseBuffer() request is complete");
|
||||||
return; // Request is complete
|
return; // Request is complete
|
||||||
@ -97,57 +80,125 @@ void HttpRequest::parseBuffer()
|
|||||||
|
|
||||||
bool HttpRequest::parseBufferforRequestLine()
|
bool HttpRequest::parseBufferforRequestLine()
|
||||||
{
|
{
|
||||||
Log::trace("HttpRequest::parseBufferforRequestLine() called");
|
Log::trace(LOCATION);
|
||||||
size_t pos = buffer_.find(Http::Protocol::CRLF);
|
size_t pos = buffer_.find(Http::Protocol::CRLF);
|
||||||
if (pos == std::string::npos)
|
if (pos == std::string::npos)
|
||||||
{
|
{
|
||||||
Log::debug("HttpRequest::parseBuffer() in state RequestLine waiting for more data");
|
Log::debug("RequestLine waiting for more data : " + LOCATION);
|
||||||
return false; // Wait for more data
|
return false;
|
||||||
}
|
}
|
||||||
requestLine_ = buffer_.substr(0, pos);
|
std::string requestLine_ = buffer_.substr(0, pos);
|
||||||
|
|
||||||
buffer_.erase(0, pos + Http::Protocol::CRLF.size());
|
buffer_.erase(0, pos + Http::Protocol::CRLF.size());
|
||||||
state_ = State::Headers;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpRequest::parseBufferforHeaders()
|
bool HttpRequest::parseBufferforHeaders()
|
||||||
{
|
{
|
||||||
Log::trace("HttpRequest::parseBufferforHeaders() called");
|
Log::trace(LOCATION);
|
||||||
size_t pos = buffer_.find(Http::Protocol::DOUBLE_CRLF);
|
size_t pos = buffer_.find(Http::Protocol::DOUBLE_CRLF);
|
||||||
if (pos == std::string::npos)
|
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
|
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());
|
buffer_.erase(0, pos + Http::Protocol::DOUBLE_CRLF.size());
|
||||||
parseContentLength();
|
// parseContentLength();
|
||||||
|
|
||||||
if (contentLength_ > 0)
|
if (this->headers_.getContentLength() > 0)
|
||||||
{
|
{
|
||||||
state_ = State::Body;
|
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");
|
Log::debug("HttpRequest::parseBuffer() in state Headers no body to read");
|
||||||
state_ = State::Complete;
|
state_ = State::Complete;
|
||||||
return false; // No body to read
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpRequest::parseBufferforBody()
|
bool HttpRequest::parseBufferforBody()
|
||||||
{
|
{
|
||||||
Log::trace("HttpRequest::parseBufferforBody() called");
|
if (!headers_.getContentLength().has_value())
|
||||||
if (buffer_.size() < contentLength_)
|
|
||||||
{
|
{
|
||||||
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
|
return false; // Wait for more data
|
||||||
}
|
}
|
||||||
body_ = buffer_.substr(0, contentLength_);
|
body_ = buffer_.substr(0, *headers_.getContentLength());
|
||||||
buffer_.erase(0, contentLength_);
|
buffer_.erase(0, *headers_.getContentLength());
|
||||||
state_ = State::Complete;
|
state_ = State::Complete;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -155,11 +206,11 @@ bool HttpRequest::parseBufferforBody()
|
|||||||
|
|
||||||
void HttpRequest::reset()
|
void HttpRequest::reset()
|
||||||
{
|
{
|
||||||
Log::trace("HttpRequest::reset() called");
|
Log::trace(LOCATION);
|
||||||
state_ = State::RequestLine;
|
state_ = State::RequestLine;
|
||||||
buffer_.clear();
|
buffer_.clear();
|
||||||
requestLine_.clear();
|
// requestLine_.clear();
|
||||||
headers_.clear();
|
// headers_.clear();
|
||||||
body_.clear();
|
body_.clear();
|
||||||
contentLength_ = 0;
|
// contentLength_ = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "webserv/http/HttpHeaders.hpp"
|
||||||
|
|
||||||
#include <webserv/config/ServerConfig.hpp>
|
#include <webserv/config/ServerConfig.hpp>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@ -16,6 +18,7 @@ class HttpRequest
|
|||||||
RequestLine,
|
RequestLine,
|
||||||
Headers,
|
Headers,
|
||||||
Body,
|
Body,
|
||||||
|
Chunked,
|
||||||
Complete
|
Complete
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,9 +31,12 @@ class HttpRequest
|
|||||||
~HttpRequest();
|
~HttpRequest();
|
||||||
|
|
||||||
[[nodiscard]] State getState() const;
|
[[nodiscard]] State getState() const;
|
||||||
[[nodiscard]] const std::string &getHeaders() const;
|
[[nodiscard]] const HttpHeaders &getHeaders() const;
|
||||||
[[nodiscard]] const std::string &getBody() 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 receiveData(const char *data, size_t length);
|
||||||
void reset();
|
void reset();
|
||||||
@ -39,7 +45,9 @@ class HttpRequest
|
|||||||
void parseBuffer();
|
void parseBuffer();
|
||||||
[[nodiscard]] bool parseBufferforRequestLine();
|
[[nodiscard]] bool parseBufferforRequestLine();
|
||||||
[[nodiscard]] bool parseBufferforHeaders();
|
[[nodiscard]] bool parseBufferforHeaders();
|
||||||
|
[[nodiscard]] bool parseHeaderLine();
|
||||||
[[nodiscard]] bool parseBufferforBody();
|
[[nodiscard]] bool parseBufferforBody();
|
||||||
|
[[nodiscard]] bool parseBufferforChunkedBody();
|
||||||
|
|
||||||
void parseContentLength();
|
void parseContentLength();
|
||||||
|
|
||||||
@ -48,10 +56,14 @@ class HttpRequest
|
|||||||
|
|
||||||
State state_ = State::RequestLine;
|
State state_ = State::RequestLine;
|
||||||
|
|
||||||
std::string buffer_;
|
HttpHeaders headers_;
|
||||||
|
|
||||||
std::string requestLine_;
|
std::string buffer_;
|
||||||
std::string headers_;
|
|
||||||
std::string body_;
|
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