NetServices: use BBorrow<BDataIO> for custom body targets

Change-Id: Ib2d4b0ca3689338d906f943295278c086c6f2c83
This commit is contained in:
Niels Sascha Reedijk 2022-09-04 07:27:08 +01:00
parent 1e22817dfb
commit 27196c4068
8 changed files with 91 additions and 58 deletions

View File

@ -114,9 +114,8 @@ namespace Network {
\brief Represents a HTTP response body.
The HTTP response body is captured in this object. The body is either stored into a
\ref target, or into a \a text variable, depending on how you called the
\ref BHttpSession::Execute() method. If there is a \a target, the body will be empty,
and vice versa.
target, or into a \ref text variable, depending on how you called the
\ref BHttpSession::Execute() method.
You will usually get a reference to this object through the \ref BHttpResult::Body() method.
If you want to keep the contents of the body beyond the lifetime of the BHttpResult object,
@ -127,17 +126,13 @@ namespace Network {
/*!
\var std::unique_ptr<BDataIO> BHttpBody::target
\brief An owned pointer to where the body has been written.
\since Haiku R1
*/
/*!
\var BString BHttpBody::text
\var std::optional<BString> BHttpBody::text
\brief A string containing the body of the HTTP request.
The value of this class variable is set to \c std::nullopt if the target body was written to
a specified target. Otherwise, the response body is stored in this string. If the response
body was empty, then this will be an empty string.
\since Haiku R1
*/

View File

@ -165,14 +165,16 @@ namespace Network {
/*!
\fn BHttpResult BHttpSession::Execute(BHttpRequest &&request,
std::unique_ptr< BDataIO > target=nullptr, BMessenger observer=BMessenger())
BBorrow< BDataIO > target=nullptr, BMessenger observer=BMessenger())
\brief Schedule and execute a \a request.
\param request The (valid) request to move from.
\param target An optional data buffer to write the incoming body of the request to. This can be
\c nullptr if you want to use the default internal storage. If you provide a buffer, it
must be wrapped in a \c std::unique_ptr. This means that you transfer ownership to the
session. After the request is finished, you can regain ownership.
must be wrapped in a \ref BBorrow object. This means that you exclusively borrow the
target to this session object. After the request is finished, you can regain usage of the
object through the matching \ref BExclusiveBorrow object. Use the \ref BHttpResult::Body()
method to synchronize when the target is available again.
\param observer An optional observer that will receive the progress and status messages for
this request.

View File

@ -23,8 +23,7 @@ struct HttpResultPrivate;
struct BHttpBody
{
std::unique_ptr<BDataIO> target = nullptr;
BString text;
std::optional<BString> text;
};

View File

@ -8,6 +8,7 @@
#include <memory>
#include <ExclusiveBorrow.h>
#include <Messenger.h>
class BUrl;
@ -35,7 +36,7 @@ public:
// Requests
BHttpResult Execute(BHttpRequest&& request,
std::unique_ptr<BDataIO> target = nullptr,
BBorrow<BDataIO> target = nullptr,
BMessenger observer = BMessenger());
void Cancel(int32 identifier);
void Cancel(const BHttpResult& request);

View File

@ -15,6 +15,7 @@
#include <string>
#include <DataIO.h>
#include <ExclusiveBorrow.h>
#include <OS.h>
#include <String.h>
@ -45,10 +46,9 @@ struct HttpResultPrivate {
std::optional<BHttpBody> body;
std::optional<std::exception_ptr> error;
// Body storage
std::unique_ptr<BDataIO> ownedBody = nullptr;
// std::shared_ptr<BMemoryRingIO> shared_body = nullptr;
BString bodyText;
// Interim body storage (used while the request is running)
BString bodyString;
BBorrow<BDataIO> bodyTarget;
// Utility functions
HttpResultPrivate(int32 identifier);
@ -98,6 +98,9 @@ HttpResultPrivate::SetCancel()
inline void
HttpResultPrivate::SetError(std::exception_ptr e)
{
// Release any held body target borrow
bodyTarget.Return();
error = e;
atomic_set(&requestStatus, kError);
release_sem(data_wait);
@ -125,7 +128,12 @@ HttpResultPrivate::SetFields(BHttpFields&& f)
inline void
HttpResultPrivate::SetBody()
{
body = BHttpBody{std::move(ownedBody), std::move(bodyText)};
if (bodyTarget.HasValue()) {
body = BHttpBody{};
bodyTarget.Return();
} else
body = BHttpBody{std::move(bodyString)};
atomic_set(&requestStatus, kBodyReady);
release_sem(data_wait);
}
@ -136,14 +144,15 @@ HttpResultPrivate::WriteToBody(const void* buffer, size_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 (ownedBody == nullptr) {
bodyText.Append(static_cast<const char*>(buffer), size);
if (bodyTarget.HasValue()) {
auto result = bodyTarget->Write(buffer, size);
if (result < 0)
throw BSystemError("BDataIO::Write()", result);
return result;
} else {
bodyString.Append(reinterpret_cast<const char*>(buffer), size);
return size;
}
auto result = ownedBody->Write(buffer, size);
if (result < 0)
throw BSystemError("BDataIO::Write()", result);
return result;
}

View File

@ -61,7 +61,7 @@ struct CounterDeleter {
class BHttpSession::Request {
public:
Request(BHttpRequest&& request,
std::unique_ptr<BDataIO> target,
BBorrow<BDataIO> target,
BMessenger observer);
Request(Request& original, const Redirect& redirect);
@ -142,7 +142,7 @@ public:
~Impl() noexcept;
BHttpResult Execute(BHttpRequest&& request,
std::unique_ptr<BDataIO> target,
BBorrow<BDataIO> target,
BMessenger observer);
void Cancel(int32 identifier);
void SetMaxConnectionsPerHost(size_t maxConnections);
@ -232,7 +232,7 @@ BHttpSession::Impl::~Impl() noexcept
BHttpResult
BHttpSession::Impl::Execute(BHttpRequest&& request, std::unique_ptr<BDataIO> target,
BHttpSession::Impl::Execute(BHttpRequest&& request, BBorrow<BDataIO> target,
BMessenger observer)
{
auto wRequest = Request(std::move(request), std::move(target), observer);
@ -635,7 +635,7 @@ BHttpSession::operator=(const BHttpSession&) noexcept = default;
BHttpResult
BHttpSession::Execute(BHttpRequest&& request, std::unique_ptr<BDataIO> target, BMessenger observer)
BHttpSession::Execute(BHttpRequest&& request, BBorrow<BDataIO> target, BMessenger observer)
{
return fImpl->Execute(std::move(request), std::move(target), observer);
}
@ -670,8 +670,7 @@ BHttpSession::SetMaxHosts(size_t maxConnections)
// #pragma mark -- BHttpSession::Request (helpers)
BHttpSession::Request::Request(BHttpRequest&& request, std::unique_ptr<BDataIO> target,
BHttpSession::Request::Request(BHttpRequest&& request, BBorrow<BDataIO> target,
BMessenger observer)
: fRequest(std::move(request)), fObserver(observer)
{
@ -682,7 +681,10 @@ BHttpSession::Request::Request(BHttpRequest&& request, std::unique_ptr<BDataIO>
// create shared data
fResult = std::make_shared<HttpResultPrivate>(identifier);
fResult->ownedBody = std::move(target);
// check if there is a target
if (target.HasValue())
fResult->bodyTarget = std::move(target);
// inform the parser when we do a HEAD request, so not to expect content
if (fRequest.Method() == BHttpMethod::Head)

View File

@ -14,6 +14,7 @@
#include <tools/cppunit/ThreadedTestCaller.h>
#include <DateTime.h>
#include <ExclusiveBorrow.h>
#include <HttpFields.h>
#include <HttpRequest.h>
#include <HttpResult.h>
@ -23,6 +24,8 @@
#include <Url.h>
using BPrivate::BDateTime;
using BPrivate::Network::BBorrow;
using BPrivate::Network::BExclusiveBorrow;
using BPrivate::Network::BHttpFields;
using BPrivate::Network::BHttpMethod;
using BPrivate::Network::BHttpRequest;
@ -32,6 +35,7 @@ using BPrivate::Network::BHttpTime;
using BPrivate::Network::BHttpTimeFormat;
using BPrivate::Network::BNetworkRequestError;
using BPrivate::Network::format_http_time;
using BPrivate::Network::make_exclusive_borrow;
using BPrivate::Network::parse_http_time;
using namespace std::literals;
@ -453,6 +457,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
testCaller->addThread("HostAndNetworkFailTest",
&HttpIntegrationTest::HostAndNetworkFailTest);
testCaller->addThread("GetTest", &HttpIntegrationTest::GetTest);
testCaller->addThread("GetWithBufferTest", &HttpIntegrationTest::GetWithBufferTest);
testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest);
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
@ -477,6 +482,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
testCaller->addThread("HostAndNetworkFailTest",
&HttpIntegrationTest::HostAndNetworkFailTest);
testCaller->addThread("GetTest", &HttpIntegrationTest::GetTest);
testCaller->addThread("GetWithBufferTest", &HttpIntegrationTest::GetWithBufferTest);
testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest);
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
@ -559,7 +565,25 @@ HttpIntegrationTest::GetTest()
CPPUNIT_ASSERT_EQUAL(field.Value(), (*expectedField).Value());
}
auto receivedBody = result.Body().text;
CPPUNIT_ASSERT_EQUAL(kExpectedGetBody, receivedBody.String());
CPPUNIT_ASSERT(receivedBody.has_value());
CPPUNIT_ASSERT_EQUAL(kExpectedGetBody, receivedBody.value().String());
} catch (const BPrivate::Network::BError& e) {
CPPUNIT_FAIL(e.DebugMessage().String());
}
}
void
HttpIntegrationTest::GetWithBufferTest()
{
auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/"));
auto body = make_exclusive_borrow<BMallocIO>();
auto result = fSession.Execute(std::move(request), BBorrow<BDataIO>(body), fLoggerMessenger);
try {
result.Body();
auto bodyString = std::string(reinterpret_cast<const char*>(body->Buffer()),
body->BufferLength());
CPPUNIT_ASSERT_EQUAL(kExpectedGetBody, bodyString);
} catch (const BPrivate::Network::BError& e) {
CPPUNIT_FAIL(e.DebugMessage().String());
}
@ -584,8 +608,7 @@ HttpIntegrationTest::HeadTest()
CPPUNIT_ASSERT_EQUAL(field.Value(), (*expectedField).Value());
}
auto receivedBody = result.Body().text;
CPPUNIT_ASSERT_EQUAL(receivedBody.Length(), 0);
CPPUNIT_ASSERT(result.Body().text->Length() == 0);
} catch (const BPrivate::Network::BError& e) {
CPPUNIT_FAIL(e.DebugMessage().String());
}
@ -618,8 +641,7 @@ HttpIntegrationTest::NoContentTest()
CPPUNIT_ASSERT_EQUAL(field.Value(), (*expectedField).Value());
}
auto receivedBody = result.Body().text;
CPPUNIT_ASSERT_EQUAL(receivedBody.Length(), 0);
CPPUNIT_ASSERT(result.Body().text->Length() == 0);
} catch (const BPrivate::Network::BError& e) {
CPPUNIT_FAIL(e.DebugMessage().String());
}
@ -644,7 +666,8 @@ HttpIntegrationTest::AutoRedirectTest()
CPPUNIT_ASSERT_EQUAL(field.Value(), (*expectedField).Value());
}
auto receivedBody = result.Body().text;
CPPUNIT_ASSERT_EQUAL(kExpectedGetBody, receivedBody.String());
CPPUNIT_ASSERT(receivedBody.has_value());
CPPUNIT_ASSERT_EQUAL(kExpectedGetBody, receivedBody.value().String());
} catch (const BPrivate::Network::BError& e) {
CPPUNIT_FAIL(e.DebugMessage().String());
}
@ -681,7 +704,7 @@ HttpIntegrationTest::StopOnErrorTest()
auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger);
CPPUNIT_ASSERT(result.Status().code == 400);
CPPUNIT_ASSERT(result.Fields().CountFields() == 0);
CPPUNIT_ASSERT(result.Body().text.Length() == 0);
CPPUNIT_ASSERT(result.Body().text->Length() == 0);
}
@ -764,8 +787,9 @@ HttpIntegrationTest::PostTest()
auto result = fSession.Execute(std::move(request), nullptr, BMessenger(observer));
CPPUNIT_ASSERT_EQUAL(kExpectedPostBody.Length(), result.Body().text.Length());
CPPUNIT_ASSERT(result.Body().text == kExpectedPostBody);
CPPUNIT_ASSERT(result.Body().text.has_value());
CPPUNIT_ASSERT_EQUAL(kExpectedPostBody.Length(), result.Body().text.value().Length());
CPPUNIT_ASSERT(result.Body().text.value() == kExpectedPostBody);
usleep(2000); // give some time to catch up on receiving all messages

View File

@ -33,22 +33,23 @@ public:
class HttpIntegrationTest : public BThreadedTestCase
{
public:
HttpIntegrationTest(TestServerMode mode);
HttpIntegrationTest(TestServerMode mode);
virtual void setUp() override;
virtual void tearDown() override;
virtual void setUp() override;
virtual void tearDown() override;
void HostAndNetworkFailTest();
void GetTest();
void HeadTest();
void NoContentTest();
void AutoRedirectTest();
void BasicAuthTest();
void StopOnErrorTest();
void RequestCancelTest();
void PostTest();
void HostAndNetworkFailTest();
void GetTest();
void GetWithBufferTest();
void HeadTest();
void NoContentTest();
void AutoRedirectTest();
void BasicAuthTest();
void StopOnErrorTest();
void RequestCancelTest();
void PostTest();
static void AddTests(BTestSuite& suite);
static void AddTests(BTestSuite& suite);
private:
TestServer fTestServer;