NetServices: add optional fields to BHttpRequest

Change-Id: I6228419a55c81203ce2c26827d17ff6a402d86c5
This commit is contained in:
Niels Sascha Reedijk 2022-05-15 16:43:07 +01:00
parent 46b7da1f4f
commit f751534257
4 changed files with 110 additions and 6 deletions

View File

@ -329,6 +329,12 @@ namespace Network {
<td> The URL. This must start with http or https. </td> <td> The URL. This must start with http or https. </td>
<td> Defaults to an empty \ref BUrl </td> <td> Defaults to an empty \ref BUrl </td>
</tr> </tr>
<tr>
<td> \ref Fields() </td>
<td> \ref SetFields() </td>
<td> Additional fields set in the request header. </td>
<td> Defaults with no additional fields </td>
</tr>
<tr> <tr>
<td> \ref Method() </td> <td> \ref Method() </td>
<td> \ref SetMethod() </td> <td> \ref SetMethod() </td>
@ -472,6 +478,16 @@ namespace Network {
//! @{ //! @{
/*!
\fn const BHttpFields& BHttpRequest::Fields() const noexcept
\brief Get the additional header fields set for the request.
The returned header fields may be empty if no additional header fields were set.
\since Haiku R1
*/
/*! /*!
\fn const BHttpMethod& BHttpRequest::Method() const noexcept \fn const BHttpMethod& BHttpRequest::Method() const noexcept
\brief Get the current method for the request. \brief Get the current method for the request.
@ -515,6 +531,26 @@ namespace Network {
//! @{ //! @{
/*!
\fn void BHttpRequest::SetFields(const BHttpFields &fields)
\brief Set additional header \a fields for this request.
There are a few reserved fields, which cannot be set as optional fields. These currently are:
* \c Host
* \c Accept
* \c Accept-Encoding
* \c Connection
\param fields Additional fields for the header of the request.
\exception std::bad_alloc This exception may be raised if it is impossible to allocate memory.
\exception BHttpFields::InvalidData This exception is raised when the \a fields contain
reserved fields.
\since Haiku R1
*/
/*! /*!
\fn void BHttpRequest::SetMethod(const BHttpMethod &method) \fn void BHttpRequest::SetMethod(const BHttpMethod &method)
\brief Set the \a method for this request. \brief Set the \a method for this request.

View File

@ -23,6 +23,9 @@ namespace BPrivate {
namespace Network { namespace Network {
class BHttpFields;
class BHttpMethod { class BHttpMethod {
public: public:
// Constants for default methods in RFC 7230 section 4.2 // Constants for default methods in RFC 7230 section 4.2
@ -92,11 +95,13 @@ public:
// Access // Access
bool IsEmpty() const noexcept; bool IsEmpty() const noexcept;
const BHttpFields& Fields() const noexcept;
const BHttpMethod& Method() const noexcept; const BHttpMethod& Method() const noexcept;
const BHttpRedirectOptions& Redirect() const noexcept; const BHttpRedirectOptions& Redirect() const noexcept;
const BUrl& Url() const noexcept; const BUrl& Url() const noexcept;
// Named Setters // Named Setters
void SetFields(const BHttpFields& fields);
void SetMethod(const BHttpMethod& method); void SetMethod(const BHttpMethod& method);
void SetRedirect(const BHttpRedirectOptions& redirectOptions); void SetRedirect(const BHttpRedirectOptions& redirectOptions);
void SetUrl(const BUrl& url); void SetUrl(const BUrl& url);

View File

@ -157,12 +157,13 @@ BHttpMethod::Method() const noexcept
static const BUrl kDefaultUrl = BUrl(); static const BUrl kDefaultUrl = BUrl();
static const BHttpMethod kDefaultMethod = BHttpMethod::Get; static const BHttpMethod kDefaultMethod = BHttpMethod::Get;
static const BHttpRedirectOptions kDefaultRedirectOptions = BHttpRedirectOptions(); static const BHttpRedirectOptions kDefaultRedirectOptions = BHttpRedirectOptions();
static const BHttpFields kDefaultOptionalFields = BHttpFields();
struct BHttpRequest::Data { struct BHttpRequest::Data {
BUrl url = kDefaultUrl; BUrl url = kDefaultUrl;
BHttpMethod method = kDefaultMethod; BHttpMethod method = kDefaultMethod;
BHttpRedirectOptions redirectOptions; BHttpRedirectOptions redirectOptions;
BHttpFields optionalFields;
}; };
@ -200,6 +201,15 @@ BHttpRequest::IsEmpty() const noexcept
} }
const BHttpFields&
BHttpRequest::Fields() const noexcept
{
if (!fData)
return kDefaultOptionalFields;
return fData->optionalFields;
}
const BHttpMethod& const BHttpMethod&
BHttpRequest::Method() const noexcept BHttpRequest::Method() const noexcept
{ {
@ -227,6 +237,32 @@ BHttpRequest::Url() const noexcept
} }
static constexpr std::array<std::string_view, 4> fReservedOptionalFieldNames = {
"Host"sv,
"Accept"sv,
"Accept-Encoding"sv,
"Connection"sv
};
void
BHttpRequest::SetFields(const BHttpFields& fields)
{
if (!fData)
fData = std::make_unique<Data>();
for (auto& field: fields) {
if (std::find(fReservedOptionalFieldNames.begin(), fReservedOptionalFieldNames.end(),
field.Name()) != fReservedOptionalFieldNames.end())
{
std::string_view fieldName = field.Name();
throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(fieldName.data(), fieldName.size()));
}
}
fData->optionalFields = fields;
}
void void
BHttpRequest::SetMethod(const BHttpMethod& method) BHttpRequest::SetMethod(const BHttpMethod& method)
{ {
@ -326,6 +362,11 @@ BHttpRequest::SerializeHeaderTo(BDataIO* target) const
bytesWritten += _write_to_dataio(target, "\r\n"sv); bytesWritten += _write_to_dataio(target, "\r\n"sv);
} }
for (const auto& field: fData->optionalFields) {
bytesWritten += _write_to_dataio(target, field.RawField());
bytesWritten += _write_to_dataio(target, "\r\n"sv);
}
bytesWritten += _write_to_dataio(target, "\r\n"sv); bytesWritten += _write_to_dataio(target, "\r\n"sv);
return bytesWritten; return bytesWritten;
} }

View File

@ -276,12 +276,13 @@ HttpProtocolTest::HttpMethodTest()
} }
constexpr std::string_view kHaikuGetRequestText = constexpr std::string_view kExpectedRequestText =
"GET / HTTP/1.1\r\n" "GET / HTTP/1.1\r\n"
"Host: www.haiku-os.org\r\n" "Host: www.haiku-os.org\r\n"
"Accept: *\r\n" "Accept: *\r\n"
"Accept-Encoding: gzip\r\n" "Accept-Encoding: gzip\r\n"
"Connection: close\r\n\r\n"; "Connection: close\r\n"
"Api-Key: 01234567890abcdef\r\n\r\n";
void void
@ -294,9 +295,22 @@ HttpProtocolTest::HttpRequestTest()
request.SetUrl(url); request.SetUrl(url);
CPPUNIT_ASSERT(request.Url() == url); CPPUNIT_ASSERT(request.Url() == url);
// Add Invalid HTTP fields (should throw)
try {
BHttpFields invalidField = {{"Host"sv, "haiku-os.org"sv}};
request.SetFields(invalidField);
CPPUNIT_FAIL("Should not be able to add the invalid \"Host\" field to a request");
} catch (BHttpFields::InvalidInput& e) {
// Correct; do nothing
}
// Add valid HTTP field
BHttpFields validField = {{"Api-Key"sv, "01234567890abcdef"}};
request.SetFields(validField);
// Validate header serialization // Validate header serialization
BString header = request.HeaderToString(); BString header = request.HeaderToString();
CPPUNIT_ASSERT(header.Compare(kHaikuGetRequestText.data(), kHaikuGetRequestText.size()) == 0); CPPUNIT_ASSERT(header.Compare(kExpectedRequestText.data(), kExpectedRequestText.size()) == 0);
} }
@ -326,6 +340,14 @@ private:
}; };
constexpr std::string_view kExpectedStreamText =
"GET / HTTP/1.1\r\n"
"Host: www.haiku-os.org\r\n"
"Accept: *\r\n"
"Accept-Encoding: gzip\r\n"
"Connection: close\r\n\r\n";
void void
HttpProtocolTest::HttpRequestStreamTest() HttpProtocolTest::HttpRequestStreamTest()
{ {
@ -336,11 +358,11 @@ HttpProtocolTest::HttpRequestStreamTest()
// Test streaming the request // Test streaming the request
BHttpRequestStream requestStream(request); BHttpRequestStream requestStream(request);
RequestStreamTestIO testIO(kHaikuGetRequestText.data()); RequestStreamTestIO testIO(kExpectedStreamText.data());
bool finished = false; bool finished = false;
ssize_t expectedBytesWritten = 8; ssize_t expectedBytesWritten = 8;
ssize_t expectedTotalBytesWritten = 8; ssize_t expectedTotalBytesWritten = 8;
const ssize_t expectedTotalSize = kHaikuGetRequestText.size(); const ssize_t expectedTotalSize = kExpectedStreamText.size();
if (expectedTotalSize < 8) { if (expectedTotalSize < 8) {
expectedBytesWritten = expectedTotalSize; expectedBytesWritten = expectedTotalSize;
expectedTotalBytesWritten = expectedTotalSize; expectedTotalBytesWritten = expectedTotalSize;