Added HTTPClient type.

This commit is contained in:
Patrick 2024-08-20 00:28:12 +02:00
parent 99f5987f4b
commit 03f255a7d0
3 changed files with 148 additions and 17 deletions

View File

@ -1,6 +1,8 @@
#include "./http.hpp"
#include <format>
#include "../util/iterators.hpp"
#include "../util/string.hpp"
@ -69,23 +71,14 @@ Task<StreamError> HTTPStream::c_writeRequest(const mijin::HTTPRequest& request)
}
}
MIJIN_HTTP_WRITE(request.method);
MIJIN_HTTP_WRITE(" ");
MIJIN_HTTP_WRITE(request.address);
MIJIN_HTTP_WRITE(" HTTP/1.0\n");
MIJIN_HTTP_WRITE(std::format("{} {} HTTP/{}.{}\n", request.method, request.address, request.version.major, request.version.minor));
for (const auto& [key, value] : moreHeaders)
{
MIJIN_HTTP_WRITE(key);
MIJIN_HTTP_WRITE(": ");
MIJIN_HTTP_WRITE(value);
MIJIN_HTTP_WRITE("\n");
MIJIN_HTTP_WRITE(std::format("{}: {}\n", key, value));
}
for (const auto& [key, value] : request.headers)
{
MIJIN_HTTP_WRITE(key);
MIJIN_HTTP_WRITE(": ");
MIJIN_HTTP_WRITE(value);
MIJIN_HTTP_WRITE("\n");
MIJIN_HTTP_WRITE(std::format("{}: {}\n", key, value));
}
MIJIN_HTTP_WRITE("\n");
@ -163,7 +156,106 @@ Task<StreamResult<HTTPResponse>> HTTPStream::c_readResponse() noexcept
co_return response;
}
Task<StreamResult<HTTPResponse>> HTTPClient::c_request(ip_address_t address, std::uint16_t port, bool https,
HTTPRequest request) noexcept
{
if (const StreamError error = createSocket(address, port, https); error != StreamError::SUCCESS)
{
co_return error;
}
if (!request.headers.contains("connection"))
{
request.headers.emplace("connection", "keep-alive");
}
StreamResult<HTTPResponse> response = co_await stream_->c_request(request);
if (response.isError())
{
disconnect();
if (const StreamError error = createSocket(address, port, https); error != StreamError::SUCCESS)
{
co_return error;
}
response = co_await stream_->c_request(request);
}
co_return response;
}
Task<StreamResult<HTTPResponse>> HTTPClient::c_request(const URL& url, HTTPRequest request) noexcept
{
if (url.getHost().empty())
{
co_return StreamError::UNKNOWN_ERROR;
}
std::uint16_t port = url.getPort();
bool https = false;
if (equalsIgnoreCase(url.getScheme(), "http"))
{
port = (port != 0) ? port : 80;
}
else if (equalsIgnoreCase(url.getScheme(), "https"))
{
port = (port != 0) ? port : 443;
https = true;
}
else
{
co_return StreamError::UNKNOWN_ERROR;
}
Optional<ip_address_t> ipAddress = ipAddressFromString(url.getHost());
// TODO: lookup host
if (ipAddress.empty())
{
co_return StreamError::UNKNOWN_ERROR;
}
if (!request.headers.contains("host"))
{
request.headers.emplace("host", url.getHost());
}
request.address = url.getPathQueryFragment();
if (request.address.empty())
{
request.address = "/";
}
co_return co_await c_request(*ipAddress, port, https, std::move(request));
}
void HTTPClient::disconnect() noexcept
{
if (socket_ == nullptr)
{
return;
}
stream_.destroy();
socket_ = nullptr;
}
StreamError HTTPClient::createSocket(ip_address_t address, std::uint16_t port, bool https) noexcept
{
if (socket_ != nullptr && address == lastIP_ && port == lastPort_ && https == lastWasHttps_)
{
return StreamError::SUCCESS;
}
disconnect();
MIJIN_ASSERT(!https, "HTTPS not supported yet.");
std::unique_ptr<TCPSocket> newSocket = std::make_unique<TCPSocket>();
if (const StreamError error = newSocket->open(address, port); error != StreamError::SUCCESS)
{
return error;
}
lastIP_ = address;
lastPort_ = port;
lastWasHttps_ = https;
socket_ = std::move(newSocket);
stream_.construct(socket_->getStream());
return StreamError::SUCCESS;
}
}
#undef MIJIN_HTTP_WRITE
#undef MIJIN_HTTP_READLINE
#undef MIJIN_HTTP_CHECKREAD
#undef MIJIN_HTTP_READLINE

View File

@ -7,6 +7,9 @@
#include <memory>
#include <string>
#include <map>
#include "./socket.hpp"
#include "./url.hpp"
#include "../container/boxed_object.hpp"
#include "../io/stream.hpp"
namespace mijin
@ -19,12 +22,11 @@ struct HTTPVersion
struct HTTPRequest
{
HTTPVersion version = {1, 1};
std::string address;
std::string method = "GET";
std::multimap<std::string, std::string> headers;
std::string body;
explicit HTTPRequest(std::string address_) noexcept : address(std::move(address_)) {}
};
struct HTTPResponse
@ -50,6 +52,23 @@ private:
Task<StreamError> c_writeRequest(const HTTPRequest& request) noexcept;
Task<StreamResult<HTTPResponse>> c_readResponse() noexcept;
};
class HTTPClient
{
private:
std::unique_ptr<Socket> socket_;
mijin::BoxedObject<HTTPStream> stream_;
ip_address_t lastIP_;
std::uint16_t lastPort_ = 0;
bool lastWasHttps_ = false;
public:
~HTTPClient() noexcept { disconnect(); }
Task<StreamResult<HTTPResponse>> c_request(ip_address_t address, std::uint16_t port, bool https, HTTPRequest request) noexcept;
Task<StreamResult<HTTPResponse>> c_request(const URL& url, HTTPRequest request = {}) noexcept;
void disconnect() noexcept;
private:
StreamError createSocket(ip_address_t address, std::uint16_t port, bool https) noexcept;
};
}
#endif // !defined(MIJIN_NET_HTTP_HPP_INCLUDED)

View File

@ -265,16 +265,25 @@ mijin::Task<StreamError> TCPStream::c_readRaw(std::span<std::uint8_t> buffer, co
MIJIN_ASSERT(isOpen(), "Socket is not open.");
setAsync(true);
if (buffer.empty())
{
co_return StreamError::SUCCESS;
}
while(true)
{
const long bytesRead = osRecv(handle_, buffer, readFlags(options));
if (bytesRead >= 0)
if (bytesRead > 0)
{
if (outBytesRead != nullptr) {
*outBytesRead = static_cast<std::size_t>(bytesRead);
}
co_return StreamError::SUCCESS;
}
else if (bytesRead == 0)
{
co_return StreamError::CONNECTION_CLOSED;
}
else if (errno != EAGAIN)
{
co_return translateErrno();
@ -286,14 +295,25 @@ mijin::Task<StreamError> TCPStream::c_readRaw(std::span<std::uint8_t> buffer, co
mijin::Task<StreamError> TCPStream::c_writeRaw(std::span<const std::uint8_t> buffer)
{
MIJIN_ASSERT(isOpen(), "Socket is not open.");
if (buffer.empty())
{
co_return StreamError::SUCCESS;
}
setAsync(true);
while (true)
{
if (osSend(handle_, buffer, 0) >= 0)
const long bytesSent = osSend(handle_, buffer, 0);
if (bytesSent == static_cast<long>(buffer.size()))
{
co_return StreamError::SUCCESS;
}
else if (bytesSent == 0)
{
co_return StreamError::CONNECTION_CLOSED;
}
else if (errno != EAGAIN)
{
co_return translateErrno();