3#include <coroio/socket.hpp>
4#include <coroio/accept_errors.hpp>
6#include <coroio/sockutils.hpp>
15#include <unordered_map>
31 TUri(
const std::string& uriStr);
33 const std::string&
Path()
const;
40 void Parse(
const std::string& uriStr);
42 std::map<std::string, std::string> QueryParameters_;
43 std::string Fragment_;
58 TRequest(std::string&& header,
62 std::string_view
Method()
const;
66 std::string_view
Version()
const {
return Version_; }
89 const std::map<std::string_view, std::string_view>&
Headers()
const;
92 void ParseRequestLine();
99 size_t HeaderStartPos_ = 0;
100 std::map<std::string_view, std::string_view> Headers_;
101 size_t ContentLength_ = 0;
102 bool HasBody_ =
false;
103 bool Chunked_ =
false;
104 bool BodyConsumed_ =
false;
105 size_t CurrentChunkSize_ = 0;
106 std::function<TFuture<ssize_t>(
char*,
size_t)> BodyReader_;
107 std::function<TFuture<std::string>()> ChunkHeaderReader_;
108 std::string_view Method_;
110 std::string_view Version_;
134 : Writer_(std::move(writer))
139 void SetHeader(
const std::string& name,
const std::string& value);
174 int StatusCode_ = 200;
175 std::map<std::string, std::string> Headers_;
176 bool HeadersSent_ =
false;
177 bool Chunked_ =
false;
178 bool IsClosed_ =
false;
179 std::function<TFuture<ssize_t>(
const void*,
size_t)> Writer_;
224template<
typename TSocket>
235 : ServerSocket(std::move(serverSocket))
237 , Logger(std::move(logger))
252 auto clientSocket =
co_await ServerSocket.Accept();
254 HandleClient(std::move(*clientSocket));
255 }
else if (!IsTransientAcceptError(clientSocket.error())) {
257 Logger(
"Accept failed: " + std::system_category().message(clientSocket.error()));
259 co_await ServerSocket.Poller()->Sleep(std::chrono::milliseconds(100));
269 co_return co_await byteReader.ReadSome(buffer, size);
272 auto chunkHeaderReader = [&]() -> TFuture<std::string> {
273 co_return co_await byteReader.ReadUntil(
"\r\n");
276 auto bodyWriter = [&](
const void* data,
size_t size) -> TFuture<ssize_t> {
277 co_return co_await clientSocket.
WriteSome(data, size);
280 std::string clientString = clientSocket.
RemoteAddr() ? clientSocket.
RemoteAddr()->ToString() :
"unknown";
284 auto header =
co_await byteReader.ReadUntil(
"\r\n\r\n");
285 TRequest request(std::move(header), bodyReader, chunkHeaderReader);
286 TResponse response(bodyWriter);
287 co_await Router.HandleRequest(request, response);
288 Log(request, response, clientString);
289 if (response.IsClosed() || request.RequireConnectionClose()) {
293 }
catch (
const std::exception& ex) {
295 Logger(std::string(
"Client handler exception: ") + ex.what());
301 void Log(
const TRequest& request,
const TResponse& response,
const std::string& clientString) {
309 std::string fullPath = request.Uri().Path();
310 const auto& qp = request.Uri().QueryParameters();
312 fullPath.push_back(
'?');
314 for (
const auto& [k,v] : qp) {
315 if (!first) fullPath.push_back(
'&');
318 fullPath.push_back(
'=');
324 auto now = std::chrono::system_clock::now();
325 std::time_t raw = std::chrono::system_clock::to_time_t(now);
329 localtime_s(&localTm, &raw);
330 gmtime_s(&gmTm, &raw);
332 localtime_r(&raw, &localTm);
333 gmtime_r(&raw, &gmTm);
337 std::time_t localTime = std::mktime(&localTm);
338 std::time_t gmTime = std::mktime(&gmTm);
339 long tzOffsetSec =
static_cast<long>(difftime(localTime, gmTime));
340 int tzSign = tzOffsetSec >= 0 ? 1 : -1;
341 tzOffsetSec = std::labs(tzOffsetSec);
342 int tzHours =
static_cast<int>(tzOffsetSec / 3600);
343 int tzMins =
static_cast<int>((tzOffsetSec % 3600) / 60);
345 std::snprintf(tzBuf,
sizeof(tzBuf),
"%c%02d%02d", tzSign > 0 ?
'+' :
'-', tzHours, tzMins);
348 std::string_view ver = request.Version();
349 bool hasHttpPrefix = ver.size() >= 5 && ver.substr(0,5) ==
"HTTP/";
351 LogStream << clientString <<
" - - [" << std::put_time(&localTm,
"%d/%b/%Y:%H:%M:%S ") << tzBuf <<
"] \""
352 << request.Method() <<
' ' << fullPath <<
' ' << (hasHttpPrefix ? std::string(ver) : (std::string(
"HTTP/") + std::string(ver))) <<
"\" "
353 << response.StatusCode() <<
' ' <<
'-' <<
' ' <<
"\"-\" \"-\"";
354 Logger(LogStream.str());
357 TSocket ServerSocket;
359 std::function<void(
const std::string&)> Logger;
360 std::ostringstream LogStream;
Represents an incoming HTTP/1.1 request.
Definition httpd.hpp:56
std::string_view Version() const
HTTP version string (e.g. "HTTP/1.1").
Definition httpd.hpp:66
bool BodyConsumed() const
Returns true once the full body has been read.
Definition httpd.cpp:201
bool HasBody() const
Returns true if the request has a body (non-zero Content-Length or chunked).
Definition httpd.cpp:197
const std::map< std::string_view, std::string_view > & Headers() const
All request headers as a name → value map (views into the header buffer).
Definition httpd.cpp:172
TFuture< std::string > ReadBodyFull()
Reads and returns the entire request body as a string.
Definition httpd.cpp:205
bool RequireConnectionClose() const
Returns true if the client sent Connection: close or is HTTP/1.0.
Definition httpd.cpp:176
std::string_view Method() const
HTTP method string (e.g. "GET", "POST").
Definition httpd.cpp:189
TFuture< ssize_t > ReadBodySome(char *buffer, size_t size)
Reads up to size bytes of the request body into buffer.
Definition httpd.cpp:233
const TUri & Uri() const
Parsed request URI.
Definition httpd.cpp:193
Builds and sends an HTTP/1.1 response.
Definition httpd.hpp:131
TFuture< void > WriteBodyChunk(const char *data, size_t size)
Writes data as one chunk using Transfer-Encoding: chunked.
Definition httpd.cpp:352
void SetStatus(int statusCode)
Sets the HTTP status code (default is 200). Must be called before SendHeaders.
Definition httpd.cpp:294
TFuture< void > WriteBodyFull(const std::string &data)
Writes the full body, sending the headers first if they were not sent yet.
Definition httpd.cpp:368
int StatusCode() const
Returns the status code set via SetStatus (default 200).
Definition httpd.hpp:167
TFuture< void > SendHeaders()
Sends the status line and all accumulated headers to the client.
Definition httpd.cpp:316
bool IsClosed() const
Returns true if the connection has been closed or an error occurred.
Definition httpd.cpp:348
void SetHeader(const std::string &name, const std::string &value)
Adds or replaces a response header. Must be called before SendHeaders.
Definition httpd.cpp:298
auto WriteSome(const void *buf, size_t size)
Asynchronously writes data from the provided buffer to the socket.
Definition socket.hpp:193
High-level asynchronous socket for network communication.
Definition socket.hpp:404
const std::optional< TAddress > & RemoteAddr() const
Returns the remote address of the connected peer.
Definition socket.cpp:133
Parsed HTTP request URI: /path?key=value&...#fragment.
Definition httpd.hpp:27
const std::string & Fragment() const
Returns the fragment (the part after #), or empty string if absent.
Definition httpd.cpp:83
const std::map< std::string, std::string > & QueryParameters() const
Returns query parameters as a key→value map (e.g. {"page": "2"}).
Definition httpd.cpp:87
const std::string & Path() const
Returns the decoded path component (e.g. "/api/v1/users").
Definition httpd.cpp:91
TWebServer(TSocket &&serverSocket, IRouter &router, std::function< void(const std::string &)> logger={})
Constructs the server.
Definition httpd.hpp:234
TVoidTask Start()
Starts the accept loop as a detached TVoidTask.
Definition httpd.hpp:250
Implementation of a promise/future system for coroutines.
Interface for HTTP request handlers.
Definition httpd.hpp:200
A utility for reading data from a socket-like object, either a fixed number of bytes or until a speci...
Definition sockutils.hpp:76
Owned coroutine handle that carries a result of type T.
Definition corochain.hpp:185
Fire-and-forget coroutine handle.
Definition promises.hpp:26