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=(BHttpSession&&) noexcept = delete;
|
||||
|
||||
|
||||
// Requests
|
||||
BHttpResult Execute(BHttpRequest&& request,
|
||||
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
|
||||
HttpResult.cpp
|
||||
HttpSession.cpp
|
||||
HttpStream.cpp
|
||||
NetServicesMisc.cpp
|
||||
;
|
||||
|
||||
|
@ -15,14 +15,16 @@
|
||||
#include <HttpFields.h>
|
||||
#include <HttpRequest.h>
|
||||
#include <HttpResult.h>
|
||||
#include <HttpStream.h>
|
||||
#include <NetServicesDefs.h>
|
||||
#include <Url.h>
|
||||
|
||||
using BPrivate::Network::BHttpFields;
|
||||
using BPrivate::Network::BHttpMethod;
|
||||
using BPrivate::Network::BHttpRequest;
|
||||
using BPrivate::Network::BHttpSession;
|
||||
using BPrivate::Network::BHttpResult;
|
||||
using BPrivate::Network::BHttpSession;
|
||||
using BPrivate::Network::BHttpRequestStream;
|
||||
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
|
||||
HttpProtocolTest::HttpRequestTest()
|
||||
{
|
||||
@ -237,7 +247,72 @@ HttpProtocolTest::HttpRequestTest()
|
||||
|
||||
// Validate header serialization
|
||||
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));
|
||||
suite.addTest(new CppUnit::TestCaller<HttpProtocolTest>(
|
||||
"HttpProtocolTest::HttpRequestTest", &HttpProtocolTest::HttpRequestTest));
|
||||
suite.addTest(new CppUnit::TestCaller<HttpProtocolTest>(
|
||||
"HttpProtocolTest::HttpRequestStreamTest", &HttpProtocolTest::HttpRequestStreamTest));
|
||||
suite.addTest(new CppUnit::TestCaller<HttpProtocolTest>(
|
||||
"HttpProtocolTest::HttpIntegrationTest", &HttpProtocolTest::HttpIntegrationTest));
|
||||
|
||||
|
@ -20,6 +20,7 @@ public:
|
||||
void HttpFieldsTest();
|
||||
void HttpMethodTest();
|
||||
void HttpRequestTest();
|
||||
void HttpRequestStreamTest();
|
||||
void HttpIntegrationTest();
|
||||
|
||||
static void AddTests(BTestSuite& suite);
|
||||
|
Loading…
Reference in New Issue
Block a user