NetServices: Initial implementation of BHttpResult

Incomplete class, but will provide the basis to start working on the internals of the BHttpSession.

Change-Id: I3ca14b7bd823fc1b4a5a32f5784592d214c4e9a7
This commit is contained in:
Niels Sascha Reedijk
2022-03-05 14:56:57 +00:00
parent 6ce6e96470
commit 1baacbfebf
6 changed files with 612 additions and 1 deletions
+293
View File
@@ -0,0 +1,293 @@
/*
* Copyright 2022 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Niels Sascha Reedijk, [email protected]
*
* Corresponds to:
* headers/private/netservices2/HttpResult.h hrev?????
* src/kits/network/libnetservices2/HttpResult.cpp hrev?????
*/
#if __cplusplus >= 201703L
/*!
\file HttpResult.h
\ingroup netservices
\brief Provides classes and tools to handle HTTP responses.
\since Haiku R1
*/
namespace BPrivate {
namespace Network {
/*!
\struct BHttpStatus
\ingroup netservices
\brief Represents the HTTP status code and status text of an incoming response.
The HTTP Standard specifies that each response should include a
<a href="https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2">three digit status
code</a> and a phrase that describes the status code. When processing the response status, you
should ignore the textual representation and only look at the status code.
Instances of this class provide a representation of the actual status information in the
response. There is no additional validation done, other than that the structure of the
status line is in compliance with the HTTP specification.
\since Haiku R1
*/
/*!
\var int16 BHttpStatus::code
\brief The three digit status code
This code represents the result of how the server is processing or has processed a request.
Common codes are \c 200 indicating success, or \c 404 indicating that the requested resource is
not found.
See <a href="https://datatracker.ietf.org/doc/html/rfc7231#page-48">RFC7231 and its
references</a> for more information.
\since Haiku R1
*/
/*!
\var BString BHttpStatus::text
\brief A textual representation of the result status.
As defined in the
<a href="https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2">specification</a>, the
status text should only be taken as informative. Only the \ref BHttpStatus::code should be used
for further processing.
\since Haiku R1
*/
/*!
\class BHttpResult
\ingroup netservices
\brief Unique object to wait for and access a HTTP response data.
Once you have scheduled a HTTP request in a HTTP session, you will get an object of this type
as a return value. This object allows you to track the progress of receiving the response, and
to inspect the status, the headers and the data as the response is received from the server.
The object is a future type, meaning that eventually it will contain the data or an error.
The \ref Status(), \ref Fields() and \ref Body() methods will yield the respective data. If it
is not yet received, they will block until it is available. You can also use the non-blocking
methods to check if data is available yet.
The result can either be a partial or completed HTTP Response, or an error. The partial aspect
is represented by the fact that the status line, the fields and the body are loaded
progressively and can be accessed as soon as they have been received. The meaning of a HTTP
response is defined by the HTTP standards. For example, a GET request can return a response
with a 200 status code, a set of headers and a body. But it can also return a 404 response,
indicating that the resource was not found at the location. It is important to note that both
responses are valid HTTP responses within the context of this API. This means that you can
still use the access methods of this class to access data from the 404 response without raising
an exception.
When there are errors during the request that lead to the situation where there is no valid
response according to the HTTP specification, then this object goes into an error state. This
means that the access methods of this object will throw an exception of the
\ref BNetServiceError type.
A special property of this object is that it is unique. This means it cannot be copied, only
moved. Objects that have moved from, are in an invalid state, and will always raise a
\ref BRuntimeError exception when they are used.
\since Haiku R1
*/
/*!
\name Constructors, assignment operators and destructor
*/
//! @{
/*!
\fn BHttpResult::BHttpResult(BHttpResult &&other) noexcept
\brief Move constructor.
\param other The object to move from. The \a other object will be in an invalid state and will
always throw exceptions when it is used.
\since Haiku R1
*/
/*!
\fn BHttpResult::BHttpResult(const BHttpResult &other)=delete
\brief Copy constructor is disabled.
These objects cannot be copied.
\since Haiku R1
*/
/*!
\fn BHttpResult& BHttpResult::operator=(BHttpResult &&other) noexcept
\brief Move operator.
\param other The object to move from. The \a other object will be in an invalid state and will
always throw exceptions when it is used.
\since Haiku R1
*/
/*!
\fn BHttpResult& BHttpResult::operator=(const BHttpResult &other)=delete
\brief Copy assignment is disabled.
These objects cannot be copied.
\since Haiku R1
*/
/*!
\fn BHttpResult::~BHttpResult()
\brief Destructor
\since Haiku R1
*/
//! @}
/*!
\name Non-blocking status functions
*/
//! @{
/*!
\fn int32 BHttpResult::Identity() const
\brief Unique identifier for the response.
The identifier can be used to cancel requests in a BHttpSession. It can also be uses to check
incoming asynchronous event messages against the response.
\return A unique identifier that associates this response with an active or completed request.
\exception BRuntimeException This exception is raised when the object has been moved from and
is thus no longer valid.
\since Haiku R1
*/
/*!
\fn bool BHttpResult::HasStatus() const
\brief Check if the status is available.
\retval true The status line of the response is available using the \ref Status() method.
\retval false The line is not yet available. Any call to \ref Status() will block.
\exception BRuntimeException This exception is raised when the object has been moved from and
is thus no longer valid.
\since Haiku R1
*/
/*!
\fn bool BPrivate::Network::BHttpResult::HasHeaders() const
\brief Check if the headers are available.
\retval true The headers of the response is available using the \ref Headers() method.
\retval false They are not yet available. Any call to \ref Headers() will block.
\exception BRuntimeException This exception is raised when the object has been moved from and
is thus no longer valid.
\since Haiku R1
*/
/*!
\fn bool BPrivate::Network::BHttpResult::HasBody() const
\brief Check if the body is available.
\retval true The body of the response is available using the \ref Body() method.
\retval false The body is not yet available. Any call to \ref Body() will block.
\exception BRuntimeException This exception is raised when the object has been moved from and
is thus no longer valid.
\since Haiku R1
*/
/*!
\fn bool BPrivate::Network::BHttpResult::IsCompleted() const
\brief Check if the request is completed.
A request is completed when the status, headers and body have been received, or an error was
raised while receiving the data.
\exception BRuntimeException This exception is raised when the object has been moved from and
is thus no longer valid.
\since Haiku R1
*/
//! @}
/*!
\name Blocking Data Access
*/
//! @{
/*!
\fn const BHttpStatus& BHttpResult::Status() const
\brief Retrieve the status line of the HTTP response.
If the status line is not yet available, then this method call will block until it is. You can
use the \ref HasStatus() method to do a non-blocking check if the status is available.
\returns A const reference to the \ref BHttpStatus object that describes the status of the
response.
\exception BRuntimeException This exception is raised when the object has been moved from and
is thus no longer valid.
\exception BNetServicesError This exception is raised when there was an error that prevented
completely retrieving and parsing the HTTP response.
\since Haiku R1
*/
//! @}
} // namespace BPrivate
} // namespace Network
#endif
+65
View File
@@ -0,0 +1,65 @@
/*
* Copyright 2022 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _B_HTTP_RESULT_H_
#define _B_HTTP_RESULT_H_
#include <memory>
#include <String.h>
namespace BPrivate {
namespace Network {
struct HttpResultPrivate;
struct BHttpStatus
{
int16 code = 0;
BString text;
};
class BHttpResult
{
public:
// Constructors and destructor
BHttpResult(const BHttpResult& other) = delete;
BHttpResult(BHttpResult&& other) noexcept;
~BHttpResult();
// Assignment operators
BHttpResult& operator=(const BHttpResult& other) = delete;
BHttpResult& operator=(BHttpResult&& other) noexcept;
// Blocking Access Functions
const BHttpStatus& Status() const;
// BHttpHeaders& Headers() const;
// BHttpBody& Body() const;
// Check if data is available yet
bool HasStatus() const;
bool HasHeaders() const;
bool HasBody() const;
bool IsCompleted() const;
// Identity
int32 Identity() const;
private:
friend class BHttpSession;
BHttpResult(std::shared_ptr<HttpResultPrivate> data);
std::shared_ptr<HttpResultPrivate> fData;
};
} // namespace Network
} // namespace BPrivate
#endif // _B_HTTP_RESPONSE_H_
@@ -0,0 +1,102 @@
/*
* Copyright 2022 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Niels Sascha Reedijk, [email protected]
*/
#include <ErrorsExt.h>
#include <HttpResult.h>
#include "HttpResultPrivate.h"
using namespace BPrivate::Network;
/*private*/
BHttpResult::BHttpResult(std::shared_ptr<HttpResultPrivate> data)
: fData(data)
{
}
BHttpResult::BHttpResult(BHttpResult&& other) noexcept = default;
BHttpResult::~BHttpResult()
{
if (fData)
fData->SetCancel();
}
BHttpResult&
BHttpResult::operator=(BHttpResult&& other) noexcept = default;
const BHttpStatus&
BHttpResult::Status() const
{
if (!fData)
throw BRuntimeError(__PRETTY_FUNCTION__, "The BHttpResult object is no longer valid");
status_t status = B_OK;
while (status == B_INTERRUPTED || status == B_OK) {
auto dataStatus = fData->GetStatusAtomic();
if (dataStatus == HttpResultPrivate::kError)
std::rethrow_exception(*(fData->error));
if (dataStatus >= HttpResultPrivate::kStatusReady)
return *(fData->status);
status = acquire_sem(fData->data_wait);
}
throw BRuntimeError(__PRETTY_FUNCTION__, "Unexpected error waiting for status!");
}
bool
BHttpResult::HasStatus() const
{
if (!fData)
throw BRuntimeError(__PRETTY_FUNCTION__, "The BHttpResult object is no longer valid");
return fData->GetStatusAtomic() >= HttpResultPrivate::kStatusReady;
}
bool
BHttpResult::HasHeaders() const
{
if (!fData)
throw BRuntimeError(__PRETTY_FUNCTION__, "The BHttpResult object is no longer valid");
return fData->GetStatusAtomic() >= HttpResultPrivate::kHeadersReady;
}
bool
BHttpResult::HasBody() const
{
if (!fData)
throw BRuntimeError(__PRETTY_FUNCTION__, "The BHttpResult object is no longer valid");
return fData->GetStatusAtomic() >= HttpResultPrivate::kBodyReady;
}
bool
BHttpResult::IsCompleted() const
{
if (!fData)
throw BRuntimeError(__PRETTY_FUNCTION__, "The BHttpResult object is no longer valid");
return HasBody();
}
int32
BHttpResult::Identity() const
{
if (!fData)
throw BRuntimeError(__PRETTY_FUNCTION__, "The BHttpResult object is no longer valid");
return fData->id;
}
@@ -0,0 +1,150 @@
/*
* Copyright 2022 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Niels Sascha Reedijk, [email protected]
*/
#ifndef _HTTP_RESULT_PRIVATE_H_
#define _HTTP_RESULT_PRIVATE_H_
#include <memory>
#include <optional>
#include <string>
#include <DataIO.h>
#include <OS.h>
namespace BPrivate {
namespace Network {
struct HttpResultPrivate {
// Read-only properties (multi-thread safe)
const int32 id;
// Locking and atomic variables
sem_id data_wait;
enum {
kNoData = 0,
kStatusReady,
kHeadersReady,
kBodyReady,
kError
};
int32 requestStatus = kNoData;
int32 canCancel = 0;
// Data
std::optional<BHttpStatus> status;
// std::optional<BHttpHeaders> headers;
// std::optional<BHttpBody> body;
std::optional<std::exception_ptr> error;
// Body storage
std::unique_ptr<BDataIO> owned_body = nullptr;
// std::shared_ptr<BMemoryRingIO> shared_body = nullptr;
std::string body_text;
// Utility functions
HttpResultPrivate(int32 identifier);
int32 GetStatusAtomic();
bool CanCancel();
void SetCancel();
void SetError(std::exception_ptr e);
void SetStatus(BHttpStatus&& s);
// void SetHeaders(BHttpHeaders&& h);
// void SetBody();
ssize_t WriteToBody(const void* buffer, ssize_t size);
};
inline
HttpResultPrivate::HttpResultPrivate(int32 identifier)
: id(identifier)
{
std::string name = "httpresult:" + std::to_string(identifier);
data_wait = create_sem(1, name.c_str());
if (data_wait < B_OK)
throw BRuntimeError(__PRETTY_FUNCTION__, "Cannot create internal sem for httpresult");
}
inline int32
HttpResultPrivate::GetStatusAtomic()
{
return atomic_get(&requestStatus);
}
inline bool
HttpResultPrivate::CanCancel()
{
return atomic_get(&canCancel) == 1;
}
inline void
HttpResultPrivate::SetCancel()
{
atomic_set(&canCancel, 1);
}
inline void
HttpResultPrivate::SetError(std::exception_ptr e)
{
error = e;
atomic_set(&requestStatus, kError);
release_sem(data_wait);
}
inline void
HttpResultPrivate::SetStatus(BHttpStatus&& s)
{
status = std::move(s);
atomic_set(&requestStatus, kStatusReady);
release_sem(data_wait);
}
//inline void
//HttpResultPrivate::SetHeaders(BHttpHeaders&& h)
//{
// headers = std::move(h);
// atomic_set(&requestStatus, kHeadersReady);
// release_sem(data_wait);
//}
//inline void
//HttpResultPrivate::SetBody()
//{
// body = BHttpBody{std::move(owned_body), std::move(body_text)};
// atomic_set(&requestStatus, kBodyReady);
// release_sem(data_wait);
//}
inline ssize_t
HttpResultPrivate::WriteToBody(const void* buffer, ssize_t size)
{
// TODO: when the support for a shared BMemoryRingIO is here, choose
// between one or the other depending on which one is available.
if (owned_body == nullptr) {
body_text.append(static_cast<const char*>(buffer), size);
return size;
}
return owned_body->Write(buffer, size);
}
} // namespace Network
} // namespace BPrivate
#endif // _HTTP_RESULT_PRIVATE_H_
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Haiku Inc. All rights reserved.
* Copyright 2022 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
+1
View File
@@ -18,6 +18,7 @@ for architectureObject in [ MultiArchSubDirSetup ] {
ErrorsExt.cpp
HttpFields.cpp
HttpRequest.cpp
HttpResult.cpp
HttpSession.cpp
NetServicesMisc.cpp
;