1662f51193
We parse a date/time for headers now with a specific method that returns a QDateTime We added a partial (incremental) download feature to avoid the need to wait until completed. This also allows us to lower mem usage by spooling to disk as data comes in.
142 lines
5.4 KiB
C++
142 lines
5.4 KiB
C++
/*
|
|
* 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>
|
|
#include <QDateTime>
|
|
|
|
#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>
|
|
#include <boost/beast/http/field.hpp>
|
|
|
|
class SimpleHttpClient : public QObject, public std::enable_shared_from_this<SimpleHttpClient>
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
/**
|
|
* 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
|
|
*/
|
|
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);
|
|
|
|
/**
|
|
* 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);
|
|
|
|
enum ErrorType {
|
|
NoError,
|
|
DnsIssues,
|
|
SslIssues,
|
|
SendIssues,
|
|
ReceiveIssues,
|
|
PeerIssues, // remote server gave error
|
|
};
|
|
|
|
Streaming::ConstBuffer responseBody() const;
|
|
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;
|
|
|
|
QDateTime headerDate(boost::beast::http::field field) const;
|
|
|
|
boost::beast::http::request<boost::beast::http::vector_body<char>> request() const;
|
|
|
|
signals:
|
|
void finished();
|
|
void errored(ErrorType type);
|
|
void dataAvailable(const Streaming::ConstBuffer &data);
|
|
|
|
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);
|
|
void readHeaderReply(boost::beast::error_code ec, std::size_t bytesTransferred);
|
|
void readReply(boost::beast::error_code ec, std::size_t bytesTransferred);
|
|
void readPartialReply(boost::beast::error_code ec, std::size_t bytesTransferred);
|
|
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
|
|
std::unique_ptr<boost::beast::http::response<boost::beast::http::dynamic_body>> m_response;
|
|
// for HEAD requests
|
|
boost::beast::http::response<boost::beast::http::empty_body> m_headResponse;
|
|
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;
|
|
|
|
Streaming::ConstBuffer m_responseBody;
|
|
|
|
|
|
ErrorType m_error = NoError;
|
|
bool m_incremental = false;
|
|
};
|
|
|
|
#endif
|