NetServices: Implement BHttpStatusCode, BHttpStatusClass and Redirects
The user of the API can set whether redirects should be followed, and if so, how many. This is part of the BHttpRequest API. The BHttpSession then follows those instructions, and executes the maximum number of redirects the user would like to follow. As part of this commit, the BHttpStatusClass and BHttpStatusCodes helper enums have been added, to give a friendlier access to HTTP status codes and status classes. Change-Id: Ic8c9e3fda158e2cce549c8f1d360951f7ac83311
This commit is contained in:
parent
59c359e5a9
commit
13bfff7be3
@ -227,6 +227,19 @@ namespace Network {
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\fn bool BHttpMethod::operator!=(const Verb &other) const noexcept
|
||||
\brief Comparison operator.
|
||||
|
||||
\param other The verb to compare to.
|
||||
|
||||
\retval true This method is different from \a other.
|
||||
\retval false This method is equal to \a other.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\fn const std::string_view BHttpMethod::Method() const noexcept
|
||||
\brief Get a string representation of the method.
|
||||
@ -249,7 +262,7 @@ namespace Network {
|
||||
|
||||
|
||||
/*!
|
||||
\fn BHttpMethod& BPrivate::Network::BHttpMethod::operator=(const BHttpMethod &other)
|
||||
\fn BHttpMethod& BHttpMethod::operator=(const BHttpMethod &other)
|
||||
\brief Copy assignment.
|
||||
|
||||
Copy data from an \a other object.
|
||||
@ -261,6 +274,40 @@ namespace Network {
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\struct BHttpRedirectOptions
|
||||
\ingroup netservices
|
||||
\brief Describe redirection options for a \ref BHttpRequest.
|
||||
|
||||
\see These options are used by \ref BHttpRequest::Redirect() and
|
||||
\ref BHttpRequest::SetRedirect()
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var bool BHttpRedirectOptions::followRedirect
|
||||
\brief Describe whether the request should follow redirects.
|
||||
|
||||
\see These options are used by \ref BHttpRequest::Redirect() and
|
||||
\ref BHttpRequest::SetRedirect()
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var uint8 BHttpRedirectOptions::maxRedirections
|
||||
\brief The maximum number of redirects that should be followed.
|
||||
|
||||
\see These options are used by \ref BHttpRequest::Redirect() and
|
||||
\ref BHttpRequest::SetRedirect()
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\class BHttpRequest
|
||||
\ingroup netservices
|
||||
@ -288,6 +335,12 @@ namespace Network {
|
||||
<td> The HTTP method for the request </td>
|
||||
<td> Defaults to \ref BHttpMethod::Get </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> \ref Redirect() </td>
|
||||
<td> \ref SetRedirect() </td>
|
||||
<td> Whether redirects should be followed, and if so, how many </td>
|
||||
<td> By default redirects are followed, up to 8 redirects for one request </td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
\since Haiku R1
|
||||
@ -430,6 +483,16 @@ namespace Network {
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\fn const BHttpRedirectOptions& BHttpRequest::Redirect() const noexcept
|
||||
\brief Get the current redirection options for this request.
|
||||
|
||||
\see \ref BHttpRequest::SetRedirect() for details on the options.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\fn const BUrl& BHttpRequest::Url() const noexcept
|
||||
\brief Get the current Url for the request.
|
||||
@ -468,6 +531,30 @@ namespace Network {
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\fn void BHttpRequest::SetRedirect(const BHttpRedirectOptions &redirectOptions)
|
||||
\brief Set the redirection options for this request.
|
||||
|
||||
The HTTP protocol allows the server to redirect requests if the resources have moved to a new
|
||||
location. For your convenience, you can instruct the network services kit to follow these
|
||||
redirections.
|
||||
|
||||
The \ref BHttpRedirectOptions allows you to set what the redirection policy should be. You can
|
||||
set whether redirects should be followed at all, and if so, how many redirects should be
|
||||
followed. The maximum value is that of an unsigned 8 bit int, so maximum is 256 redirects. This
|
||||
prevents the request from staying stuck in a redirection loop.
|
||||
|
||||
If redirects are disabled, or the maximum number of redirects have been processed, then the
|
||||
response will be set to the actual (last) received redirection response.
|
||||
|
||||
\param redirectOptions The options for redirections.
|
||||
|
||||
\exception std::bad_alloc This exception may be raised if it is impossible to allocate memory.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\fn void BHttpRequest::SetUrl(const BUrl &url)
|
||||
\brief Set the \a url for this request.
|
||||
|
@ -28,6 +28,18 @@ namespace BPrivate {
|
||||
namespace Network {
|
||||
|
||||
|
||||
/*!
|
||||
\enum BHttpStatusClass
|
||||
\brief Category of the HTTP response status code.
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\enum BHttpStatusCode
|
||||
\brief Enumeration of standardized HTTP status codes.
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\struct BHttpStatus
|
||||
\ingroup netservices
|
||||
@ -74,6 +86,28 @@ namespace Network {
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\fn BHttpStatusClass BHttpStatus::StatusClass() const noexcept
|
||||
\brief Map the \ref code value to a \ref BHttpStatusClass value
|
||||
|
||||
\return One of the valid values of \ref BHttpStatusClass, or \c BHttpStatusClass::Invalid if
|
||||
the return code cannot be maped.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\fn BHttpStatusCode BHttpStatus::StatusCode() const noexcept
|
||||
\brief Map the \ref code value to a \ref BHttpStatusCode value
|
||||
|
||||
\return One of the valid values of \ref BHttpStatusCode, or \c BHttpStatusCode::Unknown if
|
||||
the return code cannot be maped.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\struct BHttpBody
|
||||
\ingroup netservices
|
||||
|
@ -61,6 +61,7 @@ public:
|
||||
|
||||
// Comparison
|
||||
bool operator==(const Verb& other) const noexcept;
|
||||
bool operator!=(const Verb& other) const noexcept;
|
||||
|
||||
// Get the method as a string
|
||||
const std::string_view Method() const noexcept;
|
||||
@ -70,35 +71,44 @@ private:
|
||||
};
|
||||
|
||||
|
||||
struct BHttpRedirectOptions {
|
||||
bool followRedirect = true;
|
||||
uint8 maxRedirections = 8;
|
||||
};
|
||||
|
||||
|
||||
class BHttpRequest {
|
||||
public:
|
||||
// Constructors and Destructor
|
||||
BHttpRequest();
|
||||
BHttpRequest(const BUrl& url);
|
||||
BHttpRequest(const BHttpRequest& other) = delete;
|
||||
BHttpRequest(BHttpRequest&& other) noexcept;
|
||||
~BHttpRequest();
|
||||
BHttpRequest();
|
||||
BHttpRequest(const BUrl& url);
|
||||
BHttpRequest(const BHttpRequest& other) = delete;
|
||||
BHttpRequest(BHttpRequest&& other) noexcept;
|
||||
~BHttpRequest();
|
||||
|
||||
// Assignment operators
|
||||
BHttpRequest& operator=(const BHttpRequest& other) = delete;
|
||||
BHttpRequest& operator=(BHttpRequest&&) noexcept;
|
||||
BHttpRequest& operator=(const BHttpRequest& other) = delete;
|
||||
BHttpRequest& operator=(BHttpRequest&&) noexcept;
|
||||
|
||||
// Access
|
||||
bool IsEmpty() const noexcept;
|
||||
const BHttpMethod& Method() const noexcept;
|
||||
const BUrl& Url() const noexcept;
|
||||
bool IsEmpty() const noexcept;
|
||||
const BHttpMethod& Method() const noexcept;
|
||||
const BHttpRedirectOptions& Redirect() const noexcept;
|
||||
const BUrl& Url() const noexcept;
|
||||
|
||||
// Named Setters
|
||||
void SetMethod(const BHttpMethod& method);
|
||||
void SetUrl(const BUrl& url);
|
||||
void SetMethod(const BHttpMethod& method);
|
||||
void SetRedirect(const BHttpRedirectOptions& redirectOptions);
|
||||
void SetUrl(const BUrl& url);
|
||||
|
||||
// Serialization
|
||||
ssize_t SerializeHeaderTo(BDataIO* target) const;
|
||||
BString HeaderToString() const;
|
||||
ssize_t SerializeHeaderTo(BDataIO* target) const;
|
||||
BString HeaderToString() const;
|
||||
|
||||
private:
|
||||
friend class BHttpSession;
|
||||
struct Data;
|
||||
std::unique_ptr<Data> fData;
|
||||
std::unique_ptr<Data> fData;
|
||||
};
|
||||
|
||||
|
||||
|
@ -28,10 +28,79 @@ struct BHttpBody
|
||||
};
|
||||
|
||||
|
||||
enum class BHttpStatusClass : int16 {
|
||||
Invalid = 000,
|
||||
Informational = 100,
|
||||
Success = 200,
|
||||
Redirection = 300,
|
||||
ClientError = 400,
|
||||
ServerError = 500
|
||||
};
|
||||
|
||||
|
||||
enum class BHttpStatusCode : int16 {
|
||||
Unknown = 0,
|
||||
|
||||
// Informational status codes
|
||||
Continue = 100,
|
||||
SwitchingProtocols,
|
||||
|
||||
// Success status codes
|
||||
Ok = 200,
|
||||
Created,
|
||||
Accepted,
|
||||
NonAuthoritativeInformation,
|
||||
NoContent,
|
||||
ResetContent,
|
||||
PartialContent,
|
||||
|
||||
// Redirection status codes
|
||||
MultipleChoice = 300,
|
||||
MovedPermanently,
|
||||
Found,
|
||||
SeeOther,
|
||||
NotModified,
|
||||
UseProxy,
|
||||
TemporaryRedirect = 307,
|
||||
PermanentRedirect,
|
||||
|
||||
// Client error status codes
|
||||
BadRequest = 400,
|
||||
Unauthorized,
|
||||
PaymentRequired,
|
||||
Forbidden,
|
||||
NotFound,
|
||||
MethodNotAllowed,
|
||||
NotAcceptable,
|
||||
ProxyAuthenticationRequired,
|
||||
RequestTimeout,
|
||||
Conflict,
|
||||
Gone,
|
||||
LengthRequired,
|
||||
PreconditionFailed,
|
||||
RequestEntityTooLarge,
|
||||
RequestUriTooLarge,
|
||||
UnsupportedMediaType,
|
||||
RequestedRangeNotSatisfiable,
|
||||
ExpectationFailed,
|
||||
|
||||
// Server error status codes
|
||||
InternalServerError = 500,
|
||||
NotImplemented,
|
||||
BadGateway,
|
||||
ServiceUnavailable,
|
||||
GatewayTimeout,
|
||||
};
|
||||
|
||||
|
||||
struct BHttpStatus
|
||||
{
|
||||
int16 code = 0;
|
||||
BString text;
|
||||
int16 code = 0;
|
||||
BString text;
|
||||
|
||||
// Helpers
|
||||
BHttpStatusClass StatusClass() const noexcept;
|
||||
BHttpStatusCode StatusCode() const noexcept;
|
||||
};
|
||||
|
||||
|
||||
|
@ -39,6 +39,7 @@ public:
|
||||
BMessenger observer = BMessenger());
|
||||
|
||||
private:
|
||||
struct Redirect;
|
||||
class Request;
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> fImpl;
|
||||
|
@ -113,6 +113,13 @@ BHttpMethod::operator==(const BHttpMethod::Verb& other) const noexcept
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
BHttpMethod::operator!=(const BHttpMethod::Verb& other) const noexcept
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
|
||||
const std::string_view
|
||||
BHttpMethod::Method() const noexcept
|
||||
{
|
||||
@ -149,11 +156,13 @@ BHttpMethod::Method() const noexcept
|
||||
// #pragma mark -- BHttpRequest::Data
|
||||
static const BUrl kDefaultUrl = BUrl();
|
||||
static const BHttpMethod kDefaultMethod = BHttpMethod::Get;
|
||||
static const BHttpRedirectOptions kDefaultRedirectOptions = BHttpRedirectOptions();
|
||||
|
||||
|
||||
struct BHttpRequest::Data {
|
||||
BUrl url = kDefaultUrl;
|
||||
BHttpMethod method = kDefaultMethod;
|
||||
BUrl url = kDefaultUrl;
|
||||
BHttpMethod method = kDefaultMethod;
|
||||
BHttpRedirectOptions redirectOptions;
|
||||
};
|
||||
|
||||
|
||||
@ -200,6 +209,15 @@ BHttpRequest::Method() const noexcept
|
||||
}
|
||||
|
||||
|
||||
const BHttpRedirectOptions&
|
||||
BHttpRequest::Redirect() const noexcept
|
||||
{
|
||||
if (!fData)
|
||||
return kDefaultRedirectOptions;
|
||||
return fData->redirectOptions;
|
||||
}
|
||||
|
||||
|
||||
const BUrl&
|
||||
BHttpRequest::Url() const noexcept
|
||||
{
|
||||
@ -218,6 +236,15 @@ BHttpRequest::SetMethod(const BHttpMethod& method)
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BHttpRequest::SetRedirect(const BHttpRedirectOptions& redirectOptions)
|
||||
{
|
||||
if (!fData)
|
||||
fData = std::make_unique<Data>();
|
||||
fData->redirectOptions = redirectOptions;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BHttpRequest::SetUrl(const BUrl& url)
|
||||
{
|
||||
|
@ -16,6 +16,91 @@
|
||||
using namespace BPrivate::Network;
|
||||
|
||||
|
||||
// #pragma mark -- BHttpStatus
|
||||
|
||||
|
||||
BHttpStatusClass
|
||||
BHttpStatus::StatusClass() const noexcept
|
||||
{
|
||||
switch (code / 100) {
|
||||
case 1: return BHttpStatusClass::Informational;
|
||||
case 2: return BHttpStatusClass::Success;
|
||||
case 3: return BHttpStatusClass::Redirection;
|
||||
case 4: return BHttpStatusClass::ClientError;
|
||||
case 5: return BHttpStatusClass::ServerError;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return BHttpStatusClass::Invalid;
|
||||
}
|
||||
|
||||
|
||||
BHttpStatusCode
|
||||
BHttpStatus::StatusCode() const noexcept
|
||||
{
|
||||
switch (static_cast<BHttpStatusCode>(code)) {
|
||||
// 1xx
|
||||
case BHttpStatusCode::Continue: [[fallthrough]];
|
||||
case BHttpStatusCode::SwitchingProtocols: [[fallthrough]];
|
||||
|
||||
// 2xx
|
||||
case BHttpStatusCode::Ok: [[fallthrough]];
|
||||
case BHttpStatusCode::Created: [[fallthrough]];
|
||||
case BHttpStatusCode::Accepted: [[fallthrough]];
|
||||
case BHttpStatusCode::NonAuthoritativeInformation: [[fallthrough]];
|
||||
case BHttpStatusCode::NoContent: [[fallthrough]];
|
||||
case BHttpStatusCode::ResetContent: [[fallthrough]];
|
||||
case BHttpStatusCode::PartialContent: [[fallthrough]];
|
||||
|
||||
// 3xx
|
||||
case BHttpStatusCode::MultipleChoice: [[fallthrough]];
|
||||
case BHttpStatusCode::MovedPermanently: [[fallthrough]];
|
||||
case BHttpStatusCode::Found: [[fallthrough]];
|
||||
case BHttpStatusCode::SeeOther: [[fallthrough]];
|
||||
case BHttpStatusCode::NotModified: [[fallthrough]];
|
||||
case BHttpStatusCode::UseProxy: [[fallthrough]];
|
||||
case BHttpStatusCode::TemporaryRedirect: [[fallthrough]];
|
||||
case BHttpStatusCode::PermanentRedirect: [[fallthrough]];
|
||||
|
||||
// 4xx
|
||||
case BHttpStatusCode::BadRequest: [[fallthrough]];
|
||||
case BHttpStatusCode::Unauthorized: [[fallthrough]];
|
||||
case BHttpStatusCode::PaymentRequired: [[fallthrough]];
|
||||
case BHttpStatusCode::Forbidden: [[fallthrough]];
|
||||
case BHttpStatusCode::NotFound: [[fallthrough]];
|
||||
case BHttpStatusCode::MethodNotAllowed: [[fallthrough]];
|
||||
case BHttpStatusCode::NotAcceptable: [[fallthrough]];
|
||||
case BHttpStatusCode::ProxyAuthenticationRequired: [[fallthrough]];
|
||||
case BHttpStatusCode::RequestTimeout: [[fallthrough]];
|
||||
case BHttpStatusCode::Conflict: [[fallthrough]];
|
||||
case BHttpStatusCode::Gone: [[fallthrough]];
|
||||
case BHttpStatusCode::LengthRequired: [[fallthrough]];
|
||||
case BHttpStatusCode::PreconditionFailed: [[fallthrough]];
|
||||
case BHttpStatusCode::RequestEntityTooLarge: [[fallthrough]];
|
||||
case BHttpStatusCode::RequestUriTooLarge: [[fallthrough]];
|
||||
case BHttpStatusCode::UnsupportedMediaType: [[fallthrough]];
|
||||
case BHttpStatusCode::RequestedRangeNotSatisfiable: [[fallthrough]];
|
||||
case BHttpStatusCode::ExpectationFailed: [[fallthrough]];
|
||||
|
||||
// 5xx
|
||||
case BHttpStatusCode::InternalServerError: [[fallthrough]];
|
||||
case BHttpStatusCode::NotImplemented: [[fallthrough]];
|
||||
case BHttpStatusCode::BadGateway: [[fallthrough]];
|
||||
case BHttpStatusCode::ServiceUnavailable: [[fallthrough]];
|
||||
case BHttpStatusCode::GatewayTimeout:
|
||||
return static_cast<BHttpStatusCode>(code);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return BHttpStatusCode::Unknown;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -- BHttpResult
|
||||
|
||||
|
||||
/*private*/
|
||||
BHttpResult::BHttpResult(std::shared_ptr<HttpResultPrivate> data)
|
||||
: fData(data)
|
||||
|
@ -71,6 +71,8 @@ public:
|
||||
std::unique_ptr<BDataIO> target,
|
||||
BMessenger observer);
|
||||
|
||||
Request(Request& original, const Redirect& redirect);
|
||||
|
||||
// States
|
||||
enum RequestState {
|
||||
InitialState,
|
||||
@ -99,7 +101,7 @@ public:
|
||||
int Socket() const noexcept { return fSocket->Socket(); }
|
||||
int32 Id() const noexcept { return fResult->id; }
|
||||
bool CanCancel() const noexcept { return fResult->CanCancel(); }
|
||||
|
||||
|
||||
private:
|
||||
std::optional<BString> _GetLine(std::vector<std::byte>::const_iterator& offset);
|
||||
BHttpStatus _ParseStatus(BString&& statusLine);
|
||||
@ -131,12 +133,13 @@ private:
|
||||
BHttpFields fFields;
|
||||
bool fNoContent = false;
|
||||
|
||||
// Redirection
|
||||
BHttpStatus fRedirectStatus;
|
||||
int8 fRemainingRedirects;
|
||||
|
||||
// Optional decompression
|
||||
std::unique_ptr<BMallocIO> fDecompressorStorage = nullptr;
|
||||
std::unique_ptr<BDataIO> fDecompressingStream = nullptr;
|
||||
|
||||
// TODO: reset method to reset Connection and Receive State when redirected
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -176,6 +179,12 @@ private:
|
||||
};
|
||||
|
||||
|
||||
struct BHttpSession::Redirect {
|
||||
BUrl url;
|
||||
bool redirectToGet;
|
||||
};
|
||||
|
||||
|
||||
// #pragma mark -- BHttpSession::Impl
|
||||
|
||||
|
||||
@ -297,6 +306,7 @@ BHttpSession::Impl::ControlThreadFunc(void* arg)
|
||||
wait_for_thread(impl->fDataThread, &threadResult);
|
||||
// Cancel all requests
|
||||
for (auto& request: impl->fControlQueue) {
|
||||
std::cout << "ControlThreadFunc()[" << request.Id() << "] canceling request because the session is quitting" << std::endl;
|
||||
try {
|
||||
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::Canceled);
|
||||
} catch (...) {
|
||||
@ -420,22 +430,30 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
|
||||
auto finished = false;
|
||||
auto success = false;
|
||||
try {
|
||||
finished = request.ReceiveResult();
|
||||
if (request.CanCancel())
|
||||
finished = true;
|
||||
else
|
||||
finished = request.ReceiveResult();
|
||||
success = true;
|
||||
} catch (const Redirect& r) {
|
||||
// Request is redirected, send back to the controlThread
|
||||
std::cout << "DataThreadFunc() [" << request.Id() << "] will be redirected to " << r.url.UrlString().String() << std::endl;
|
||||
|
||||
// Move existing request into a new request and hand over to the control queue
|
||||
auto lock = AutoLocker<BLocker>(data->fLock);
|
||||
data->fControlQueue.emplace_back(request, r);
|
||||
release_sem(data->fControlQueueSem);
|
||||
|
||||
finished = true;
|
||||
} catch (...) {
|
||||
request.SetError(std::current_exception());
|
||||
finished = true;
|
||||
}
|
||||
|
||||
if (request.CanCancel()) {
|
||||
// This could be done earlier, but this seems cleaner for the flow
|
||||
std::cout << "DataThreadFunc() [" << request.Id() << "] CanCancel() true" << std::endl;
|
||||
if (finished) {
|
||||
// Clean up finished requests; including redirected requests
|
||||
request.Disconnect();
|
||||
data->connectionMap.erase(item.object);
|
||||
resizeObjectList = true;
|
||||
} else if (finished) {
|
||||
request.Disconnect();
|
||||
/* TODO: implement this somewhere else
|
||||
/* TODO: implement this somewhere else; only notify if we are not redirecting
|
||||
if (request.observer.IsValid()) {
|
||||
BMessage msg(UrlEvent::RequestCompleted);
|
||||
msg.AddInt32(UrlEventData::Id, request.result->id);
|
||||
@ -518,6 +536,7 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
|
||||
// Cancel all requests
|
||||
for (auto it = data->connectionMap.begin(); it != data->connectionMap.end(); it++) {
|
||||
try {
|
||||
std::cout << "DataThreadFunc() [ " << it->second.Id() << "] canceling request because we are quitting" << std::endl;
|
||||
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::Canceled);
|
||||
} catch (...) {
|
||||
it->second.SetError(std::current_exception());
|
||||
@ -574,12 +593,35 @@ BHttpSession::Request::Request(BHttpRequest&& request, std::unique_ptr<BDataIO>
|
||||
{
|
||||
auto identifier = get_netservices_request_identifier();
|
||||
|
||||
// interpret the remaining redirects
|
||||
if (fRequest.Redirect().followRedirect)
|
||||
fRemainingRedirects = fRequest.Redirect().maxRedirections;
|
||||
else
|
||||
fRemainingRedirects = 0;
|
||||
|
||||
// create shared data
|
||||
fResult = std::make_shared<HttpResultPrivate>(identifier);
|
||||
fResult->ownedBody = std::move(target);
|
||||
}
|
||||
|
||||
|
||||
BHttpSession::Request::Request(Request& original, const BHttpSession::Redirect& redirect)
|
||||
: fRequest(std::move(original.fRequest)), fObserver(original.fObserver),
|
||||
fResult(original.fResult)
|
||||
{
|
||||
// update the original request with the new location
|
||||
fRequest.SetUrl(redirect.url);
|
||||
|
||||
if (redirect.redirectToGet
|
||||
&& (fRequest.Method() != BHttpMethod::Head && fRequest.Method() != BHttpMethod::Get)) {
|
||||
fRequest.SetMethod(BHttpMethod::Get);
|
||||
// TODO: clear Post fields/Update Data when that is supported.
|
||||
}
|
||||
|
||||
fRemainingRedirects = original.fRemainingRedirects--;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\brief Resolve the hostname for a request
|
||||
*/
|
||||
@ -727,17 +769,20 @@ BHttpSession::Request::ReceiveResult()
|
||||
if (status.code != 0) {
|
||||
// the status headers are now received, decide what to do next
|
||||
|
||||
// TODO: handle the case where we have a redirect code and we want to follow redirect
|
||||
// Handle redirects
|
||||
if (status.StatusClass() == BHttpStatusClass::Redirection && fRemainingRedirects > 0) {
|
||||
fRedirectStatus = std::move(status);
|
||||
} else {
|
||||
// Register NoContent before moving the status to the result
|
||||
if (status.StatusCode() == BHttpStatusCode::NoContent)
|
||||
fNoContent = true;
|
||||
|
||||
fResult->SetStatus(std::move(status));
|
||||
// TODO: inform listeners of receiving the status code
|
||||
}
|
||||
|
||||
// TODO: handle the case where we have an error code and we want to stop on error
|
||||
|
||||
if (status.code == 204)
|
||||
fNoContent = true;
|
||||
|
||||
fResult->SetStatus(std::move(status));
|
||||
|
||||
// TODO: inform listeners of receiving the status code
|
||||
|
||||
fRequestStatus = StatusReceived;
|
||||
} else {
|
||||
// We do not have enough data for the status line yet, continue receiving data.
|
||||
@ -766,7 +811,38 @@ BHttpSession::Request::ReceiveResult()
|
||||
|
||||
// The headers have been received, now set up the rest of the response handling
|
||||
|
||||
// TODO: handle redirect
|
||||
// Handle redirects
|
||||
if (fRedirectStatus.StatusClass() == BHttpStatusClass::Redirection) {
|
||||
auto redirectToGet = false;
|
||||
switch (fRedirectStatus.StatusCode()) {
|
||||
case BHttpStatusCode::Found:
|
||||
case BHttpStatusCode::SeeOther:
|
||||
// 302 and 303 redirections convert all requests to GET request, except for HEAD
|
||||
redirectToGet = true;
|
||||
[[fallthrough]];
|
||||
case BHttpStatusCode::MovedPermanently:
|
||||
case BHttpStatusCode::TemporaryRedirect:
|
||||
case BHttpStatusCode::PermanentRedirect:
|
||||
{
|
||||
std::cout << "ReceiveResult() [" << Id() << "] Handle redirect with status: " << fRedirectStatus.code << std::endl;
|
||||
auto locationField = fFields.FindField("Location");
|
||||
if (locationField == fFields.end()) {
|
||||
throw BNetworkRequestError(__PRETTY_FUNCTION__,
|
||||
BNetworkRequestError::ProtocolError);
|
||||
}
|
||||
auto locationString = BString((*locationField).Value().data(),
|
||||
(*locationField).Value().size());
|
||||
auto redirect = BHttpSession::Redirect{BUrl(fRequest.Url(), locationString), redirectToGet};
|
||||
if (!redirect.url.IsValid())
|
||||
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError);
|
||||
|
||||
throw redirect;
|
||||
}
|
||||
default:
|
||||
// ignore other status codes and continue regular processing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Parse received cookies
|
||||
|
||||
|
@ -416,6 +416,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
|
||||
testCaller->addThread("GetTest", &HttpIntegrationTest::GetTest);
|
||||
testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest);
|
||||
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
|
||||
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
|
||||
|
||||
suite.addTest(testCaller);
|
||||
parent.addTest("HttpIntegrationTest", &suite);
|
||||
@ -435,6 +436,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
|
||||
testCaller->addThread("GetTest", &HttpIntegrationTest::GetTest);
|
||||
testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest);
|
||||
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
|
||||
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
|
||||
|
||||
suite.addTest(testCaller);
|
||||
parent.addTest("HttpsIntegrationTest", &suite);
|
||||
@ -576,3 +578,28 @@ HttpIntegrationTest::NoContentTest()
|
||||
CPPUNIT_FAIL(e.DebugMessage().String());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
HttpIntegrationTest::AutoRedirectTest()
|
||||
{
|
||||
auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/302"));
|
||||
auto result = fSession.Execute(std::move(request));
|
||||
try {
|
||||
auto receivedFields = result.Fields();
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("Mismatch in number of headers",
|
||||
kExpectedGetFields.CountFields(), receivedFields.CountFields());
|
||||
for (auto& field: receivedFields) {
|
||||
auto expectedField = kExpectedGetFields.FindField(field.Name());
|
||||
if (expectedField == kExpectedGetFields.end())
|
||||
CPPUNIT_FAIL("Could not find expected field in response headers");
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(field.Value(), (*expectedField).Value());
|
||||
}
|
||||
auto receivedBody = result.Body().text;
|
||||
CPPUNIT_ASSERT_EQUAL(kExpectedGetBody, receivedBody.String());
|
||||
} catch (const BPrivate::Network::BError& e) {
|
||||
CPPUNIT_FAIL(e.DebugMessage().String());
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ public:
|
||||
void GetTest();
|
||||
void HeadTest();
|
||||
void NoContentTest();
|
||||
void AutoRedirectTest();
|
||||
|
||||
static void AddTests(BTestSuite& suite);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user