NetServices: Introduce BHttpRequestStream and abstract interface.
This supports asynchronous transfers of Http Requests to a network interface. Change-Id: I845fb2e08160d219f85b7a08d2d8872ac7359b47
This commit is contained in:
parent
d9a4c6070c
commit
02ea57d7f9
158
docs/user/netservices/HttpStream.dox
Normal file
158
docs/user/netservices/HttpStream.dox
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Haiku, Inc. All rights reserved.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* Niels Sascha Reedijk, niels.reedijk@gmail.com
|
||||||
|
*
|
||||||
|
* Corresponds to:
|
||||||
|
* headers/private/netservices2/HttpStream.h hrev?????
|
||||||
|
* src/kits/network/libnetservices2/HttpStream.cpp hrev?????
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#if __cplusplus >= 201703L
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\file HttpStream.h
|
||||||
|
\ingroup netservices
|
||||||
|
\brief Provides classes and tools to stream HTTP requests and responses.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace BPrivate {
|
||||||
|
|
||||||
|
namespace Network {
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class BAbstractDataStream
|
||||||
|
\ingroup netservices
|
||||||
|
\brief Abstract interface for adaptors that incrementally streams HTTP requests or responses.
|
||||||
|
|
||||||
|
While this interface, and the adapters that implement it, are part of the public API, it will
|
||||||
|
be unnecessary for most intents and purposes to use them in your application. They are used
|
||||||
|
internally, by the session classes like \ref BHttpSession.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\struct BAbstractDataStream::TransferInfo
|
||||||
|
\ingroup netservices
|
||||||
|
\brief Data type to describe the progress of a transfer in a stream.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\var bool BAbstractDataStream::TransferInfo::complete
|
||||||
|
\brief Set to \c true if the data transmission is complete, or \c false if there is more to be
|
||||||
|
transferred.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\var off_t BAbstractDataStream::TransferInfo::currentBytesWritten
|
||||||
|
\brief Set to the number of bytes that was written or read in the most recent call of the
|
||||||
|
\ref BAbstractDataStream::Transfer() call.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\var off_t BAbstractDataStream::TransferInfo::totalBytesWritten
|
||||||
|
\brief Set to the total number of bytes that was written or read in all the calls of the
|
||||||
|
\ref BAbstractDataStream::Transfer() method.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\var off_t BAbstractDataStream::TransferInfo::totalSize
|
||||||
|
\brief Set to the total number of bytes that will be written or read as part of this
|
||||||
|
stream. It may be set to \c -1 if this is unknown.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn virtual TransferInfo BAbstractDataStream::Transfer(BDataIO *)=0
|
||||||
|
\brief Transfer the next set of bytes to and from the data stream.
|
||||||
|
|
||||||
|
Implementations of this interface should provide this method. It should send or receive from
|
||||||
|
the \ref BDataIO argument interface. When implementing this method, consider that it will be
|
||||||
|
used in asynchronous data transfers. This means:
|
||||||
|
- It should expect the IO's Read/Write calls to return \c B_WOULD_BLOCK. In that case the
|
||||||
|
data stream will be paused until the next invocation of this method.
|
||||||
|
- The implementation needs to be stateful, so that multiple calls to this method will
|
||||||
|
incrementally complete the transfer.
|
||||||
|
|
||||||
|
\return The actual progress info of the transfer.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class BHttpRequestStream
|
||||||
|
\ingroup netservices
|
||||||
|
\brief Stream a \ref BHttpRequest to an IO output.
|
||||||
|
|
||||||
|
\note While this class is part of the public API, it will be unnecessary for most intents and
|
||||||
|
purposes to use them in your application. They are used internally by \ref BHttpSession.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn BPrivate::Network::BHttpRequestStream::BHttpRequestStream(const BHttpRequest &request)
|
||||||
|
\brief Constructor
|
||||||
|
|
||||||
|
The lifetime of the object is bound to the lifetime of the \a request object. While the
|
||||||
|
request is being streamed, the \a request object should not be altered, as this may lead to
|
||||||
|
an invalid request being streamed to the network.
|
||||||
|
|
||||||
|
\param request The BHttpRequest to stream.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn BHttpRequestStream::~BHttpRequestStream()
|
||||||
|
\brief Destructor
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn virtual TransferInfo BHttpRequestStream::Transfer(BDataIO *target) override
|
||||||
|
\brief Stream the HTTP request to the \a target.
|
||||||
|
|
||||||
|
\param target The IO object to transfer the request to.
|
||||||
|
|
||||||
|
\return The actual progress info of the transfer.
|
||||||
|
|
||||||
|
\since Haiku R1
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace Network
|
||||||
|
|
||||||
|
} // namespace BPrivate
|
||||||
|
|
||||||
|
#endif
|
@ -33,7 +33,6 @@ public:
|
|||||||
BHttpSession& operator=(const BHttpSession&) noexcept;
|
BHttpSession& operator=(const BHttpSession&) noexcept;
|
||||||
BHttpSession& operator=(BHttpSession&&) noexcept = delete;
|
BHttpSession& operator=(BHttpSession&&) noexcept = delete;
|
||||||
|
|
||||||
|
|
||||||
// Requests
|
// Requests
|
||||||
BHttpResult Execute(BHttpRequest&& request,
|
BHttpResult Execute(BHttpRequest&& request,
|
||||||
std::unique_ptr<BDataIO> target = nullptr,
|
std::unique_ptr<BDataIO> target = nullptr,
|
||||||
@ -46,8 +45,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
} // namespace Network
|
||||||
|
|
||||||
}
|
} // namespace BPrivate
|
||||||
|
|
||||||
#endif // _B_HTTP_REQUEST_H_
|
#endif // _B_HTTP_SESSION_H_
|
||||||
|
56
headers/private/netservices2/HttpStream.h
Normal file
56
headers/private/netservices2/HttpStream.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Haiku Inc. All rights reserved.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _HTTP_STREAM_H_
|
||||||
|
#define _HTTP_STREAM_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class BDataIO;
|
||||||
|
class BMallocIO;
|
||||||
|
class BString;
|
||||||
|
|
||||||
|
|
||||||
|
namespace BPrivate {
|
||||||
|
|
||||||
|
namespace Network {
|
||||||
|
|
||||||
|
class BHttpRequest;
|
||||||
|
|
||||||
|
|
||||||
|
class BAbstractDataStream {
|
||||||
|
public:
|
||||||
|
struct TransferInfo {
|
||||||
|
off_t currentBytesWritten;
|
||||||
|
off_t totalBytesWritten;
|
||||||
|
off_t totalSize;
|
||||||
|
bool complete;
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual TransferInfo Transfer(BDataIO*) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class BHttpRequestStream : public BAbstractDataStream {
|
||||||
|
public:
|
||||||
|
BHttpRequestStream(const BHttpRequest& request);
|
||||||
|
~BHttpRequestStream();
|
||||||
|
|
||||||
|
virtual TransferInfo Transfer(BDataIO* target) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<BMallocIO> fHeader;
|
||||||
|
BDataIO* fBody;
|
||||||
|
off_t fTotalSize = 0;
|
||||||
|
off_t fBodyOffset = 0;
|
||||||
|
off_t fCurrentPos = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace Network
|
||||||
|
|
||||||
|
} // namespace BPrivate
|
||||||
|
|
||||||
|
#endif // _HTTP_STREAM_H_
|
61
src/kits/network/libnetservices2/HttpStream.cpp
Normal file
61
src/kits/network/libnetservices2/HttpStream.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Haiku Inc. All rights reserved.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* Niels Sascha Reedijk, niels.reedijk@gmail.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <HttpStream.h>
|
||||||
|
|
||||||
|
#include <DataIO.h>
|
||||||
|
#include <HttpRequest.h>
|
||||||
|
|
||||||
|
using namespace BPrivate::Network;
|
||||||
|
|
||||||
|
|
||||||
|
BHttpRequestStream::BHttpRequestStream(const BHttpRequest& request)
|
||||||
|
: fHeader(std::make_unique<BMallocIO>()), fBody(nullptr)
|
||||||
|
{
|
||||||
|
// Serialize the header of the request to text
|
||||||
|
fTotalSize = request.SerializeHeaderTo(fHeader.get());
|
||||||
|
fBodyOffset = fTotalSize;
|
||||||
|
// TODO: add size of request body to total size
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BHttpRequestStream::~BHttpRequestStream() = default;
|
||||||
|
|
||||||
|
|
||||||
|
BHttpRequestStream::TransferInfo
|
||||||
|
BHttpRequestStream::Transfer(BDataIO* target)
|
||||||
|
{
|
||||||
|
if (fCurrentPos == fTotalSize)
|
||||||
|
return TransferInfo{0, fTotalSize, fTotalSize, true};
|
||||||
|
|
||||||
|
off_t bytesWritten = 0;
|
||||||
|
|
||||||
|
if (fCurrentPos < fBodyOffset) {
|
||||||
|
// Writing the header
|
||||||
|
auto remainingSize = fBodyOffset - fCurrentPos;
|
||||||
|
bytesWritten = target->Write(
|
||||||
|
static_cast<const char*>(fHeader->Buffer()) + fCurrentPos, remainingSize);
|
||||||
|
if (bytesWritten == B_WOULD_BLOCK)
|
||||||
|
return TransferInfo{0, 0, fTotalSize, false};
|
||||||
|
else if (bytesWritten < 0)
|
||||||
|
throw BSystemError("BDataIO::Write()", bytesWritten);
|
||||||
|
|
||||||
|
fCurrentPos += bytesWritten;
|
||||||
|
|
||||||
|
if (bytesWritten < remainingSize) {
|
||||||
|
return TransferInfo{bytesWritten, fCurrentPos, fTotalSize, false};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the body
|
||||||
|
if (fBody) {
|
||||||
|
// TODO
|
||||||
|
throw BRuntimeError(__PRETTY_FUNCTION__, "Not implemented");
|
||||||
|
}
|
||||||
|
return TransferInfo{bytesWritten, fTotalSize, fTotalSize, true};
|
||||||
|
}
|
@ -23,6 +23,7 @@ for architectureObject in [ MultiArchSubDirSetup ] {
|
|||||||
HttpRequest.cpp
|
HttpRequest.cpp
|
||||||
HttpResult.cpp
|
HttpResult.cpp
|
||||||
HttpSession.cpp
|
HttpSession.cpp
|
||||||
|
HttpStream.cpp
|
||||||
NetServicesMisc.cpp
|
NetServicesMisc.cpp
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -15,14 +15,16 @@
|
|||||||
#include <HttpFields.h>
|
#include <HttpFields.h>
|
||||||
#include <HttpRequest.h>
|
#include <HttpRequest.h>
|
||||||
#include <HttpResult.h>
|
#include <HttpResult.h>
|
||||||
|
#include <HttpStream.h>
|
||||||
#include <NetServicesDefs.h>
|
#include <NetServicesDefs.h>
|
||||||
#include <Url.h>
|
#include <Url.h>
|
||||||
|
|
||||||
using BPrivate::Network::BHttpFields;
|
using BPrivate::Network::BHttpFields;
|
||||||
using BPrivate::Network::BHttpMethod;
|
using BPrivate::Network::BHttpMethod;
|
||||||
using BPrivate::Network::BHttpRequest;
|
using BPrivate::Network::BHttpRequest;
|
||||||
using BPrivate::Network::BHttpSession;
|
|
||||||
using BPrivate::Network::BHttpResult;
|
using BPrivate::Network::BHttpResult;
|
||||||
|
using BPrivate::Network::BHttpSession;
|
||||||
|
using BPrivate::Network::BHttpRequestStream;
|
||||||
using BPrivate::Network::BNetworkRequestError;
|
using BPrivate::Network::BNetworkRequestError;
|
||||||
|
|
||||||
|
|
||||||
@ -225,6 +227,14 @@ HttpProtocolTest::HttpMethodTest()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constexpr std::string_view kHaikuGetRequestText =
|
||||||
|
"GET / HTTP/1.1\r\n"
|
||||||
|
"Host: www.haiku-os.org\r\n"
|
||||||
|
"Accept: *\r\n"
|
||||||
|
"Accept-Encoding: gzip\r\n"
|
||||||
|
"Connection: close\r\n\r\n";
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
HttpProtocolTest::HttpRequestTest()
|
HttpProtocolTest::HttpRequestTest()
|
||||||
{
|
{
|
||||||
@ -237,7 +247,72 @@ HttpProtocolTest::HttpRequestTest()
|
|||||||
|
|
||||||
// Validate header serialization
|
// Validate header serialization
|
||||||
BString header = request.HeaderToString();
|
BString header = request.HeaderToString();
|
||||||
CPPUNIT_ASSERT(header.Compare("GET / HTTP/1.1\r\nHost: www.haiku-os.org\r\nAccept: *\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n") == 0);
|
CPPUNIT_ASSERT(header.Compare(kHaikuGetRequestText.data(), kHaikuGetRequestText.size()) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RequestStreamTestIO : public BDataIO
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RequestStreamTestIO(const std::string_view expectedOutput)
|
||||||
|
: fExpectedOutput(expectedOutput)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept maximum of 8 bytes at a time.
|
||||||
|
ssize_t Write(const void* buffer, size_t size) {
|
||||||
|
ssize_t bytesWritten = (size < 8) ? size : 8;
|
||||||
|
CPPUNIT_ASSERT_MESSAGE("RequestStreamTestIO: bytes written larger than expected output",
|
||||||
|
fExpectedOutput.size() >= (fPos + bytesWritten));
|
||||||
|
CPPUNIT_ASSERT(fExpectedOutput.substr(fPos, bytesWritten) == std::string_view(static_cast<const char*>(buffer), bytesWritten));
|
||||||
|
fPos += bytesWritten;
|
||||||
|
return bytesWritten;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string_view fExpectedOutput;
|
||||||
|
ssize_t fPos = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
HttpProtocolTest::HttpRequestStreamTest()
|
||||||
|
{
|
||||||
|
// Set up basic GET for https://www.haiku-os.org/
|
||||||
|
BHttpRequest request;
|
||||||
|
auto url = BUrl("https://www.haiku-os.org");
|
||||||
|
request.SetUrl(url);
|
||||||
|
|
||||||
|
// Test streaming the request
|
||||||
|
BHttpRequestStream requestStream(request);
|
||||||
|
RequestStreamTestIO testIO(kHaikuGetRequestText.data());
|
||||||
|
bool finished = false;
|
||||||
|
ssize_t expectedBytesWritten = 8;
|
||||||
|
ssize_t expectedTotalBytesWritten = 8;
|
||||||
|
const ssize_t expectedTotalSize = kHaikuGetRequestText.size();
|
||||||
|
if (expectedTotalSize < 8) {
|
||||||
|
expectedBytesWritten = expectedTotalSize;
|
||||||
|
expectedTotalBytesWritten = expectedTotalSize;
|
||||||
|
}
|
||||||
|
while (!finished) {
|
||||||
|
auto [currentBytesWritten, totalBytesWritten, totalSize, complete] = requestStream.Transfer(&testIO);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(expectedBytesWritten, currentBytesWritten);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(expectedTotalBytesWritten, totalBytesWritten);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(expectedTotalSize, totalSize);
|
||||||
|
if (expectedTotalBytesWritten == expectedTotalSize) {
|
||||||
|
// loop should be finished
|
||||||
|
CPPUNIT_ASSERT_EQUAL(true, complete);
|
||||||
|
finished = true;
|
||||||
|
} else {
|
||||||
|
// prepare for next loop
|
||||||
|
if (totalSize - totalBytesWritten < 8) {
|
||||||
|
expectedBytesWritten = totalSize - totalBytesWritten;
|
||||||
|
}
|
||||||
|
expectedTotalBytesWritten += expectedBytesWritten;
|
||||||
|
CPPUNIT_ASSERT_EQUAL(false, complete);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -300,6 +375,8 @@ HttpProtocolTest::AddTests(BTestSuite& parent)
|
|||||||
"HttpProtocolTest::HttpMethodTest", &HttpProtocolTest::HttpMethodTest));
|
"HttpProtocolTest::HttpMethodTest", &HttpProtocolTest::HttpMethodTest));
|
||||||
suite.addTest(new CppUnit::TestCaller<HttpProtocolTest>(
|
suite.addTest(new CppUnit::TestCaller<HttpProtocolTest>(
|
||||||
"HttpProtocolTest::HttpRequestTest", &HttpProtocolTest::HttpRequestTest));
|
"HttpProtocolTest::HttpRequestTest", &HttpProtocolTest::HttpRequestTest));
|
||||||
|
suite.addTest(new CppUnit::TestCaller<HttpProtocolTest>(
|
||||||
|
"HttpProtocolTest::HttpRequestStreamTest", &HttpProtocolTest::HttpRequestStreamTest));
|
||||||
suite.addTest(new CppUnit::TestCaller<HttpProtocolTest>(
|
suite.addTest(new CppUnit::TestCaller<HttpProtocolTest>(
|
||||||
"HttpProtocolTest::HttpIntegrationTest", &HttpProtocolTest::HttpIntegrationTest));
|
"HttpProtocolTest::HttpIntegrationTest", &HttpProtocolTest::HttpIntegrationTest));
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ public:
|
|||||||
void HttpFieldsTest();
|
void HttpFieldsTest();
|
||||||
void HttpMethodTest();
|
void HttpMethodTest();
|
||||||
void HttpRequestTest();
|
void HttpRequestTest();
|
||||||
|
void HttpRequestStreamTest();
|
||||||
void HttpIntegrationTest();
|
void HttpIntegrationTest();
|
||||||
|
|
||||||
static void AddTests(BTestSuite& suite);
|
static void AddTests(BTestSuite& suite);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user