From c7f925c3ee52d75ab414a8798b06d566d3b06bd1 Mon Sep 17 00:00:00 2001 From: Niels Sascha Reedijk Date: Sun, 7 Aug 2022 06:47:15 +0100 Subject: [PATCH] NetServices: add the HttpSerializer helper to help serialize requests Change-Id: Ide1e2d387884ce4cf2d406057960cd0732d61f38 --- docs/user/netservices/HttpStream.dox | 191 ---------------- headers/private/netservices2/HttpRequest.h | 4 + headers/private/netservices2/HttpStream.h | 72 ------ .../network/libnetservices2/HttpBuffer.cpp | 23 +- src/kits/network/libnetservices2/HttpBuffer.h | 6 +- .../network/libnetservices2/HttpRequest.cpp | 67 +++++- .../libnetservices2/HttpSerializer.cpp | 136 +++++++++++ .../network/libnetservices2/HttpSerializer.h | 63 ++++++ .../network/libnetservices2/HttpSession.cpp | 38 ++-- .../network/libnetservices2/HttpStream.cpp | 211 ------------------ src/kits/network/libnetservices2/Jamfile | 2 +- .../net/netservices2/HttpProtocolTest.cpp | 57 ----- .../kits/net/netservices2/HttpProtocolTest.h | 1 - 13 files changed, 314 insertions(+), 557 deletions(-) delete mode 100644 docs/user/netservices/HttpStream.dox delete mode 100644 headers/private/netservices2/HttpStream.h create mode 100644 src/kits/network/libnetservices2/HttpSerializer.cpp create mode 100644 src/kits/network/libnetservices2/HttpSerializer.h delete mode 100644 src/kits/network/libnetservices2/HttpStream.cpp diff --git a/docs/user/netservices/HttpStream.dox b/docs/user/netservices/HttpStream.dox deleted file mode 100644 index 438267d8ce..0000000000 --- a/docs/user/netservices/HttpStream.dox +++ /dev/null @@ -1,191 +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 - * - * 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 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 diff --git a/headers/private/netservices2/HttpRequest.h b/headers/private/netservices2/HttpRequest.h index 978848147f..da41e3ab66 100644 --- a/headers/private/netservices2/HttpRequest.h +++ b/headers/private/netservices2/HttpRequest.h @@ -25,6 +25,8 @@ namespace Network { class BHttpFields; +class HttpBuffer; +class HttpSerializer; class BHttpMethod { @@ -134,9 +136,11 @@ public: private: friend class BHttpSession; + friend class HttpSerializer; struct Data; bool RewindBody() noexcept; + void SerializeHeaderTo(HttpBuffer& buffer) const; std::unique_ptr fData; }; diff --git a/headers/private/netservices2/HttpStream.h b/headers/private/netservices2/HttpStream.h deleted file mode 100644 index 422199b71a..0000000000 --- a/headers/private/netservices2/HttpStream.h +++ /dev/null @@ -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 -#include -#include -#include - -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 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; - - - - -} // namespace Network - -} // namespace BPrivate - -#endif // _HTTP_STREAM_H_ diff --git a/src/kits/network/libnetservices2/HttpBuffer.cpp b/src/kits/network/libnetservices2/HttpBuffer.cpp index 354f93f8ab..07c64c39d5 100644 --- a/src/kits/network/libnetservices2/HttpBuffer.cpp +++ b/src/kits/network/libnetservices2/HttpBuffer.cpp @@ -41,7 +41,7 @@ HttpBuffer::HttpBuffer(size_t capacity) \retval >=0 The actual number of bytes read. */ ssize_t -HttpBuffer::ReadFrom(BDataIO* source) +HttpBuffer::ReadFrom(BDataIO* source, std::optional maxSize) { // Remove any unused bytes at the beginning of the buffer Flush(); @@ -49,6 +49,9 @@ HttpBuffer::ReadFrom(BDataIO* source) auto currentSize = fBuffer.size(); auto remainingBufferSize = fBuffer.capacity() - currentSize; + if (maxSize && maxSize.value() < remainingBufferSize) + remainingBufferSize = maxSize.value(); + // Adjust the buffer to the maximum size fBuffer.resize(fBuffer.capacity()); @@ -177,3 +180,21 @@ HttpBuffer::Clear() noexcept fBuffer.clear(); 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(character)); + + return *this; +} diff --git a/src/kits/network/libnetservices2/HttpBuffer.h b/src/kits/network/libnetservices2/HttpBuffer.h index 40b34171c4..983b6d64fe 100644 --- a/src/kits/network/libnetservices2/HttpBuffer.h +++ b/src/kits/network/libnetservices2/HttpBuffer.h @@ -8,6 +8,7 @@ #include #include +#include #include class BDataIO; @@ -25,7 +26,7 @@ class HttpBuffer { public: HttpBuffer(size_t capacity = 8*1024); - ssize_t ReadFrom(BDataIO* source); + ssize_t ReadFrom(BDataIO* source, std::optional maxSize = std::nullopt); void WriteExactlyTo(BDataIO* target); void WriteTo(HttpTransferFunction func, std::optional maxSize = std::nullopt); @@ -36,6 +37,9 @@ public: void Flush() noexcept; void Clear() noexcept; + // load data into the buffer + HttpBuffer& operator<<(const std::string_view& data); + private: std::vector fBuffer; size_t fCurrentOffset = 0; diff --git a/src/kits/network/libnetservices2/HttpRequest.cpp b/src/kits/network/libnetservices2/HttpRequest.cpp index 6444aade02..c254adb0db 100644 --- a/src/kits/network/libnetservices2/HttpRequest.cpp +++ b/src/kits/network/libnetservices2/HttpRequest.cpp @@ -19,6 +19,7 @@ #include #include +#include "HttpBuffer.h" #include "HttpPrivate.h" using namespace std::literals; @@ -543,4 +544,68 @@ BHttpRequest::RewindBody() noexcept == inputData->Seek(*fData->requestBody->startPosition, SEEK_SET); } return true; -} \ No newline at end of file +} + + +/*! + \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; +} diff --git a/src/kits/network/libnetservices2/HttpSerializer.cpp b/src/kits/network/libnetservices2/HttpSerializer.cpp new file mode 100644 index 0000000000..bd54a924ca --- /dev/null +++ b/src/kits/network/libnetservices2/HttpSerializer.cpp @@ -0,0 +1,136 @@ +/* + * Copyright 2022 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Niels Sascha Reedijk, niels.reedijk@gmail.com + */ + +#include "HttpSerializer.h" + +#include +#include +#include + +#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 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; +} diff --git a/src/kits/network/libnetservices2/HttpSerializer.h b/src/kits/network/libnetservices2/HttpSerializer.h new file mode 100644 index 0000000000..98e3f37156 --- /dev/null +++ b/src/kits/network/libnetservices2/HttpSerializer.h @@ -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 +#include + +class BDataIO; + +namespace BPrivate { + +namespace Network { + +class BHttpRequest; +class HttpBuffer; + +using HttpTransferFunction = std::function; + + +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 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 fBodySize; +}; + + +} // namespace Network + +} // namespace BPrivate + +#endif // _B_HTTP_SERIALIZER_H_ diff --git a/src/kits/network/libnetservices2/HttpSession.cpp b/src/kits/network/libnetservices2/HttpSession.cpp index c25d8f9e11..1181f710de 100644 --- a/src/kits/network/libnetservices2/HttpSession.cpp +++ b/src/kits/network/libnetservices2/HttpSession.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -34,6 +33,7 @@ #include "HttpBuffer.h" #include "HttpParser.h" +#include "HttpSerializer.h" #include "HttpResultPrivate.h" #include "NetServicesPrivate.h" @@ -116,12 +116,9 @@ private: BNetworkAddress fRemoteAddress; std::unique_ptr fSocket; - // Transfer state - std::unique_ptr - fDataStream; - - // Receive buffers + // Sending and receiving HttpBuffer fBuffer; + HttpSerializer fSerializer; HttpParser fParser; // Receive state @@ -820,26 +817,25 @@ BHttpSession::Request::TransferRequest() throw BRuntimeError(__PRETTY_FUNCTION__, "Write request for object that is not in the Connected state"); - if (!fDataStream) - fDataStream = std::make_unique(fRequest); - - auto [currentBytesWritten, totalBytesWritten, totalSize, complete] - = fDataStream->Transfer(fSocket.get()); + if (!fSerializer.IsInitialized()) + fSerializer.SetTo(fBuffer, fRequest); - // TODO: make nicer after replacing transferinfo - off_t vTotalBytesWritten = totalBytesWritten; - off_t vTotalSize = totalSize; - SendMessage(UrlEvent::UploadProgress, [vTotalBytesWritten, vTotalSize](BMessage& msg) { - msg.AddInt64(UrlEventData::NumBytes, vTotalBytesWritten); - msg.AddInt64(UrlEventData::TotalBytes, vTotalSize); - // TODO: handle case with unknown total size - }); + auto currentBytesWritten = fSerializer.Serialize(fBuffer, fSocket.get()); - if (complete) + if (currentBytesWritten > 0) { + SendMessage(UrlEvent::UploadProgress, [this](BMessage& msg) { + msg.AddInt64(UrlEventData::NumBytes, fSerializer.BodyBytesTransferred()); + if (auto totalSize = fSerializer.BodyBytesTotal()) + msg.AddInt64(UrlEventData::TotalBytes, totalSize.value()); + }); + } + + if (fSerializer.Complete()) fRequestStatus = RequestSent; 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; } diff --git a/src/kits/network/libnetservices2/HttpStream.cpp b/src/kits/network/libnetservices2/HttpStream.cpp deleted file mode 100644 index 7192d640ca..0000000000 --- a/src/kits/network/libnetservices2/HttpStream.cpp +++ /dev/null @@ -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 - -#include -#include - -#include -#include -#include -#include -#include - -#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& 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& fBuffer; -}; - - -ByteIOHelper::ByteIOHelper(std::vector& 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(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(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}; -} diff --git a/src/kits/network/libnetservices2/Jamfile b/src/kits/network/libnetservices2/Jamfile index 336e70845c..5a38083241 100644 --- a/src/kits/network/libnetservices2/Jamfile +++ b/src/kits/network/libnetservices2/Jamfile @@ -24,8 +24,8 @@ for architectureObject in [ MultiArchSubDirSetup ] { HttpParser.cpp HttpRequest.cpp HttpResult.cpp + HttpSerializer.cpp HttpSession.cpp - HttpStream.cpp HttpTime.cpp NetServicesMisc.cpp ; diff --git a/src/tests/kits/net/netservices2/HttpProtocolTest.cpp b/src/tests/kits/net/netservices2/HttpProtocolTest.cpp index d5415ddbc5..9ae52a43fc 100644 --- a/src/tests/kits/net/netservices2/HttpProtocolTest.cpp +++ b/src/tests/kits/net/netservices2/HttpProtocolTest.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -27,7 +26,6 @@ using BPrivate::BDateTime; using BPrivate::Network::BHttpFields; using BPrivate::Network::BHttpMethod; using BPrivate::Network::BHttpRequest; -using BPrivate::Network::BHttpRequestStream; using BPrivate::Network::BHttpResult; using BPrivate::Network::BHttpSession; 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(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 HttpProtocolTest::HttpTimeTest() { @@ -424,8 +369,6 @@ HttpProtocolTest::AddTests(BTestSuite& parent) "HttpProtocolTest::HttpMethodTest", &HttpProtocolTest::HttpMethodTest)); suite.addTest(new CppUnit::TestCaller( "HttpProtocolTest::HttpRequestTest", &HttpProtocolTest::HttpRequestTest)); - suite.addTest(new CppUnit::TestCaller( - "HttpProtocolTest::HttpRequestStreamTest", &HttpProtocolTest::HttpRequestStreamTest)); suite.addTest(new CppUnit::TestCaller( "HttpProtocolTest::HttpTimeTest", &HttpProtocolTest::HttpTimeTest)); diff --git a/src/tests/kits/net/netservices2/HttpProtocolTest.h b/src/tests/kits/net/netservices2/HttpProtocolTest.h index 71ec8e0842..ffd69267e6 100644 --- a/src/tests/kits/net/netservices2/HttpProtocolTest.h +++ b/src/tests/kits/net/netservices2/HttpProtocolTest.h @@ -23,7 +23,6 @@ public: void HttpFieldsTest(); void HttpMethodTest(); void HttpRequestTest(); - void HttpRequestStreamTest(); void HttpTimeTest(); static void AddTests(BTestSuite& suite);