NetServices: add the HttpSerializer helper to help serialize requests
Change-Id: Ide1e2d387884ce4cf2d406057960cd0732d61f38
This commit is contained in:
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -543,4 +544,68 @@ BHttpRequest::RewindBody() noexcept
|
|||||||
== inputData->Seek(*fData->requestBody->startPosition, SEEK_SET);
|
== inputData->Seek(*fData->requestBody->startPosition, SEEK_SET);
|
||||||
}
|
}
|
||||||
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]
|
|
||||||
= fDataStream->Transfer(fSocket.get());
|
|
||||||
|
|
||||||
// TODO: make nicer after replacing transferinfo
|
auto currentBytesWritten = fSerializer.Serialize(fBuffer, fSocket.get());
|
||||||
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
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
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, [email protected]
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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};
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user