NetServices: Serialize the header of a BHttpRequest to a string

Change-Id: Ib1e22536a0b39dc6e9461e7993ea6784f1ea0e2f
This commit is contained in:
Niels Sascha Reedijk
2022-03-30 07:38:57 +01:00
parent 3b172a3dc6
commit d9a4c6070c
8 changed files with 169 additions and 1 deletions
+17
View File
@@ -476,6 +476,23 @@ namespace Network {
*/
/*!
\fn void BHttpFields::AddFields(std::initializer_list< Field > fields)
\brief Add a list of fields.
This enables you to add a list of \ref BHttpFields::Field objects. Like \ref AddField(), the
fields are added in the the original order, though if there are duplicate keys within the
\a fields list, or there are existing keys in the object, they will be grouped together in
sequence.
\exception std::bad_alloc Error in case memory cannot be allocated.
\exception BHttpFields::InvalidInput This error indicates that some of the names or values in
the list do not adhere to the HTTP specification.
\since Haiku R1
*/
/*!
\fn void BHttpFields::RemoveField(const std::string_view &name) noexcept
\brief Remove all fields with the \a name.
+48
View File
@@ -473,6 +473,54 @@ namespace Network {
//! @}
/*!
\name Serialization
*/
//! @{
/*!
\fn ssize_t BHttpRequest::SerializeHeaderTo(BDataIO *target) const
\brief Serialize the HTTP Header of this request to the \a target.
The HTTP header consists of the request line, and the fields, serialized as text according to
the HTTP specification.
\param target The \ref BDataIO object to write the header to
\return The total number of byte size of the header.
\exception BSystemError In case there is an error when calling the \ref BDataIO::Write() call
of the provided \a target. Note that writing the string is \em not transactional, and that
the error can occur while a part of the header has already been written.
\exception std::bad_alloc In case it is not possible to allocate internal buffers.
\since Haiku R1
*/
/*!
\fn BString BHttpRequest::HeaderToString() const
\brief Serialize the HTTP Header of this request to a string.
The HTTP header consists of the request line, and the fields, serialized as text according to
the HTTP specification.
This method can be used to debug requests.
\return A new string that represents the HTTP request.
\exception std::bad_alloc In case it is not possible to allocate memory for the output string.
\since Haiku R1
*/
//! @}
} // namespace Network
} // namespace BPrivate
+1 -1
View File
@@ -219,7 +219,7 @@ namespace Network {
to initialize it to \c __PRETTY_FUNCTION__ by default.
\param type The error type that describes what the issue was that prevented the completion of
the request.
\param error Optional underlying system error. See the \ref BNetworkRequestError::ErrorType
\param errorCode Optional underlying system error. See the \ref BNetworkRequestError::ErrorType
documentation on which error types expect a system error.
\since Haiku R1
@@ -103,6 +103,7 @@ public:
// Modifiers
void AddField(const std::string_view& name, const std::string_view& value);
void AddFields(std::initializer_list<Field> fields);
void RemoveField(const std::string_view& name) noexcept;
void RemoveField(ConstIterator it) noexcept;
void MakeEmpty() noexcept;
@@ -13,6 +13,8 @@
#include <ErrorsExt.h>
#include <String.h>
class BDataIO;
class BMallocIO;
class BUrl;
@@ -87,6 +89,9 @@ public:
void SetMethod(const BHttpMethod& method);
void SetUrl(const BUrl& url);
// Serialization
ssize_t SerializeHeaderTo(BDataIO* target) const;
BString HeaderToString() const;
private:
friend class BHttpSession;
struct Data;
@@ -407,6 +407,16 @@ BHttpFields::AddField(const std::string_view& name, const std::string_view& valu
}
void
BHttpFields::AddFields(std::initializer_list<Field> fields)
{
for (auto& field: fields) {
if (!field.IsEmpty())
_AddField(Field(field));
}
}
void
BHttpFields::RemoveField(const std::string_view& name) noexcept
{
@@ -10,8 +10,11 @@
#include <algorithm>
#include <ctype.h>
#include <sstream>
#include <utility>
#include <DataIO.h>
#include <HttpFields.h>
#include <NetServicesDefs.h>
#include <Url.h>
@@ -219,3 +222,83 @@ BHttpRequest::SetUrl(const BUrl& url)
}
fData->url = url;
}
[[nodiscard]] static inline ssize_t
_write_to_dataio(BDataIO* target, const std::string_view& data)
{
if (auto status = target->WriteExactly(data.data(), data.size()); status != B_OK)
throw BSystemError("BDataIO::WriteExactly()", status);
return data.size();
}
[[nodiscard]] static inline ssize_t
_write_to_dataio(BDataIO* target, const BString& string)
{
auto length = string.Length();
if (auto status = target->WriteExactly(string.String(), length); status != B_OK)
throw BSystemError("BDataIO::WriteExactly()", status);
return length;
}
ssize_t
BHttpRequest::SerializeHeaderTo(BDataIO* target) const
{
auto bytesWritten = _write_to_dataio(target, fData->method.Method());
bytesWritten += _write_to_dataio(target, " "sv);
// TODO: proxy
if (fData->url.HasPath() && fData->url.Path().Length() > 0)
bytesWritten += _write_to_dataio(target, fData->url.Path());
else
bytesWritten += _write_to_dataio(target, "/"sv);
// TODO: switch between HTTP 1.0 and 1.1 based on configuration
bytesWritten += _write_to_dataio(target, " 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"sv, "*"sv},
{"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
});
}
for (const auto& field: outputFields) {
std::string_view name = field.Name();
bytesWritten += _write_to_dataio(target, name);
bytesWritten += _write_to_dataio(target, ": "sv);
bytesWritten += _write_to_dataio(target, field.Value());
bytesWritten += _write_to_dataio(target, "\r\n"sv);
}
bytesWritten += _write_to_dataio(target, "\r\n"sv);
return bytesWritten;
}
BString
BHttpRequest::HeaderToString() const
{
BMallocIO buffer;
auto size = SerializeHeaderTo(&buffer);
return BString(static_cast<const char*>(buffer.Buffer()), size);
}
@@ -234,6 +234,10 @@ HttpProtocolTest::HttpRequestTest()
auto url = BUrl("https://www.haiku-os.org");
request.SetUrl(url);
CPPUNIT_ASSERT(request.Url() == url);
// Validate header serialization
BString header = request.HeaderToString();
CPPUNIT_ASSERT(header.Compare("GET / HTTP/1.1\r\nHost: www.haiku-os.org\r\nAccept: *\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n") == 0);
}