Files
thehub/libs/apputils/SimpleHttpClient.h
T

142 lines
5.4 KiB
C++
Raw Permalink Normal View History

2025-11-05 13:01:13 +01:00
/*
* This file is part of the Flowee project
* Copyright (C) 2025 Tom Zander <tom@flowee.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SIMPLEHTTPCLIENT_H
#define SIMPLEHTTPCLIENT_H
#include <QObject>
2025-11-17 11:56:14 +01:00
#include <QDateTime>
2025-11-05 13:01:13 +01:00
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <streaming/ConstBuffer.h>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/ssl.hpp>
2025-11-05 19:19:43 +01:00
#include <boost/beast/http/field.hpp>
2025-11-05 13:01:13 +01:00
class SimpleHttpClient : public QObject, public std::enable_shared_from_this<SimpleHttpClient>
{
Q_OBJECT
public:
2025-11-05 19:24:24 +01:00
/**
* Example:
* @code
* // for a HEAD
*
* boost::beast::http::request<boost::beast::http::vector_body<char>> request;
* request.version(11); // http 1.1
* request.method(boost::beast::http::verb::head);
* request.target("/index.html");
* request.set(boost::beast::http::field::host, "flowee.org");
*
* // or for a POST;
*
* boost::beast::http::request<boost::beast::http::vector_body<char>> request;
* request.version(11);
* request.method(boost::beast::http::verb::post);
* request.target("/upload");
* request.set(boost::beast::http::field::host, "flowee.org");
* request.set(boost::beast::http::field::content_type, "application/octet-stream");
* request.body() = std::vector<char>(data.begin(), data.end());
*
* // or for a GET
*
* boost::beast::http::request<boost::beast::http::vector_body<char>> request;
* request.version(11);
* request.method(boost::beast::http::verb::get);
* request.target("/image.png");
* request.set(boost::beast::http::field::host, "flowee.org");
* @endcode
*/
2025-11-05 13:01:13 +01:00
static std::shared_ptr<SimpleHttpClient> create(boost::asio::io_context &context,
boost::asio::ssl::context &sslContext,
const boost::beast::http::request<boost::beast::http::vector_body<char>> &request);
2025-11-17 11:56:14 +01:00
/**
* Create a client that does not wait for the entire download to finish before emitting a signal.
* This is very similar in usage to the basic create() method, but the client will emit
* "dataAvailable()" signals that HAVE to be handled because they hold the data payload.
* The responseBody will always return an empty field in this version.
*/
static std::shared_ptr<SimpleHttpClient> createIncremental(boost::asio::io_context &context,
boost::asio::ssl::context &sslContext,
const boost::beast::http::request<boost::beast::http::vector_body<char>> &request);
2025-11-05 13:01:13 +01:00
enum ErrorType {
NoError,
DnsIssues,
SslIssues,
SendIssues,
ReceiveIssues,
PeerIssues, // remote server gave error
};
Streaming::ConstBuffer responseBody() const;
2025-11-05 19:19:43 +01:00
ErrorType error() const;
int httpErrorCode() const;
/// Response headers access.
std::string_view headerValue(const char *name) const;
/// Response headers access.
std::string_view headerValue(boost::beast::http::field field) const;
2025-11-05 13:01:13 +01:00
2025-11-17 11:56:14 +01:00
QDateTime headerDate(boost::beast::http::field field) const;
boost::beast::http::request<boost::beast::http::vector_body<char>> request() const;
2025-11-05 13:01:13 +01:00
signals:
void finished();
void errored(ErrorType type);
2025-11-17 11:56:14 +01:00
void dataAvailable(const Streaming::ConstBuffer &data);
2025-11-05 13:01:13 +01:00
private:
SimpleHttpClient(boost::asio::io_context &context, boost::asio::ssl::context &sslContext);
void resolve();
void connect(boost::beast::error_code ec, boost::asio::ip::tcp::resolver::results_type results);
void sslHandshake(boost::beast::error_code ec);
void sendRequest(boost::beast::error_code ec);
void requestSent(boost::beast::error_code ec, std::size_t bytesTransferred);
2025-11-05 19:19:43 +01:00
void readHeaderReply(boost::beast::error_code ec, std::size_t bytesTransferred);
2025-11-05 13:01:13 +01:00
void readReply(boost::beast::error_code ec, std::size_t bytesTransferred);
2025-11-17 11:56:14 +01:00
void readPartialReply(boost::beast::error_code ec, std::size_t bytesTransferred);
2025-11-05 13:01:13 +01:00
void connectionShutdown(boost::beast::error_code ec);
void setError(ErrorType newError);
boost::asio::ip::tcp::resolver m_resolver;
boost::beast::ssl_stream<boost::beast::tcp_stream> m_stream;
boost::beast::http::request<boost::beast::http::vector_body<char>> m_request;
boost::beast::flat_buffer m_readBuffer; // for the response
2025-11-17 11:56:14 +01:00
std::unique_ptr<boost::beast::http::response<boost::beast::http::dynamic_body>> m_response;
// for HEAD requests
2025-11-05 19:19:43 +01:00
boost::beast::http::response<boost::beast::http::empty_body> m_headResponse;
2025-11-17 11:56:14 +01:00
std::unique_ptr<boost::beast::http::response_parser<boost::beast::http::empty_body>> m_parser;
// for partial requests
std::unique_ptr<boost::beast::http::response_parser<boost::beast::http::dynamic_body>> m_partialParser;
2025-11-05 13:01:13 +01:00
Streaming::ConstBuffer m_responseBody;
2025-11-17 11:56:14 +01:00
2025-11-05 13:01:13 +01:00
ErrorType m_error = NoError;
2025-11-17 11:56:14 +01:00
bool m_incremental = false;
2025-11-05 13:01:13 +01:00
};
#endif