mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-01 01:01:37 -06:00
476 lines
21 KiB
C++
476 lines
21 KiB
C++
/*****************************************************************************************
|
|
* *
|
|
* OpenSpace *
|
|
* *
|
|
* Copyright (c) 2014-2024 *
|
|
* *
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
|
* software and associated documentation files (the "Software"), to deal in the Software *
|
|
* without restriction, including without limitation the rights to use, copy, modify, *
|
|
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
|
* permit persons to whom the Software is furnished to do so, subject to the following *
|
|
* conditions: *
|
|
* *
|
|
* The above copyright notice and this permission notice shall be included in all copies *
|
|
* or substantial portions of the Software. *
|
|
* *
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
|
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
|
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
|
****************************************************************************************/
|
|
|
|
#ifndef __OPENSPACE_CORE___HTTPREQUEST___H__
|
|
#define __OPENSPACE_CORE___HTTPREQUEST___H__
|
|
|
|
#include <ghoul/misc/boolean.h>
|
|
#include <atomic>
|
|
#include <condition_variable>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
#include <chrono>
|
|
|
|
namespace openspace {
|
|
|
|
/**
|
|
* This class performs a synchronous HTTP request to the provided URL. Any result that is
|
|
* returned based on this request is returned through three callback functions that can be
|
|
* registered using the #onHeader, #onProgress, and #onData functions. Calling these
|
|
* functions will overwrite any previously registered handler. The ProgressCallback can be
|
|
* used to stop the download if the handler returns `false`.
|
|
*
|
|
* The workflow for this class:
|
|
* 1. Create a new object with the URL that points to the location from which the data
|
|
* should be loaded
|
|
* 2. Register the callbacks that are needed (at least the #onData callback)
|
|
* 3. Start the download with the #perform function
|
|
*/
|
|
class HttpRequest {
|
|
public:
|
|
/**
|
|
* This callback is called every time there is progress to report for the download. It
|
|
* transmits currently downloaded number of bytes (which can be 0 if the download
|
|
* hasn't started yet) and the total number of bytes if that value is known. The
|
|
* return value determines if the download should continue (if the function returns
|
|
* `true`) or if it should abort (if `false` is returned).
|
|
*
|
|
* \param downloadedBytes The number of bytes that have been downloaded thus far or 0
|
|
* if the download hasn't started yet
|
|
* \param totalBytes The total number of bytes for the download if it is known, or a
|
|
* std::nullopt of the total size is unknown
|
|
* \return `true` if the download should continue or `false` if it should be aborted
|
|
*/
|
|
using ProgressCallback = std::function<
|
|
bool(size_t downloadedBytes, std::optional<size_t> totalBytes)
|
|
>;
|
|
|
|
/**
|
|
* This callback is executed every time a chunk of data arrived for the download. The
|
|
* parameters point to the buffer of this new incoming data and the number of bytes
|
|
* that have arrived. The buffer pointed to will most likely only be valid during the
|
|
* time of the callback and it is the callbacks responsibility to store the contents
|
|
* of the buffer before the callback returns. If the return value is `true`, the
|
|
* download continues, if it is `false`, this signals to the library that an error has
|
|
* occurred from which recovery is not possible.
|
|
*
|
|
* \param buffer The pointer to the beginning of the buffer where the new incoming
|
|
* data is located. This buffer is only valid during the execution of this
|
|
* callback and the contents of the buffer should be copied to a different
|
|
* location
|
|
* \param size The number of bytes that are contained in the \p buffer
|
|
* \return `true` if the download should continue or `false` if it should be aborted
|
|
*/
|
|
using DataCallback = std::function<bool(char* buffer, size_t size)>;
|
|
|
|
/**
|
|
* This callback is executed when the header of an HTTP request is received
|
|
* successfully. The provided buffer and size contain the contents of the header field
|
|
* for the response. The buffer pointed to will most likely only be valid during the
|
|
* time of the callback and it is the callbacks responsibility to store the contents
|
|
* of the buffer before the callback returns. If the return value is `true`, the
|
|
* download continues, if it is `false`, this signals to the library that an error has
|
|
* occurred from which recovery is not possible. If this function returns `false`, it
|
|
* will cause the main download to not start at all.
|
|
*
|
|
* \param buffer The pointer to the beginning of the buffer where the header
|
|
* information is located. This buffer is only valid during the execution of
|
|
* this callback and the contents of the buffer should be copied to a different
|
|
* location
|
|
* \param size The number of bytes that are contained in the \p buffer
|
|
* \return `true` if the download should continue or `false` if it should be aborted
|
|
*/
|
|
using HeaderCallback = std::function<bool(char* buffer, size_t size)>;
|
|
|
|
/**
|
|
* Creates a new HttpRequest object that will try to download the contents at the
|
|
* provided \p url.
|
|
*
|
|
* \param url The URL that should be requested by this HttpRequest
|
|
*
|
|
* \pre \p url must not be empty
|
|
*/
|
|
explicit HttpRequest(std::string url);
|
|
|
|
/**
|
|
* Registers a callback that will be called when the header for the request has been
|
|
* transmitted successfully. The contents of the header will be passed into the
|
|
* callback and the callback returns whether the request should proceed or be aborted.
|
|
*
|
|
* \param cb The callback that should be registered. This will silently replace any
|
|
* previously registered callback
|
|
*/
|
|
void onHeader(HeaderCallback cb);
|
|
|
|
/**
|
|
* Registers a callback that will be called whenever there is progress to be reported
|
|
* on the transfer of the request's body. The callback will receive information about
|
|
* the number of bytes that have been downloaded and the total number of bytes that
|
|
* should be downloaded to complete the request. This information might not always be
|
|
* available. The callback's return value determines whether the request should
|
|
* continue or be aborted.
|
|
*
|
|
* \param cb The callback that should be registered. This will silently replace any
|
|
* previously registered callback
|
|
*/
|
|
void onProgress(ProgressCallback cb);
|
|
|
|
/**
|
|
* Registers a callback that will be called whenever there is new data that has been
|
|
* received from the request. It is this callback's responsibility to store that data
|
|
* in a place that is persistent across multiple calls to the callback, usually by
|
|
* storing it in an external buffer and appending to it. The callback can return
|
|
* whether the download should proceed (by returning `true`) or be aborted (by
|
|
* returning `false`).
|
|
*
|
|
* \param cb The callback that should be registered. This will silently replace any
|
|
* previously registered callback
|
|
*/
|
|
void onData(DataCallback cb);
|
|
|
|
/**
|
|
* Performs the request to the URL provided in the constructor. As this request is
|
|
* handled synchronously, this function will only return once the request has been
|
|
* completed successfully or failed. During this call, the registered callbacks will
|
|
* be called repeatedly until the request finishes. This function returns whether the
|
|
* request was completed successfully or failed.
|
|
*
|
|
* \param timeout The amount of time the request will wait before aborting due to the
|
|
* server not responding. If this value is 0, there is no timeout on the
|
|
* request.
|
|
* \return `true` if the request completed successfully, `false` otherwise
|
|
*/
|
|
bool perform(std::chrono::milliseconds timeout = std::chrono::milliseconds(0));
|
|
|
|
/**
|
|
* Returns the URL that was passed into the constructor of this HttpRequest.
|
|
*
|
|
* \return The URL that was passed into the constructor of this HttpRequest
|
|
*/
|
|
const std::string& url() const;
|
|
|
|
private:
|
|
/// The callback that will be called when the header was received successfully
|
|
HeaderCallback _onHeader;
|
|
|
|
/// The callback that will be called when there is progress to be reported
|
|
ProgressCallback _onProgress;
|
|
|
|
/// The callback that will be called when there is data to be stored away
|
|
DataCallback _onData;
|
|
|
|
/// The URL that this HttpRequest is going to request
|
|
std::string _url;
|
|
};
|
|
|
|
/**
|
|
* This abstract base class uses the HttpRequest class to perform an asynchronous
|
|
* download. Every subclass needs to implement at least the #handleData function that will
|
|
* be called every time a chunk of data has been received from the request. The download
|
|
* is started through the #start function and it is possible to turn this into a
|
|
* synchronous download by executing the #wait function directly afterwards. If a
|
|
* HttpRequest::ProgressCallback has been registered through the #onProgress function,
|
|
* that callback is called every time the download some progress to report.
|
|
*/
|
|
class HttpDownload {
|
|
public:
|
|
/**
|
|
* Creates a new HttpDownload that will start to download the file pointed to by the
|
|
* \p url parameter as soon as the download is #start ed.
|
|
*
|
|
* \param url The URL that should be downloaded by this HttpDownload
|
|
*
|
|
* \pre \p url must not be empty
|
|
*/
|
|
explicit HttpDownload(std::string url);
|
|
|
|
/**
|
|
* Virtual destructor that will cancel the ongoing download and block until the
|
|
* download is successfully canceled.
|
|
*/
|
|
virtual ~HttpDownload();
|
|
|
|
/**
|
|
* Registers a callback that will be called whenever there is progress to be reported
|
|
* on the file's download. The callback will receive information about the number of
|
|
* bytes that have been downloaded and the total number of bytes that should be
|
|
* downloaded. This information might not always be available. The callback's return
|
|
* value determines whether the request should continue or be aborted.
|
|
*
|
|
* \param progressCallback The callback that should be registered. This will silently
|
|
* replace any previously registered callback
|
|
*/
|
|
void onProgress(HttpRequest::ProgressCallback progressCallback);
|
|
|
|
/**
|
|
* Starts the asynchronous download of the file by starting a new thread that will
|
|
* take care of the download, meaning that this function will return almost
|
|
* instantaneously. If the HttpDownload is already downloading a file this function
|
|
* does nothing.
|
|
*
|
|
* \param timeout The number of milliseconds that the download will be kept alive
|
|
* while waiting for a reply from the server. If this value is 0, the
|
|
* connection will never time out
|
|
*/
|
|
void start(std::chrono::milliseconds timeout = std::chrono::milliseconds(0));
|
|
|
|
/**
|
|
* Cancels the ongoing download. Because of the underlying library that is used, the
|
|
* transfer will only be aborted the next time any piece of data is received or the
|
|
* library reports any progress.
|
|
*/
|
|
void cancel();
|
|
|
|
/**
|
|
* This function will wait until the download has completed and will return the
|
|
* success of the download back to the caller.
|
|
*
|
|
* \return `true` if the downloaded succeeded or `false` if the download failed
|
|
*/
|
|
bool wait();
|
|
|
|
/**
|
|
* Returns `true` if the download has completed and it failed, or `false` if either
|
|
* the download is till ongoing or is finished and has succeeded.
|
|
*
|
|
* \return Whether the download has completed and it failed
|
|
*/
|
|
bool hasFailed() const;
|
|
|
|
/**
|
|
* Returns `true` if the download has completed successfully , or `false` if either
|
|
* the download is till ongoing or is finished and has failed.
|
|
*
|
|
* \return Whether the download has completed successfully
|
|
*/
|
|
bool hasSucceeded() const;
|
|
|
|
/**
|
|
* Returns the URL that was passed into the constructor of this HttpDownload.
|
|
*
|
|
* \return The URL that was passed into the constructor of this HttpDownload
|
|
*/
|
|
const std::string& url() const;
|
|
|
|
protected:
|
|
/**
|
|
* This abstract function has to be implemented by any concrete subclass to handle an
|
|
* incoming chunk of data from the download. The parameters point to the \p buffer of
|
|
* this new incoming data and the number of bytes that have arrived. The buffer
|
|
* pointed to will most likely only be valid during the time of the callback and it is
|
|
* the callbacks responsibility to store the contents of the buffer before the
|
|
* callback returns. If the return value is `true`, the download continues, if it is
|
|
* `false`, this signals to the library that an error has occurred from which recovery
|
|
* is not possible. This function will be called on a different thread from the one
|
|
* that called the #start method.
|
|
*
|
|
* \param buffer The beginning of the buffer of this chunk of data
|
|
* \param size The number of bytes that the \p buffer contains
|
|
* \return The implementation should return `true` if the downloading should continue
|
|
* and `false` if the handling of the data caused some error that the
|
|
* subclass is incapable of recovering from
|
|
*/
|
|
virtual bool handleData(char* buffer, size_t size) = 0;
|
|
|
|
/**
|
|
* This function is called before the downloading starts and can be used by subclasses
|
|
* to perform one-time setup functions, such as opening a file, reserving a block of
|
|
* storage, etc. This function guaranteed to be only called once per HttpDownload. The
|
|
* return value determines if the setup operation completed successfully or if an
|
|
* error occurred that will cause the download to be terminated. This function will be
|
|
* called on a different thread from the one that called the #start method.
|
|
*
|
|
* \return `true` if the setup completed successfully and `false` if the setup
|
|
* failed unrecoverably
|
|
*/
|
|
virtual bool setup();
|
|
|
|
/**
|
|
* This function is called after the downloading has finished and before a potential
|
|
* call to #wait is performed. This function can be used by a subclass to perform
|
|
* one-time operations that are required when the downloading fininshes, such as
|
|
* closing file handles, committing some memory etc. The return value of this function
|
|
* signals whether the teardown completed successfully. This function will be called
|
|
* on a different thread from the one that called the #start method.
|
|
*
|
|
* \return `true` if the teardown completed successfully and `false` if it failed
|
|
*/
|
|
virtual bool teardown();
|
|
|
|
private:
|
|
/// The callback that will be called whenever there is some progress to be reported
|
|
HttpRequest::ProgressCallback _onProgress;
|
|
|
|
/// Value indicating whether the HttpDownload is currently downloading a file
|
|
bool _isDownloading = false;
|
|
|
|
/// Value indicating whether the download is finished
|
|
bool _isFinished = false;
|
|
|
|
/// Value indicated whether the download was successful
|
|
bool _isSuccessful = false;
|
|
|
|
/// Marker telling the downloading thread that the download should be cancelled
|
|
bool _shouldCancel = false;
|
|
|
|
/// The HttpRequest class that will be used for the download
|
|
HttpRequest _httpRequest;
|
|
|
|
/// The thread that contains the HttpRequest to download the file
|
|
std::thread _downloadThread;
|
|
|
|
/// This condition variable is used by the #wait function to be able to wait for
|
|
/// completion of the downloading thread
|
|
std::condition_variable _downloadFinishCondition;
|
|
};
|
|
|
|
/**
|
|
* This specific subclass of the HttpDownload downloads the contents of the provided URL
|
|
* into a file on disk. By default, an existing file will not be overwritten and will
|
|
* cause the download to fail. This behavior can be overwritten through a parameter in the
|
|
* constructor of this class.
|
|
*/
|
|
class HttpFileDownload : public HttpDownload {
|
|
public:
|
|
BooleanType(Overwrite);
|
|
|
|
/**
|
|
* Constructor that will create a HttpFileDownload which will download the contents of
|
|
* the provided \p url to the \p destinationPath. If the \p destinationPath already
|
|
* contains a file and \p overwrite is Overwrite::No, the download will fail; if it is
|
|
* Overwrite::Yes, the existing content at the \p destinationPath will be overwritten.
|
|
*/
|
|
HttpFileDownload(std::string url, std::filesystem::path destinationPath,
|
|
Overwrite overwrite = Overwrite::No);
|
|
|
|
/**
|
|
* This destructor will cancel any ongoing download and wait for its completion, so it
|
|
* might not block for a short amount of time.
|
|
*/
|
|
virtual ~HttpFileDownload() override = default;
|
|
|
|
/**
|
|
* Returns the path where the contents of the URL provided in the constructor will be
|
|
* saved to.
|
|
*
|
|
* \return The path where URL will be downloaded to
|
|
*/
|
|
std::filesystem::path destination() const;
|
|
|
|
private:
|
|
/**
|
|
* Will create all directories that are necessary to reach _destination and then
|
|
* fight with other HttpFileDownloads to get one of a limited number of file handles
|
|
* to open _file.
|
|
*/
|
|
bool setup() override;
|
|
|
|
/**
|
|
* Closes the _file and returns the handle back to the pool of available handles.
|
|
*/
|
|
bool teardown() override;
|
|
|
|
/**
|
|
* Stores the chunk of data into the _file handle.
|
|
*/
|
|
bool handleData(char* buffer, size_t size) override;
|
|
|
|
/// A flag whether this HttpFileDownload got a handle from the limited supply of
|
|
/// handles. This limit is used to prevent all HttpFileDownloads from getting file
|
|
/// handles from the operating system as that resource is limited and downloads would
|
|
/// fail unrecoverably if no handles are available. So we limit the maximum number and
|
|
/// if that number is exceeded, the HttpFileDownload will wait until a handle is
|
|
/// available
|
|
std::atomic_bool _hasHandle = false;
|
|
|
|
/// The destination path where the contents of the URL provided in the constructor
|
|
/// will be saved to
|
|
std::filesystem::path _destination;
|
|
|
|
/// The file handle to the _destination used to save incoming chunks
|
|
std::ofstream _file;
|
|
|
|
/// Mutex that will be prevent multiple HttpFileDownloads to simultaneously try to
|
|
/// create the necessary intermediate directories, which would cause issues
|
|
static std::mutex _directoryCreationMutex;
|
|
|
|
/// The maximum number of file handles that all HttpFileDownloads combined can use up
|
|
static constexpr int MaxFileHandles = 32;
|
|
|
|
/// Stores the number of currently open file handles across all HttpFileDownloads
|
|
static std::atomic_int nCurrentFileHandles;
|
|
};
|
|
|
|
/**
|
|
* This concerete HttpDownload subclass downloads the contents of the URL passed into the
|
|
* constructor into a buffer of memory that can be retrieve. Please note that that buffer
|
|
* should only be used accessed once the HttpDownload::hasFinished function returns
|
|
* `true`.
|
|
*/
|
|
class HttpMemoryDownload : public HttpDownload {
|
|
public:
|
|
/**
|
|
* Creates an instance of a HttpMemoryDownload that will download the contents of the
|
|
* \p url into memory.
|
|
*
|
|
* \param url The URL whose contents should be downloaded
|
|
*/
|
|
explicit HttpMemoryDownload(std::string url);
|
|
|
|
/**
|
|
* This destructor will cancel any ongoing download and wait for its completion, so it
|
|
* might not block for a short amount of time.
|
|
*/
|
|
virtual ~HttpMemoryDownload() override = default;
|
|
|
|
/**
|
|
* Returns a reference to the buffer that is used to store the contents of the URL
|
|
* passed in the constructor. Please observe that while the HttpDownload::hasFinished
|
|
* method returns `false`, this buffer will be changed by a different thread and
|
|
* access is not thread-safe. After that function returns `true`, it is safe to access
|
|
* the buffer.
|
|
*
|
|
* \return A reference to the buffer used to hold the contents of the URL
|
|
*/
|
|
const std::vector<char>& downloadedData() const;
|
|
|
|
private:
|
|
/**
|
|
* Stores each downloaded chunk into the stored buffer.
|
|
*/
|
|
bool handleData(char* buffer, size_t size) override;
|
|
|
|
/// The buffer where the downloaded chunks are accumulated
|
|
std::vector<char> _buffer;
|
|
};
|
|
|
|
} // namespace openspace
|
|
|
|
#endif // __OPENSPACE_CORE___HTTPREQUEST___H__
|