NetServices: Add/rework the MaxRedirections, Timeout and StopOnError options

Change-Id: I4e59b51c16c6777941dc92ce02505386257dad03
This commit is contained in:
Niels Sascha Reedijk 2022-06-03 09:40:42 +01:00
parent f9d9d20245
commit 2f3b9b18ac
6 changed files with 164 additions and 69 deletions

View File

@ -274,40 +274,6 @@ 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
*/
/*!
\struct BHttpAuthentication
\ingroup netservices
@ -370,10 +336,22 @@ namespace Network {
<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>
<td> \ref MaxRedirections() </td>
<td> \ref SetMaxRedirections() </td>
<td> How many redirections should be followed. Set to 0 to disable. </td>
<td> Defaults to 8 redirections per request </td>
</tr>
<tr>
<td> \ref StopOnError() </td>
<td> \ref SetStopOnError() </td>
<td> Stop parsing the server response when there is a client or server error. </td>
<td> Defaults to \a false </td>
</tr>
<tr>
<td> \ref Timeout() </td>
<td> \ref SetTimeout() </td>
<td> The timeout determines how long is waited for the server to respond </td>
<td> \c B_INFINITE_TIMEOUT </td>
</tr>
</table>
@ -540,10 +518,30 @@ namespace Network {
/*!
\fn const BHttpRedirectOptions& BHttpRequest::Redirect() const noexcept
\fn uint8 BHttpRequest::MaxRedirections() const noexcept
\brief Get the current redirection options for this request.
\see \ref BHttpRequest::SetRedirect() for details on the options.
\see \ref BHttpRequest::SetMaxRedirections() for details on the options.
\since Haiku R1
*/
/*!
\fn bool BHttpRequest::StopOnError() const noexcept
\brief Is the request set to parse the full response on error.
\retval true When encountering a HTTP status of the client error class (4xx) or server error
class (5xx), then the response will not be parsed.
\retval false The full response will be parsed, even with an error status code.
\since Haiku R1
*/
/*!
\fn bigtime_t BHttpRequest::Timeout() const noexcept
\brief Get the current timeout for the server to respond.
\since Haiku R1
*/
@ -623,22 +621,57 @@ namespace Network {
/*!
\fn void BHttpRequest::SetRedirect(const BHttpRedirectOptions &redirectOptions)
\fn void BHttpRequest::SetMaxRedirections(uint8 maxRedirections)
\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.
redirections. You can set 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.
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
If redirects are set to 0, 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.
\param maxRedirections The number of redirections to follow. Set to 0 to disable.
\exception std::bad_alloc This exception may be raised if it is impossible to allocate memory.
\since Haiku R1
*/
/*!
\fn void BHttpRequest::SetStopOnError(bool stopOnError)
\brief Set whether the entire response will be parsed on a client or server error.
When the server encounters an error processing a request, it may respond with an error code.
Error responses can be either an error with the request, like the 404 Not Found error, or
errors on the server side, like a 500 Internal Server Error. Error responses may still have
header fields and bodies.
If your application is not interested in the rest of the response in case a client error or
a server error occurs, you can set this option to stop parsing. This will allow you to use the
\ref BHttpResult object as normal, but the response fields will be empty, and there will be no
body data.
\param stopOnError Set to \c true to stop parsing the HTTP response when a client error or
server error occurs.
\exception std::bad_alloc This exception may be raised if it is impossible to allocate memory.
\since Haiku R1
*/
/*!
\fn void BHttpRequest::SetTimeout(bigtime_t timeout)
\brief Set the maximum time waiting for the server to respond.
If the request times out, then the response will hold the \ref BNetworkRequestError of the
\c NetworkError type. By default, the request does not time out.
\param timeout The timeout in milliseconds.
\exception std::bad_alloc This exception may be raised if it is impossible to allocate memory.

View File

@ -74,12 +74,6 @@ private:
};
struct BHttpRedirectOptions {
bool followRedirect = true;
uint8 maxRedirections = 8;
};
struct BHttpAuthentication {
BString username;
BString password;
@ -103,15 +97,19 @@ public:
bool IsEmpty() const noexcept;
const BHttpAuthentication* Authentication() const noexcept;
const BHttpFields& Fields() const noexcept;
uint8 MaxRedirections() const noexcept;
const BHttpMethod& Method() const noexcept;
const BHttpRedirectOptions& Redirect() const noexcept;
bool StopOnError() const noexcept;
bigtime_t Timeout() const noexcept;
const BUrl& Url() const noexcept;
// Named Setters
void SetAuthentication(const BHttpAuthentication& authentication);
void SetFields(const BHttpFields& fields);
void SetMaxRedirections(uint8 maxRedirections);
void SetMethod(const BHttpMethod& method);
void SetRedirect(const BHttpRedirectOptions& redirectOptions);
void SetStopOnError(bool stopOnError);
void SetTimeout(bigtime_t timeout);
void SetUrl(const BUrl& url);
// Serialization

View File

@ -156,15 +156,16 @@ BHttpMethod::Method() const noexcept
// #pragma mark -- BHttpRequest::Data
static const BUrl kDefaultUrl = BUrl();
static const BHttpMethod kDefaultMethod = BHttpMethod::Get;
static const BHttpRedirectOptions kDefaultRedirectOptions = BHttpRedirectOptions();
static const BHttpFields kDefaultOptionalFields = BHttpFields();
struct BHttpRequest::Data {
BUrl url = kDefaultUrl;
BHttpMethod method = kDefaultMethod;
BHttpRedirectOptions redirectOptions;
uint8 maxRedirections = 8;
BHttpFields optionalFields;
std::optional<BHttpAuthentication> authentication;
bool stopOnError = false;
bigtime_t timeout = B_INFINITE_TIMEOUT;
};
@ -236,6 +237,15 @@ BHttpRequest::Fields() const noexcept
}
uint8
BHttpRequest::MaxRedirections() const noexcept
{
if (!fData)
return 8;
return fData->maxRedirections;
}
const BHttpMethod&
BHttpRequest::Method() const noexcept
{
@ -245,12 +255,21 @@ BHttpRequest::Method() const noexcept
}
const BHttpRedirectOptions&
BHttpRequest::Redirect() const noexcept
bool
BHttpRequest::StopOnError() const noexcept
{
if (!fData)
return kDefaultRedirectOptions;
return fData->redirectOptions;
return false;
return fData->stopOnError;
}
bigtime_t
BHttpRequest::Timeout() const noexcept
{
if (!fData)
return B_INFINITE_TIMEOUT;
return fData->timeout;
}
@ -299,6 +318,15 @@ BHttpRequest::SetFields(const BHttpFields& fields)
}
void
BHttpRequest::SetMaxRedirections(uint8 maxRedirections)
{
if (!fData)
fData = std::make_unique<Data>();
fData->maxRedirections = maxRedirections;
}
void
BHttpRequest::SetMethod(const BHttpMethod& method)
{
@ -309,11 +337,20 @@ BHttpRequest::SetMethod(const BHttpMethod& method)
void
BHttpRequest::SetRedirect(const BHttpRedirectOptions& redirectOptions)
BHttpRequest::SetStopOnError(bool stopOnError)
{
if (!fData)
fData = std::make_unique<Data>();
fData->redirectOptions = redirectOptions;
fData->stopOnError = stopOnError;
}
void
BHttpRequest::SetTimeout(bigtime_t timeout)
{
if (!fData)
fData = std::make_unique<Data>();
fData->timeout = timeout;
}

View File

@ -594,10 +594,7 @@ 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;
fRemainingRedirects = fRequest.MaxRedirections();
// create shared data
fResult = std::make_shared<HttpResultPrivate>(identifier);
@ -662,6 +659,9 @@ BHttpSession::Request::OpenConnection()
fSocket = std::make_unique<BSocket>();
}
// Set timeout
fSocket->SetTimeout(fRequest.Timeout());
// Open connection
if (auto status = fSocket->Connect(fRemoteAddress); status != B_OK) {
// TODO: inform listeners that the connection failed
@ -781,6 +781,17 @@ BHttpSession::Request::ReceiveResult()
// TODO: inform listeners of receiving the status code
}
if ((status.StatusClass() == BHttpStatusClass::ClientError
|| status.StatusClass() == BHttpStatusClass::ServerError)
&& fRequest.StopOnError())
{
fRequestStatus = ContentReceived;
fResult->SetStatus(std::move(status));
fResult->SetFields(BHttpFields());
fResult->SetBody();
return true;
}
// TODO: handle the case where we have an error code and we want to stop on error
fRequestStatus = StatusReceived;

View File

@ -495,6 +495,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
testCaller->addThread("BasicAuthTest", &HttpIntegrationTest::BasicAuthTest);
testCaller->addThread("StopOnErrorTest", &HttpIntegrationTest::StopOnErrorTest);
suite.addTest(testCaller);
parent.addTest("HttpIntegrationTest", &suite);
@ -516,6 +517,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
testCaller->addThread("BasicAuthTest", &HttpIntegrationTest::BasicAuthTest);
testCaller->addThread("StopOnErrorTest", &HttpIntegrationTest::StopOnErrorTest);
suite.addTest(testCaller);
parent.addTest("HttpsIntegrationTest", &suite);
@ -699,3 +701,16 @@ HttpIntegrationTest::BasicAuthTest()
result = fSession.Execute(std::move(request));
CPPUNIT_ASSERT(result.Status().code == 401);
}
void
HttpIntegrationTest::StopOnErrorTest()
{
// Test the Stop on Error functionality
auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/400"));
request.SetStopOnError(true);
auto result = fSession.Execute(std::move(request));
CPPUNIT_ASSERT(result.Status().code == 400);
CPPUNIT_ASSERT(result.Fields().CountFields() == 0);
CPPUNIT_ASSERT(result.Body().text.Length() == 0);
}

View File

@ -44,6 +44,7 @@ public:
void NoContentTest();
void AutoRedirectTest();
void BasicAuthTest();
void StopOnErrorTest();
static void AddTests(BTestSuite& suite);