NetServices: add the HttpSerializer helper to help serialize requests

Change-Id: Ide1e2d387884ce4cf2d406057960cd0732d61f38
This commit is contained in:
Niels Sascha Reedijk
2022-08-07 06:47:15 +01:00
parent e68284565a
commit c7f925c3ee
13 changed files with 314 additions and 557 deletions
-191
View File
@@ -1,191 +0,0 @@
/*
* Copyright 2022 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Niels Sascha Reedijk, [email protected]
*
* 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
*/
/*!
\fn ssize_t BAbstractDataStream::BufferData(BDataIO* source, size_t maxSize)
\brief Internal method to append data to the internal buffer.
This is a helper method that reads data from a \a source into the internal \ref fBuffer.
The number of bytes depends loaded depends on the following properties:
- The maximum number of bytes for the fBuffer is set to 65kB. If there already is data in
the buffer, only additional bytes up to the maximum buffer size are read.
- If \a maxSize is larger than 65kB, or if \a maxSize plus the current size of the input
buffer is larger than 65kB, only a maximum of 65kB will be loaded.
- If the \a source has fewer than \a maxSize bytes, then fewer bytes will be loaded.
\param source The data source to read from.
\param maxSize The maximum size to read from the source.
\return The output of the \ref BDataIO::Read() call that is executed on the source. When actual
data is read, this will be the number of bytes that are read. If it is an error, all errors
will be returned, except for \c B_INTERRUPTED, as interrupted \ref BDataIO::Read() calls
are retried. It is up to the calling implementation to do further error handling.
\exception std::bad_alloc This exception may be raised if it is impossible to allocate memory.
\since Haiku R1
*/
/*!
\var std::vector<std::byte> BAbstractDataStream::fBuffer
\brief Internal buffer that can be used by implementations to buffer data.
*/
/*!
\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
@@ -25,6 +25,8 @@ namespace Network {
class BHttpFields; class BHttpFields;
class HttpBuffer;
class HttpSerializer;
class BHttpMethod { class BHttpMethod {
@@ -134,9 +136,11 @@ public:
private: private:
friend class BHttpSession; friend class BHttpSession;
friend class HttpSerializer;
struct Data; struct Data;
bool RewindBody() noexcept; bool RewindBody() noexcept;
void SerializeHeaderTo(HttpBuffer& buffer) const;
std::unique_ptr<Data> fData; std::unique_ptr<Data> fData;
}; };
-72
View File
@@ -1,72 +0,0 @@
/*
* Copyright 2022 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HTTP_STREAM_H_
#define _HTTP_STREAM_H_
#include <functional>
#include <memory>
#include <optional>
#include <vector>
class BDataIO;
class BMallocIO;
class BString;
namespace BPrivate {
namespace Network {
class BHttpFields;
class BHttpRequest;
class BHttpStatus;
class HttpBuffer;
class BAbstractDataStream {
public:
struct TransferInfo {
off_t currentBytesWritten;
off_t totalBytesWritten;
off_t totalSize;
bool complete;
};
virtual TransferInfo Transfer(BDataIO*) = 0;
protected:
ssize_t BufferData(BDataIO* source, size_t maxSize);
std::vector<std::byte> fBuffer;
};
class BHttpRequestStream : public BAbstractDataStream {
public:
BHttpRequestStream(const BHttpRequest& request);
~BHttpRequestStream();
virtual TransferInfo Transfer(BDataIO* target) override;
private:
off_t fRemainingHeaderSize = 0;
BDataIO* fBody = nullptr;
off_t fTotalBodySize = 0;
off_t fBufferedBodySize = 0;
off_t fTransferredBodySize = 0;
};
using HttpTransferFunction = std::function<size_t (const std::byte*, size_t)>;
} // namespace Network
} // namespace BPrivate
#endif // _HTTP_STREAM_H_
@@ -41,7 +41,7 @@ HttpBuffer::HttpBuffer(size_t capacity)
\retval >=0 The actual number of bytes read. \retval >=0 The actual number of bytes read.
*/ */
ssize_t ssize_t
HttpBuffer::ReadFrom(BDataIO* source) HttpBuffer::ReadFrom(BDataIO* source, std::optional<size_t> maxSize)
{ {
// Remove any unused bytes at the beginning of the buffer // Remove any unused bytes at the beginning of the buffer
Flush(); Flush();
@@ -49,6 +49,9 @@ HttpBuffer::ReadFrom(BDataIO* source)
auto currentSize = fBuffer.size(); auto currentSize = fBuffer.size();
auto remainingBufferSize = fBuffer.capacity() - currentSize; auto remainingBufferSize = fBuffer.capacity() - currentSize;
if (maxSize && maxSize.value() < remainingBufferSize)
remainingBufferSize = maxSize.value();
// Adjust the buffer to the maximum size // Adjust the buffer to the maximum size
fBuffer.resize(fBuffer.capacity()); fBuffer.resize(fBuffer.capacity());
@@ -177,3 +180,21 @@ HttpBuffer::Clear() noexcept
fBuffer.clear(); fBuffer.clear();
fCurrentOffset = 0; fCurrentOffset = 0;
} }
/*!
\brief Load data into the buffer
\exception BNetworkRequestError in case of a buffer overflow
*/
HttpBuffer&
HttpBuffer::operator<<(const std::string_view& data)
{
if (data.size() > (fBuffer.capacity() - fBuffer.size()))
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError);
for (const auto& character: data)
fBuffer.push_back(static_cast<const std::byte>(character));
return *this;
}
@@ -8,6 +8,7 @@
#include <functional> #include <functional>
#include <optional> #include <optional>
#include <string_view>
#include <vector> #include <vector>
class BDataIO; class BDataIO;
@@ -25,7 +26,7 @@ class HttpBuffer {
public: public:
HttpBuffer(size_t capacity = 8*1024); HttpBuffer(size_t capacity = 8*1024);
ssize_t ReadFrom(BDataIO* source); ssize_t ReadFrom(BDataIO* source, std::optional<size_t> maxSize = std::nullopt);
void WriteExactlyTo(BDataIO* target); void WriteExactlyTo(BDataIO* target);
void WriteTo(HttpTransferFunction func, void WriteTo(HttpTransferFunction func,
std::optional<size_t> maxSize = std::nullopt); std::optional<size_t> maxSize = std::nullopt);
@@ -36,6 +37,9 @@ public:
void Flush() noexcept; void Flush() noexcept;
void Clear() noexcept; void Clear() noexcept;
// load data into the buffer
HttpBuffer& operator<<(const std::string_view& data);
private: private:
std::vector<std::byte> fBuffer; std::vector<std::byte> fBuffer;
size_t fCurrentOffset = 0; size_t fCurrentOffset = 0;
@@ -19,6 +19,7 @@
#include <NetServicesDefs.h> #include <NetServicesDefs.h>
#include <Url.h> #include <Url.h>
#include "HttpBuffer.h"
#include "HttpPrivate.h" #include "HttpPrivate.h"
using namespace std::literals; using namespace std::literals;
@@ -544,3 +545,67 @@ BHttpRequest::RewindBody() noexcept
} }
return true; return true;
} }
/*!
\brief Private method used by HttpSerializer::SetTo() to serialize the header data into a
buffer.
*/
void
BHttpRequest::SerializeHeaderTo(HttpBuffer& buffer) const
{
// Method & URL
// TODO: proxy
buffer << fData->method.Method() << " "sv;
if (fData->url.HasPath() && fData->url.Path().Length() > 0)
buffer << std::string_view(fData->url.Path().String());
else
buffer << "/"sv;
// TODO: switch between HTTP 1.0 and 1.1 based on configuration
buffer << " HTTP/1.1\r\n"sv;
BHttpFields outputFields;
if (true /* http == 1.1 */) {
BString host = fData->url.Host();
int defaultPort = fData->url.Protocol() == "http" ? 80 : 443;
if (fData->url.HasPort() && fData->url.Port() != defaultPort)
host << ':' << fData->url.Port();
outputFields.AddFields({
{"Host"sv, std::string_view(host.String())},
{"Accept-Encoding"sv, "gzip"sv},
// Allows the server to compress data using the "gzip" format.
// "deflate" is not supported, because there are two interpretations
// of what it means (the RFC and Microsoft products), and we don't
// want to handle this. Very few websites support only deflate,
// and most of them will send gzip, or at worst, uncompressed data.
{"Connection"sv, "close"sv}
// Let the remote server close the connection after response since
// we don't handle multiple request on a single connection
});
}
if (fData->authentication) {
// This request will add a Basic authorization header
BString authorization = build_basic_http_header(fData->authentication->username,
fData->authentication->password);
outputFields.AddField("Authorization"sv, std::string_view(authorization.String()));
}
if (fData->requestBody) {
outputFields.AddField("Content-Type"sv, std::string_view(fData->requestBody->mimeType.String()));
if (fData->requestBody->size)
outputFields.AddField("Content-Length"sv, std::to_string(*fData->requestBody->size));
else
throw BRuntimeError(__PRETTY_FUNCTION__, "Transfer body with unknown content length; chunked transfer not supported");
}
for (const auto& field: outputFields)
buffer << field.RawField() << "\r\n"sv;
for (const auto& field: fData->optionalFields)
buffer << field.RawField() << "\r\n"sv;
buffer << "\r\n"sv;
}
@@ -0,0 +1,136 @@
/*
* Copyright 2022 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Niels Sascha Reedijk, [email protected]
*/
#include "HttpSerializer.h"
#include <DataIO.h>
#include <HttpRequest.h>
#include <NetServicesDefs.h>
#include "HttpBuffer.h"
using namespace std::literals;
using namespace BPrivate::Network;
/*!
\brief Set the \a request to serialize, and load the initial data into the \a buffer.
*/
void
HttpSerializer::SetTo(HttpBuffer& buffer, const BHttpRequest& request)
{
buffer.Clear();
request.SerializeHeaderTo(buffer);
fState = HttpSerializerState::Header;
if (auto requestBody = request.RequestBody()) {
fBody = requestBody->input.get();
if (requestBody->size) {
fBodySize = *(requestBody->size);
}
}
}
/*!
\brief Transfer the HTTP request to \a target while using \a buffer for intermediate storage.
\returns The number of body bytes written during the call.
*/
size_t
HttpSerializer::Serialize(HttpBuffer& buffer, BDataIO* target)
{
bool finishing = false;
size_t bodyBytesWritten = 0;
while (!finishing) {
switch (fState) {
case HttpSerializerState::Uninitialized:
throw BRuntimeError(__PRETTY_FUNCTION__, "Invalid state: Uninitialized");
case HttpSerializerState::Header:
_WriteToTarget(buffer, target);
if (buffer.RemainingBytes() > 0) {
// There are more bytes to be processed; wait for the next iteration
return 0;
}
if (fBody == nullptr) {
fState = HttpSerializerState::Done;
return 0;
} else if (_IsChunked())
//fState = HttpSerializerState::ChunkHeader;
throw BRuntimeError(__PRETTY_FUNCTION__, "Chunked serialization not implemented");
else
fState = HttpSerializerState::Body;
break;
case HttpSerializerState::Body:
{
auto bytesWritten = _WriteToTarget(buffer, target);
bodyBytesWritten += bytesWritten;
fTransferredBodySize += bytesWritten;
if (buffer.RemainingBytes() > 0) {
// did not manage to write all the bytes in the buffer; continue in the next round
finishing = true;
break;
}
if (fBodySize && fBodySize.value() == fTransferredBodySize) {
fState = HttpSerializerState::Done;
finishing = true;
}
break;
}
case HttpSerializerState::Done:
default:
finishing = true;
continue;
}
// Load more data into the buffer
std::optional<size_t> maxReadSize = std::nullopt;
if (fBodySize)
maxReadSize = fBodySize.value() - fTransferredBodySize;
buffer.ReadFrom(fBody, maxReadSize);
}
return bodyBytesWritten;
}
bool
HttpSerializer::_IsChunked() const noexcept
{
return fBodySize == std::nullopt;
}
size_t
HttpSerializer::_WriteToTarget(HttpBuffer& buffer, BDataIO* target) const
{
size_t bytesWritten = 0;
buffer.WriteTo([target, &bytesWritten](const std::byte* buffer, size_t size){
ssize_t result = B_INTERRUPTED;
while (result == B_INTERRUPTED) {
result = target->Write(buffer, size);
}
if (result <= 0 && result != B_WOULD_BLOCK) {
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::NetworkError,
result);
} else if (result > 0) {
bytesWritten += result;
return size_t(result);
} else {
return size_t(0);
}
});
return bytesWritten;
}
@@ -0,0 +1,63 @@
/*
* Copyright 2022 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _B_HTTP_SERIALIZER_H_
#define _B_HTTP_SERIALIZER_H_
#include <functional>
#include <optional>
class BDataIO;
namespace BPrivate {
namespace Network {
class BHttpRequest;
class HttpBuffer;
using HttpTransferFunction = std::function<size_t (const std::byte*, size_t)>;
enum class HttpSerializerState {
Uninitialized,
Header,
ChunkHeader,
Body,
Done
};
class HttpSerializer {
public:
HttpSerializer() {};
void SetTo(HttpBuffer& buffer, const BHttpRequest& request);
bool IsInitialized() const noexcept { return fState != HttpSerializerState::Uninitialized; }
size_t Serialize(HttpBuffer& buffer, BDataIO* target);
std::optional<off_t> BodyBytesTotal() const noexcept { return fBodySize; };
off_t BodyBytesTransferred() const noexcept { return fTransferredBodySize; };
bool Complete() const noexcept { return fState == HttpSerializerState::Done; };
private:
bool _IsChunked() const noexcept;
size_t _WriteToTarget(HttpBuffer& buffer, BDataIO* target) const;
private:
HttpSerializerState fState = HttpSerializerState::Uninitialized;
BDataIO* fBody = nullptr;
off_t fTransferredBodySize = 0;
std::optional<off_t> fBodySize;
};
} // namespace Network
} // namespace BPrivate
#endif // _B_HTTP_SERIALIZER_H_
@@ -21,7 +21,6 @@
#include <HttpRequest.h> #include <HttpRequest.h>
#include <HttpResult.h> #include <HttpResult.h>
#include <HttpSession.h> #include <HttpSession.h>
#include <HttpStream.h>
#include <Locker.h> #include <Locker.h>
#include <Messenger.h> #include <Messenger.h>
#include <NetBuffer.h> #include <NetBuffer.h>
@@ -34,6 +33,7 @@
#include "HttpBuffer.h" #include "HttpBuffer.h"
#include "HttpParser.h" #include "HttpParser.h"
#include "HttpSerializer.h"
#include "HttpResultPrivate.h" #include "HttpResultPrivate.h"
#include "NetServicesPrivate.h" #include "NetServicesPrivate.h"
@@ -116,12 +116,9 @@ private:
BNetworkAddress fRemoteAddress; BNetworkAddress fRemoteAddress;
std::unique_ptr<BSocket> fSocket; std::unique_ptr<BSocket> fSocket;
// Transfer state // Sending and receiving
std::unique_ptr<BAbstractDataStream>
fDataStream;
// Receive buffers
HttpBuffer fBuffer; HttpBuffer fBuffer;
HttpSerializer fSerializer;
HttpParser fParser; HttpParser fParser;
// Receive state // Receive state
@@ -820,26 +817,25 @@ BHttpSession::Request::TransferRequest()
throw BRuntimeError(__PRETTY_FUNCTION__, throw BRuntimeError(__PRETTY_FUNCTION__,
"Write request for object that is not in the Connected state"); "Write request for object that is not in the Connected state");
if (!fDataStream) if (!fSerializer.IsInitialized())
fDataStream = std::make_unique<BHttpRequestStream>(fRequest); fSerializer.SetTo(fBuffer, fRequest);
auto [currentBytesWritten, totalBytesWritten, totalSize, complete] auto currentBytesWritten = fSerializer.Serialize(fBuffer, fSocket.get());
= fDataStream->Transfer(fSocket.get());
// TODO: make nicer after replacing transferinfo if (currentBytesWritten > 0) {
off_t vTotalBytesWritten = totalBytesWritten; SendMessage(UrlEvent::UploadProgress, [this](BMessage& msg) {
off_t vTotalSize = totalSize; msg.AddInt64(UrlEventData::NumBytes, fSerializer.BodyBytesTransferred());
SendMessage(UrlEvent::UploadProgress, [vTotalBytesWritten, vTotalSize](BMessage& msg) { if (auto totalSize = fSerializer.BodyBytesTotal())
msg.AddInt64(UrlEventData::NumBytes, vTotalBytesWritten); msg.AddInt64(UrlEventData::TotalBytes, totalSize.value());
msg.AddInt64(UrlEventData::TotalBytes, vTotalSize);
// TODO: handle case with unknown total size
}); });
}
if (complete) if (fSerializer.Complete())
fRequestStatus = RequestSent; fRequestStatus = RequestSent;
std::cout << "TransferRequest() [" << Id() << "] currentBytesWritten: " << currentBytesWritten << " totalBytesWritten: " << std::cout << "TransferRequest() [" << Id() << "] currentBytesWritten: " << currentBytesWritten << " totalBytesWritten: " <<
totalBytesWritten << " totalSize: " << totalSize << " complete: " << complete << std::endl; fSerializer.BodyBytesTransferred() << " totalSize: " << fSerializer.BodyBytesTotal().value_or(0) << " complete: "
<< fSerializer.Complete() << std::endl;
} }
@@ -1,211 +0,0 @@
/*
* 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 <optional>
#include <string>
#include <DataIO.h>
#include <HttpFields.h>
#include <HttpRequest.h>
#include <HttpResult.h>
#include <NetServicesDefs.h>
#include "HttpBuffer.h"
using namespace BPrivate::Network;
/*!
\brief Size of the internal buffer for reads/writes
Curl 7.82.0 sets the default to 512 kB (524288 bytes)
https://github.com/curl/curl/blob/64db5c575d9c5536bd273a890f50777ad1ca7c13/include/curl/curl.h#L232
Libsoup sets it to 8 kB, though the buffer may grow beyond that if there are leftover bytes.
The absolute maximum seems to be 64 kB (HEADER_SIZE_LIMIT)
https://gitlab.gnome.org/GNOME/libsoup/-/blob/master/libsoup/http1/soup-client-message-io-http1.c#L58
The previous iteration set it to 4 kB, though the input buffer would dynamically grow.
*/
static constexpr ssize_t kMaxBufferSize = 8192;
// #pragma mark -- ByteIOHelper base class
class ByteIOHelper : public BDataIO {
public:
ByteIOHelper(std::vector<std::byte>& buffer);
virtual ssize_t Read(void* buffer, size_t size) override;
virtual ssize_t Write(const void* buffer, size_t size) override;
private:
std::vector<std::byte>& fBuffer;
};
ByteIOHelper::ByteIOHelper(std::vector<std::byte>& buffer)
: fBuffer(buffer)
{
if (buffer.size() != 0)
throw BRuntimeError(__PRETTY_FUNCTION__, "Target buffer with size > 0");
}
ssize_t
ByteIOHelper::Read(void* buffer, size_t size)
{
throw BRuntimeError(__PRETTY_FUNCTION__, "Unexpected Read() call");
}
ssize_t
ByteIOHelper::Write(const void* buffer, size_t size)
{
auto remainingSize = kMaxBufferSize - fBuffer.size();
if (remainingSize < 0)
return 0;
if (size > remainingSize)
size = remainingSize;
auto bufferCast = static_cast<const std::byte*>(buffer);
fBuffer.insert(fBuffer.end(), bufferCast, bufferCast + size);
return size;
}
// #pragma mark -- BAbstractDataStream (helper methods)
/*!
\brief Load data from \a source into the internal buffer.
The buffer will be filled up to the maximum size (64kB). Partial reads are supported; it will
not do a retry.
\return The return value of the underlying BDataIO::Read() call.
*/
ssize_t
BAbstractDataStream::BufferData(BDataIO* source, size_t maxSize)
{
auto currentSize = fBuffer.size();
auto remainingSize = kMaxBufferSize - currentSize;
if (remainingSize < 0)
return B_OK;
if (remainingSize > maxSize)
remainingSize = maxSize;
fBuffer.resize(currentSize + remainingSize);
ssize_t readSize = B_INTERRUPTED;
while (readSize == B_INTERRUPTED)
readSize = source->Read(fBuffer.data() + currentSize, remainingSize);
if (readSize <= 0) {
fBuffer.resize(currentSize); // resize back to the original size
return readSize;
}
if (readSize > 0)
fBuffer.resize(currentSize + readSize);
return readSize;
}
// #pragma mark -- BHttpRequestStream
BHttpRequestStream::BHttpRequestStream(const BHttpRequest& request)
: fBody(nullptr)
{
// Serialize the header of the request to text
ByteIOHelper helper(fBuffer);
fRemainingHeaderSize = request.SerializeHeaderTo(&helper);
// Check if there is a body
if (auto requestBody = request.RequestBody()) {
fBody = requestBody->input.get();
if (!requestBody->size) {
throw BRuntimeError(__PRETTY_FUNCTION__,
"BHttpRequestStream: chunked transfer not implemented");
}
fTotalBodySize += *requestBody->size;
}
}
BHttpRequestStream::~BHttpRequestStream() = default;
BHttpRequestStream::TransferInfo
BHttpRequestStream::Transfer(BDataIO* target)
{
if (fBuffer.size() == 0 && fTotalBodySize == fBufferedBodySize) {
// all done; header was written and no more body left
return TransferInfo{0, fTotalBodySize, fTotalBodySize, true};
}
if (fBody != nullptr && fBuffer.size() < kMaxBufferSize) {
// buffer additional data from the body in the buffer
auto remainingBodySize = fTotalBodySize - fBufferedBodySize;
auto bufferedSize = BufferData(fBody, remainingBodySize);
if (bufferedSize == B_WOULD_BLOCK) {
// do nothing; try again next round
} else if (bufferedSize == 0) {
// no remaining data; throw error
throw BRuntimeError(__PRETTY_FUNCTION__,
"No more data in request input body while more data is expected");
} else if (bufferedSize < 0) {
throw BSystemError(__PRETTY_FUNCTION__, bufferedSize);
} else {
// update counters
fBufferedBodySize += bufferedSize;
if (fBufferedBodySize == fTotalBodySize) {
// no more body to load
fBody = nullptr;
}
}
}
if (fBuffer.size() == 0) {
// nothing this round
return TransferInfo{0, fTransferredBodySize, fTotalBodySize, false};
}
auto bytesWritten = target->Write(fBuffer.data(), fBuffer.size());
if (bytesWritten == B_WOULD_BLOCK || bytesWritten == 0)
return TransferInfo{0, fTransferredBodySize, fTotalBodySize, false};
else if (bytesWritten < 0)
throw BSystemError("BDataIO::Write()", bytesWritten);
// Adjust the buffer
if (static_cast<size_t>(bytesWritten) == fBuffer.size())
fBuffer.clear();
else
fBuffer.erase(fBuffer.begin(), fBuffer.begin() + bytesWritten);
// Update the stats and return
if (fRemainingHeaderSize > 0){
if (bytesWritten >= fRemainingHeaderSize) {
bytesWritten -= fRemainingHeaderSize;
fRemainingHeaderSize = 0;
} else {
fRemainingHeaderSize -= bytesWritten;
bytesWritten = 0;
}
}
fTransferredBodySize += bytesWritten;
auto complete = fRemainingHeaderSize == 0 && fTransferredBodySize == fTotalBodySize;
return TransferInfo{bytesWritten, fTransferredBodySize, fTotalBodySize, complete};
}
+1 -1
View File
@@ -24,8 +24,8 @@ for architectureObject in [ MultiArchSubDirSetup ] {
HttpParser.cpp HttpParser.cpp
HttpRequest.cpp HttpRequest.cpp
HttpResult.cpp HttpResult.cpp
HttpSerializer.cpp
HttpSession.cpp HttpSession.cpp
HttpStream.cpp
HttpTime.cpp HttpTime.cpp
NetServicesMisc.cpp NetServicesMisc.cpp
; ;
@@ -17,7 +17,6 @@
#include <HttpFields.h> #include <HttpFields.h>
#include <HttpRequest.h> #include <HttpRequest.h>
#include <HttpResult.h> #include <HttpResult.h>
#include <HttpStream.h>
#include <HttpTime.h> #include <HttpTime.h>
#include <Looper.h> #include <Looper.h>
#include <NetServicesDefs.h> #include <NetServicesDefs.h>
@@ -27,7 +26,6 @@ using BPrivate::BDateTime;
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::BHttpRequestStream;
using BPrivate::Network::BHttpResult; using BPrivate::Network::BHttpResult;
using BPrivate::Network::BHttpSession; using BPrivate::Network::BHttpSession;
using BPrivate::Network::BHttpTime; using BPrivate::Network::BHttpTime;
@@ -314,59 +312,6 @@ HttpProtocolTest::HttpRequestTest()
} }
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;
};
constexpr std::string_view kExpectedStreamText =
"GET / HTTP/1.1\r\n"
"Host: www.haiku-os.org\r\n"
"Accept-Encoding: gzip\r\n"
"Connection: close\r\n\r\n";
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(kExpectedStreamText.data());
bool finished = false;
while (!finished) {
auto [currentBytesWritten, totalBytesWritten, totalSize, complete]
= requestStream.Transfer(&testIO);
finished = complete;
}
}
void void
HttpProtocolTest::HttpTimeTest() HttpProtocolTest::HttpTimeTest()
{ {
@@ -424,8 +369,6 @@ 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::HttpTimeTest", &HttpProtocolTest::HttpTimeTest)); "HttpProtocolTest::HttpTimeTest", &HttpProtocolTest::HttpTimeTest));
@@ -23,7 +23,6 @@ public:
void HttpFieldsTest(); void HttpFieldsTest();
void HttpMethodTest(); void HttpMethodTest();
void HttpRequestTest(); void HttpRequestTest();
void HttpRequestStreamTest();
void HttpTimeTest(); void HttpTimeTest();
static void AddTests(BTestSuite& suite); static void AddTests(BTestSuite& suite);