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
|