NetServices: format code using haiku-format

This commit formats all the netservices2 code with the `haiku-format` tool from
https://github.com/owenca/haiku-format (commit aa7408e), with the following
customizations:
 * SpaceBeforeRangeBasedForLoopColon is set to false
 * Braces before a catch block are not wrapped
 * Most headers, except for ExclusiveBorrow.h, have been manually reformatted
   to adhere to Haiku's header format (issue #19 in the repository)

Change-Id: I693c4515cf26402e48f35d1213ab6d5fcf14bd1e
This commit is contained in:
Niels Sascha Reedijk
2022-10-29 22:53:57 +01:00
parent 93069fc4bc
commit 71e29bbeea
31 changed files with 1502 additions and 1560 deletions
+40 -40
View File
@@ -18,74 +18,74 @@ namespace BPrivate {
namespace Network { namespace Network {
class BError { class BError
{
public: public:
BError(const char* origin); BError(const char* origin);
BError(BString origin); BError(BString origin);
virtual ~BError() noexcept; virtual ~BError() noexcept;
BError(const BError& error); BError(const BError& error);
BError(BError&& error) noexcept; BError(BError&& error) noexcept;
BError& operator=(const BError& error); BError& operator=(const BError& error);
BError& operator=(BError&& error) noexcept; BError& operator=(BError&& error) noexcept;
virtual const char* Message() const noexcept = 0; virtual const char* Message() const noexcept = 0;
virtual const char* Origin() const noexcept; virtual const char* Origin() const noexcept;
virtual BString DebugMessage() const; virtual BString DebugMessage() const;
void WriteToStream(std::ostream& stream) const; void WriteToStream(std::ostream& stream) const;
size_t WriteToOutput(BDataIO* output) const; size_t WriteToOutput(BDataIO* output) const;
private: private:
virtual void _ReservedError1(); virtual void _ReservedError1();
virtual void _ReservedError2(); virtual void _ReservedError2();
virtual void _ReservedError3(); virtual void _ReservedError3();
virtual void _ReservedError4(); virtual void _ReservedError4();
private: private:
BString fOrigin; BString fOrigin;
}; };
class BRuntimeError : public BError { class BRuntimeError : public BError
{
public: public:
BRuntimeError(const char* origin, const char* message); BRuntimeError(const char* origin, const char* message);
BRuntimeError(const char* origin, BString message); BRuntimeError(const char* origin, BString message);
BRuntimeError(BString origin, BString message); BRuntimeError(BString origin, BString message);
BRuntimeError(const BRuntimeError& other); BRuntimeError(const BRuntimeError& other);
BRuntimeError(BRuntimeError&& other) noexcept; BRuntimeError(BRuntimeError&& other) noexcept;
BRuntimeError& operator=(const BRuntimeError& other); BRuntimeError& operator=(const BRuntimeError& other);
BRuntimeError& operator=(BRuntimeError&& other) noexcept; BRuntimeError& operator=(BRuntimeError&& other) noexcept;
virtual const char* Message() const noexcept override; virtual const char* Message() const noexcept override;
private: private:
BString fMessage; BString fMessage;
}; };
class BSystemError : public BError class BSystemError : public BError
{ {
public: public:
BSystemError(const char* origin, status_t error); BSystemError(const char* origin, status_t error);
BSystemError(BString origin, status_t error); BSystemError(BString origin, status_t error);
BSystemError(const BSystemError& other); BSystemError(const BSystemError& other);
BSystemError& operator=(const BSystemError& other); BSystemError& operator=(const BSystemError& other);
#if __cplusplus >= 201103L BSystemError(BSystemError&& other) noexcept;
BSystemError(BSystemError&& other) noexcept; BSystemError& operator=(BSystemError&& other) noexcept;
BSystemError& operator=(BSystemError&& other) noexcept;
#endif
virtual const char* Message() const noexcept override; virtual const char* Message() const noexcept override;
virtual BString DebugMessage() const override; virtual BString DebugMessage() const override;
status_t Error() noexcept; status_t Error() noexcept;
private: private:
status_t fErrorCode; status_t fErrorCode;
}; };
} // namespace Network } // namespace Network
+56 -104
View File
@@ -16,42 +16,37 @@ namespace BPrivate {
namespace Network { namespace Network {
class BBorrowError : public BError { class BBorrowError : public BError
{
public: public:
BBorrowError(const char* origin) BBorrowError(const char* origin)
: BError(origin) :
BError(origin)
{ {
} }
virtual const char* virtual const char* Message() const noexcept override { return "BBorrowError"; }
Message() const noexcept override
{
return "BBorrowError";
}
}; };
class BorrowAdmin { class BorrowAdmin
{
private: private:
static constexpr uint8 kOwned = 0x1; static constexpr uint8 kOwned = 0x1;
static constexpr uint8 kBorrowed = 0x2; static constexpr uint8 kBorrowed = 0x2;
std::atomic<uint8> fState = kOwned; std::atomic<uint8> fState = kOwned;
protected: protected:
virtual ~BorrowAdmin() = default; virtual ~BorrowAdmin() = default;
virtual void Cleanup() noexcept {};
virtual void ReleasePointer() noexcept {};
virtual void Cleanup() noexcept {};
virtual void ReleasePointer() noexcept {};
public: public:
BorrowAdmin() noexcept BorrowAdmin() noexcept {}
{
}
void void Borrow()
Borrow()
{ {
auto alreadyBorrowed = (fState.fetch_or(kBorrowed) & kBorrowed) == kBorrowed; auto alreadyBorrowed = (fState.fetch_or(kBorrowed) & kBorrowed) == kBorrowed;
if (alreadyBorrowed) { if (alreadyBorrowed) {
@@ -60,8 +55,7 @@ public:
} }
void void Return() noexcept
Return() noexcept
{ {
auto cleanup = (fState.fetch_and(~kBorrowed) & kOwned) != kOwned; auto cleanup = (fState.fetch_and(~kBorrowed) & kOwned) != kOwned;
if (cleanup) if (cleanup)
@@ -69,8 +63,7 @@ public:
} }
void void Forfeit() noexcept
Forfeit() noexcept
{ {
auto cleanup = (fState.fetch_and(~kOwned) & kBorrowed) != kBorrowed; auto cleanup = (fState.fetch_and(~kOwned) & kBorrowed) != kBorrowed;
if (cleanup) if (cleanup)
@@ -78,15 +71,10 @@ public:
} }
bool bool IsBorrowed() noexcept { return (fState.load() & kBorrowed) == kBorrowed; }
IsBorrowed() noexcept
{
return (fState.load() & kBorrowed) == kBorrowed;
}
void void Release()
Release()
{ {
if ((fState.load() & kBorrowed) == kBorrowed) if ((fState.load() & kBorrowed) == kBorrowed)
throw BBorrowError(__PRETTY_FUNCTION__); throw BBorrowError(__PRETTY_FUNCTION__);
@@ -96,57 +84,39 @@ public:
}; };
template <typename T> template<typename T> class BorrowPointer : public BorrowAdmin
class BorrowPointer : public BorrowAdmin
{ {
public: public:
BorrowPointer(T* object) noexcept BorrowPointer(T* object) noexcept
: fPtr(object) :
fPtr(object)
{ {
} }
virtual ~BorrowPointer() { virtual ~BorrowPointer() { delete fPtr; }
delete fPtr;
}
protected: protected:
virtual void virtual void Cleanup() noexcept override { delete this; }
Cleanup() noexcept override
{
delete this;
}
virtual void virtual void ReleasePointer() noexcept override { fPtr = nullptr; }
ReleasePointer() noexcept override
{
fPtr = nullptr;
}
private: private:
T* fPtr; T* fPtr;
}; };
template <typename T> template<typename T> class BExclusiveBorrow
class BExclusiveBorrow { {
template<typename P> template<typename P> friend class BBorrow;
friend class BBorrow;
T* fPtr = nullptr; T* fPtr = nullptr;
BorrowAdmin* fAdminBlock = nullptr; BorrowAdmin* fAdminBlock = nullptr;
public: public:
BExclusiveBorrow() noexcept BExclusiveBorrow() noexcept {}
{
}
BExclusiveBorrow(nullptr_t) noexcept BExclusiveBorrow(nullptr_t) noexcept {}
{
}
BExclusiveBorrow(T* object) BExclusiveBorrow(T* object)
@@ -178,8 +148,7 @@ public:
} }
BExclusiveBorrow& BExclusiveBorrow& operator=(BExclusiveBorrow&& other) noexcept
operator=(BExclusiveBorrow&& other) noexcept
{ {
if (fAdminBlock) if (fAdminBlock)
fAdminBlock->Forfeit(); fAdminBlock->Forfeit();
@@ -191,15 +160,10 @@ public:
} }
bool bool HasValue() const noexcept { return bool(fPtr); }
HasValue() const noexcept
{
return bool(fPtr);
}
T& T& operator*() const
operator*() const
{ {
if (fAdminBlock && !fAdminBlock->IsBorrowed()) if (fAdminBlock && !fAdminBlock->IsBorrowed())
return *fPtr; return *fPtr;
@@ -207,8 +171,7 @@ public:
} }
T* T* operator->() const
operator->() const
{ {
if (fAdminBlock && !fAdminBlock->IsBorrowed()) if (fAdminBlock && !fAdminBlock->IsBorrowed())
return fPtr; return fPtr;
@@ -216,8 +179,7 @@ public:
} }
std::unique_ptr<T> std::unique_ptr<T> Release()
Release()
{ {
if (!fAdminBlock) if (!fAdminBlock)
throw BBorrowError(__PRETTY_FUNCTION__); throw BBorrowError(__PRETTY_FUNCTION__);
@@ -230,27 +192,23 @@ public:
}; };
template <typename T> template<typename T> class BBorrow
class BBorrow { {
T* fPtr = nullptr; T* fPtr = nullptr;
BorrowAdmin* fAdminBlock = nullptr; BorrowAdmin* fAdminBlock = nullptr;
public: public:
BBorrow() noexcept BBorrow() noexcept {}
{
}
BBorrow(nullptr_t) noexcept BBorrow(nullptr_t) noexcept {}
{
}
template<typename P> template<typename P>
explicit BBorrow(BExclusiveBorrow<P>& owner) explicit BBorrow(BExclusiveBorrow<P>& owner)
: fPtr(owner.fPtr), fAdminBlock(owner.fAdminBlock) :
fPtr(owner.fPtr),
fAdminBlock(owner.fAdminBlock)
{ {
fAdminBlock->Borrow(); fAdminBlock->Borrow();
} }
@@ -263,15 +221,16 @@ public:
BBorrow(BBorrow&& other) noexcept BBorrow(BBorrow&& other) noexcept
: fPtr(other.fPtr), fAdminBlock(other.fAdminBlock) :
fPtr(other.fPtr),
fAdminBlock(other.fAdminBlock)
{ {
other.fPtr = nullptr; other.fPtr = nullptr;
other.fAdminBlock = nullptr; other.fAdminBlock = nullptr;
} }
BBorrow& BBorrow& operator=(BBorrow&& other) noexcept
operator=(BBorrow&& other) noexcept
{ {
if (fAdminBlock) if (fAdminBlock)
fAdminBlock->Return(); fAdminBlock->Return();
@@ -291,15 +250,10 @@ public:
} }
bool bool HasValue() const noexcept { return bool(fPtr); }
HasValue() const noexcept
{
return bool(fPtr);
}
T& T& operator*() const
operator*() const
{ {
if (fPtr) if (fPtr)
return *fPtr; return *fPtr;
@@ -307,8 +261,7 @@ public:
} }
T* T* operator->() const
operator->() const
{ {
if (fPtr) if (fPtr)
return fPtr; return fPtr;
@@ -316,8 +269,7 @@ public:
} }
void void Return() noexcept
Return() noexcept
{ {
if (fAdminBlock) if (fAdminBlock)
fAdminBlock->Return(); fAdminBlock->Return();
@@ -327,9 +279,9 @@ public:
}; };
template<class T, class ..._Args> template<class T, class... _Args>
BExclusiveBorrow<T> BExclusiveBorrow<T>
make_exclusive_borrow(_Args&& ...__args) make_exclusive_borrow(_Args&&... __args)
{ {
auto guardedObject = std::make_unique<T>(std::forward<_Args>(__args)...); auto guardedObject = std::make_unique<T>(std::forward<_Args>(__args)...);
auto retval = BExclusiveBorrow<T>(guardedObject.get()); auto retval = BExclusiveBorrow<T>(guardedObject.get());
+77 -64
View File
@@ -21,70 +21,15 @@ namespace BPrivate {
namespace Network { namespace Network {
class BHttpFields { class BHttpFields
{
public: public:
// Exceptions // Exceptions
class InvalidInput : public BError { class InvalidInput;
public:
InvalidInput(const char* origin, BString input);
virtual const char* Message() const noexcept override;
virtual BString DebugMessage() const override;
BString input;
};
// Wrapper Types // Wrapper Types
class FieldName { class FieldName;
public: class Field;
// Comparison
bool operator==(const BString& other) const noexcept;
bool operator==(const std::string_view& other) const noexcept;
bool operator==(const FieldName& other) const noexcept;
// Conversion
operator std::string_view() const;
private:
friend class BHttpFields;
FieldName() noexcept;
FieldName(const std::string_view& name) noexcept;
FieldName(const FieldName& other) noexcept;
FieldName(FieldName&&) noexcept;
FieldName& operator=(const FieldName& other) noexcept;
FieldName& operator=(FieldName&&) noexcept;
std::string_view fName;
};
class Field {
public:
// Constructors
Field() noexcept;
Field(const std::string_view& name, const std::string_view& value);
Field(BString& field);
Field(const Field& other);
Field(Field&&) noexcept;
// Assignment
Field& operator=(const Field& other);
Field& operator=(Field&& other) noexcept;
// Access Operators
const FieldName& Name() const noexcept;
std::string_view Value() const noexcept;
std::string_view RawField() const noexcept;
bool IsEmpty() const noexcept;
private:
friend class BHttpFields;
Field(BString&& rawField);
std::optional<BString> fRawField;
FieldName fName;
std::string_view fValue;
};
// Type Aliases // Type Aliases
using ConstIterator = std::list<Field>::const_iterator; using ConstIterator = std::list<Field>::const_iterator;
@@ -101,14 +46,14 @@ public:
BHttpFields& operator=(BHttpFields&&) noexcept; BHttpFields& operator=(BHttpFields&&) noexcept;
// Access list // Access list
const Field& operator[](size_t index) const; const Field& operator[](size_t index) const;
// Modifiers // Modifiers
void AddField(const std::string_view& name, void AddField(const std::string_view& name,
const std::string_view& value); const std::string_view& value);
void AddField(BString& field); void AddField(BString& field);
void AddFields(std::initializer_list<Field> fields); void AddFields(std::initializer_list<Field> fields);
void RemoveField(const std::string_view& name) noexcept; void RemoveField(const std::string_view& name) noexcept;
void RemoveField(ConstIterator it) noexcept; void RemoveField(ConstIterator it) noexcept;
void MakeEmpty() noexcept; void MakeEmpty() noexcept;
@@ -126,9 +71,77 @@ private:
}; };
class BHttpFields::InvalidInput : public BError
{
public:
InvalidInput(const char* origin, BString input);
virtual const char* Message() const noexcept override;
virtual BString DebugMessage() const override;
BString input;
};
class BHttpFields::FieldName
{
public:
// Comparison
bool operator==(const BString& other) const noexcept;
bool operator==(const std::string_view& other) const noexcept;
bool operator==(const FieldName& other) const noexcept;
// Conversion
operator std::string_view() const;
private:
friend class BHttpFields;
FieldName() noexcept;
FieldName(const std::string_view& name) noexcept;
FieldName(const FieldName& other) noexcept;
FieldName(FieldName&&) noexcept;
FieldName& operator=(const FieldName& other) noexcept;
FieldName& operator=(FieldName&&) noexcept;
std::string_view fName;
};
class BHttpFields::Field
{
public:
// Constructors
Field() noexcept;
Field(const std::string_view& name, const std::string_view& value);
Field(BString& field);
Field(const Field& other);
Field(Field&&) noexcept;
// Assignment
Field& operator=(const Field& other);
Field& operator=(Field&& other) noexcept;
// Access Operators
const FieldName& Name() const noexcept;
std::string_view Value() const noexcept;
std::string_view RawField() const noexcept;
bool IsEmpty() const noexcept;
private:
friend class BHttpFields;
Field(BString&& rawField);
std::optional<BString> fRawField;
FieldName fName;
std::string_view fValue;
};
} // namespace Network } // namespace Network
} // namespace BPrivate } // namespace BPrivate
#endif // _B_HTTP_FIELDS_H_ #endif // _B_HTTP_FIELDS_H_
+61 -61
View File
@@ -29,30 +29,14 @@ class HttpBuffer;
class HttpSerializer; class HttpSerializer;
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
enum Verb { enum Verb { Get, Head, Post, Put, Delete, Connect, Options, Trace };
Get,
Head,
Post,
Put,
Delete,
Connect,
Options,
Trace
};
// Error type when constructing with a custom method // Error type when constructing with a custom method
class InvalidMethod : public BError { class InvalidMethod;
public:
InvalidMethod(const char* origin, BString input);
virtual const char* Message() const noexcept override;
virtual BString DebugMessage() const override;
BString input;
};
// Constructors & Destructor // Constructors & Destructor
BHttpMethod(Verb verb) noexcept; BHttpMethod(Verb verb) noexcept;
@@ -70,78 +54,94 @@ public:
bool operator!=(const Verb& other) const noexcept; bool operator!=(const Verb& other) const noexcept;
// Get the method as a string // Get the method as a string
const std::string_view Method() const noexcept; const std::string_view Method() const noexcept;
private: private:
std::variant<Verb, BString> fMethod; std::variant<Verb, BString> fMethod;
};
class BHttpMethod::InvalidMethod : public BError
{
public:
InvalidMethod(const char* origin, BString input);
virtual const char* Message() const noexcept override;
virtual BString DebugMessage() const override;
BString input;
}; };
struct BHttpAuthentication { struct BHttpAuthentication {
BString username; BString username;
BString password; BString password;
}; };
class BHttpRequest { class BHttpRequest
{
public: public:
// Aggregate parameter types // Aggregate parameter types
struct Body { struct Body;
std::unique_ptr<BDataIO> input;
BString mimeType;
std::optional<off_t> size;
std::optional<off_t> startPosition;
};
// Constructors and Destructor // Constructors and Destructor
BHttpRequest(); BHttpRequest();
BHttpRequest(const BUrl& url); BHttpRequest(const BUrl& url);
BHttpRequest(const BHttpRequest& other) = delete; BHttpRequest(const BHttpRequest& other) = delete;
BHttpRequest(BHttpRequest&& other) noexcept; BHttpRequest(BHttpRequest&& other) noexcept;
~BHttpRequest(); ~BHttpRequest();
// Assignment operators // Assignment operators
BHttpRequest& operator=(const BHttpRequest& other) = delete; BHttpRequest& operator=(const BHttpRequest& other) = delete;
BHttpRequest& operator=(BHttpRequest&&) noexcept; BHttpRequest& operator=(BHttpRequest&&) noexcept;
// Access // Access
bool IsEmpty() const noexcept; bool IsEmpty() const noexcept;
const BHttpAuthentication* Authentication() const noexcept; const BHttpAuthentication* Authentication() const noexcept;
const BHttpFields& Fields() const noexcept; const BHttpFields& Fields() const noexcept;
uint8 MaxRedirections() const noexcept; uint8 MaxRedirections() const noexcept;
const BHttpMethod& Method() const noexcept; const BHttpMethod& Method() const noexcept;
const Body* RequestBody() const noexcept; const Body* RequestBody() const noexcept;
bool StopOnError() const noexcept; bool StopOnError() const noexcept;
bigtime_t Timeout() const noexcept; bigtime_t Timeout() const noexcept;
const BUrl& Url() const noexcept; const BUrl& Url() const noexcept;
// Named Setters // Named Setters
void SetAuthentication(const BHttpAuthentication& authentication); void SetAuthentication(const BHttpAuthentication& authentication);
void SetFields(const BHttpFields& fields); void SetFields(const BHttpFields& fields);
void SetMaxRedirections(uint8 maxRedirections); void SetMaxRedirections(uint8 maxRedirections);
void SetMethod(const BHttpMethod& method); void SetMethod(const BHttpMethod& method);
void SetRequestBody(std::unique_ptr<BDataIO> input, void SetRequestBody(std::unique_ptr<BDataIO> input, BString mimeType,
BString mimeType, std::optional<off_t> size); std::optional<off_t> size);
void SetStopOnError(bool stopOnError); void SetStopOnError(bool stopOnError);
void SetTimeout(bigtime_t timeout); void SetTimeout(bigtime_t timeout);
void SetUrl(const BUrl& url); void SetUrl(const BUrl& url);
// Clearing Options // Clearing Options
void ClearAuthentication() noexcept; void ClearAuthentication() noexcept;
std::unique_ptr<BDataIO> ClearRequestBody() noexcept; std::unique_ptr<BDataIO> ClearRequestBody() noexcept;
// Serialization // Serialization
BString HeaderToString() const; BString HeaderToString() const;
private: private:
friend class BHttpSession; friend class BHttpSession;
friend class HttpSerializer; friend class HttpSerializer;
struct Data; struct Data;
bool RewindBody() noexcept; bool RewindBody() noexcept;
void SerializeHeaderTo(HttpBuffer& buffer) const; void SerializeHeaderTo(HttpBuffer& buffer) const;
std::unique_ptr<Data> fData; std::unique_ptr<Data> fData;
};
struct BHttpRequest::Body {
std::unique_ptr<BDataIO> input;
BString mimeType;
std::optional<off_t> size;
std::optional<off_t> startPosition;
}; };
+28 -30
View File
@@ -22,19 +22,18 @@ class BHttpFields;
struct HttpResultPrivate; struct HttpResultPrivate;
struct BHttpBody struct BHttpBody {
{ std::optional<BString> text;
std::optional<BString> text;
}; };
enum class BHttpStatusClass : int16 { enum class BHttpStatusClass : int16 {
Invalid = 000, Invalid = 000,
Informational = 100, Informational = 100,
Success = 200, Success = 200,
Redirection = 300, Redirection = 300,
ClientError = 400, ClientError = 400,
ServerError = 500 ServerError = 500
}; };
@@ -93,14 +92,13 @@ enum class BHttpStatusCode : int16 {
}; };
struct BHttpStatus struct BHttpStatus {
{ int16 code = 0;
int16 code = 0; BString text;
BString text;
// Helpers // Helpers
BHttpStatusClass StatusClass() const noexcept; BHttpStatusClass StatusClass() const noexcept;
BHttpStatusCode StatusCode() const noexcept; BHttpStatusCode StatusCode() const noexcept;
}; };
@@ -108,32 +106,32 @@ class BHttpResult
{ {
public: public:
// Constructors and destructor // Constructors and destructor
BHttpResult(const BHttpResult& other) = delete; BHttpResult(const BHttpResult& other) = delete;
BHttpResult(BHttpResult&& other) noexcept; BHttpResult(BHttpResult&& other) noexcept;
~BHttpResult(); ~BHttpResult();
// Assignment operators // Assignment operators
BHttpResult& operator=(const BHttpResult& other) = delete; BHttpResult& operator=(const BHttpResult& other) = delete;
BHttpResult& operator=(BHttpResult&& other) noexcept; BHttpResult& operator=(BHttpResult&& other) noexcept;
// Blocking Access Functions // Blocking Access Functions
const BHttpStatus& Status() const; const BHttpStatus& Status() const;
const BHttpFields& Fields() const; const BHttpFields& Fields() const;
BHttpBody& Body() const; BHttpBody& Body() const;
// Check if data is available yet // Check if data is available yet
bool HasStatus() const; bool HasStatus() const;
bool HasFields() const; bool HasFields() const;
bool HasBody() const; bool HasBody() const;
bool IsCompleted() const; bool IsCompleted() const;
// Identity // Identity
int32 Identity() const; int32 Identity() const;
private: private:
friend class BHttpSession; friend class BHttpSession;
BHttpResult(std::shared_ptr<HttpResultPrivate> data); BHttpResult(std::shared_ptr<HttpResultPrivate> data);
std::shared_ptr<HttpResultPrivate> fData; std::shared_ptr<HttpResultPrivate> fData;
}; };
+21 -26
View File
@@ -22,53 +22,48 @@ class BHttpRequest;
class BHttpResult; class BHttpResult;
class BHttpSession { class BHttpSession
{
public: public:
// Constructors & Destructor // Constructors & Destructor
BHttpSession(); BHttpSession();
BHttpSession(const BHttpSession&) noexcept; BHttpSession(const BHttpSession&) noexcept;
BHttpSession(BHttpSession&&) noexcept = delete; BHttpSession(BHttpSession&&) noexcept = delete;
~BHttpSession() noexcept; ~BHttpSession() noexcept;
// Assignment operators // Assignment operators
BHttpSession& operator=(const BHttpSession&) noexcept; BHttpSession& operator=(const BHttpSession&) noexcept;
BHttpSession& operator=(BHttpSession&&) noexcept = delete; BHttpSession& operator=(BHttpSession&&) noexcept = delete;
// Requests // Requests
BHttpResult Execute(BHttpRequest&& request, BHttpResult Execute(BHttpRequest&& request, BBorrow<BDataIO> target = nullptr,
BBorrow<BDataIO> target = nullptr, BMessenger observer = BMessenger());
BMessenger observer = BMessenger()); void Cancel(int32 identifier);
void Cancel(int32 identifier); void Cancel(const BHttpResult& request);
void Cancel(const BHttpResult& request);
// Concurrency limits // Concurrency limits
void SetMaxConnectionsPerHost(size_t maxConnections); void SetMaxConnectionsPerHost(size_t maxConnections);
void SetMaxHosts(size_t maxConnections); void SetMaxHosts(size_t maxConnections);
private: private:
struct Redirect; struct Redirect;
class Request; class Request;
class Impl; class Impl;
std::shared_ptr<Impl> fImpl; std::shared_ptr<Impl> fImpl;
}; };
namespace UrlEvent { namespace UrlEvent {
enum { enum { HttpStatus = '_HST', HttpFields = '_HHF', CertificateError = '_CER', HttpRedirect = '_HRE' };
HttpStatus = '_HST',
HttpFields = '_HHF',
CertificateError = '_CER',
HttpRedirect = '_HRE'
};
} }
namespace UrlEventData { namespace UrlEventData {
extern const char* HttpStatusCode; extern const char* HttpStatusCode;
extern const char* SSLCertificate; extern const char* SSLCertificate;
extern const char* SSLMessage; extern const char* SSLMessage;
extern const char* HttpRedirectUrl; extern const char* HttpRedirectUrl;
} } // namespace UrlEventData
} // namespace Network } // namespace Network
+30 -28
View File
@@ -15,52 +15,54 @@ namespace BPrivate {
namespace Network { namespace Network {
enum class BHttpTimeFormat : int8 { enum class BHttpTimeFormat : int8 { RFC1123 = 0, RFC850, AscTime };
RFC1123 = 0,
RFC850,
AscTime
};
class BHttpTime { class BHttpTime
{
public: public:
// Error type // Error type
class InvalidInput : public BError { class InvalidInput;
public:
InvalidInput(const char* origin, BString input);
virtual const char* Message() const noexcept override;
virtual BString DebugMessage() const override;
BString input;
};
// Constructors // Constructors
BHttpTime() noexcept; BHttpTime() noexcept;
BHttpTime(BDateTime date); BHttpTime(BDateTime date);
BHttpTime(const BString& dateString); BHttpTime(const BString& dateString);
// Date modification // Date modification
void SetTo(const BString& string); void SetTo(const BString& string);
void SetTo(BDateTime date); void SetTo(BDateTime date);
// Date Access // Date Access
BDateTime DateTime() const noexcept; BDateTime DateTime() const noexcept;
BHttpTimeFormat DateTimeFormat() const noexcept; BHttpTimeFormat DateTimeFormat() const noexcept;
BString ToString(BHttpTimeFormat outputFormat = BHttpTimeFormat::RFC1123) const; BString ToString(BHttpTimeFormat outputFormat
= BHttpTimeFormat::RFC1123) const;
private: private:
void _Parse(const BString& dateString); void _Parse(const BString& dateString);
BDateTime fDate; BDateTime fDate;
BHttpTimeFormat fDateFormat; BHttpTimeFormat fDateFormat;
};
class BHttpTime::InvalidInput : public BError
{
public:
InvalidInput(const char* origin, BString input);
virtual const char* Message() const noexcept override;
virtual BString DebugMessage() const override;
BString input;
}; };
// Convenience functions // Convenience functions
BDateTime parse_http_time(const BString& string); BDateTime parse_http_time(const BString& string);
BString format_http_time(BDateTime timestamp, BString format_http_time(BDateTime timestamp,
BHttpTimeFormat outputFormat = BHttpTimeFormat::RFC1123); BHttpTimeFormat outputFormat = BHttpTimeFormat::RFC1123);
+54 -61
View File
@@ -18,65 +18,62 @@ namespace Network {
// Standard exceptions // Standard exceptions
class BUnsupportedProtocol : public BError { class BUnsupportedProtocol : public BError
{
public: public:
BUnsupportedProtocol(const char* origin, BUrl url, BUnsupportedProtocol(const char* origin, BUrl url,
BStringList supportedProtocols); BStringList supportedProtocols);
BUnsupportedProtocol(BString origin, BUrl url, BUnsupportedProtocol(BString origin, BUrl url,
BStringList supportedProtocols); BStringList supportedProtocols);
virtual const char* Message() const noexcept override; virtual const char* Message() const noexcept override;
const BUrl& Url() const; const BUrl& Url() const;
const BStringList& SupportedProtocols() const; const BStringList& SupportedProtocols() const;
private: private:
BUrl fUrl; BUrl fUrl;
BStringList fSupportedProtocols; BStringList fSupportedProtocols;
}; };
class BInvalidUrl : public BError { class BInvalidUrl : public BError
{
public: public:
BInvalidUrl(const char* origin, BUrl url); BInvalidUrl(const char* origin, BUrl url);
BInvalidUrl(BString origin, BUrl url); BInvalidUrl(BString origin, BUrl url);
virtual const char* Message() const noexcept override; virtual const char* Message() const noexcept override;
const BUrl& Url() const; const BUrl& Url() const;
private: private:
BUrl fUrl; BUrl fUrl;
}; };
class BNetworkRequestError : public BError { class BNetworkRequestError : public BError
{
public: public:
enum ErrorType { enum ErrorType { HostnameError, NetworkError, ProtocolError, SystemError, Canceled };
HostnameError,
NetworkError,
ProtocolError,
SystemError,
Canceled
};
BNetworkRequestError(const char* origin, ErrorType type, BNetworkRequestError(const char* origin, ErrorType type,
status_t errorCode, const BString& customMessage = BString()); status_t errorCode, const BString& customMessage = BString());
BNetworkRequestError(const char* origin, ErrorType type, BNetworkRequestError(const char* origin, ErrorType type,
const BString& customMessage = BString()); const BString& customMessage = BString());
virtual const char* Message() const noexcept override; virtual const char* Message() const noexcept override;
virtual BString DebugMessage() const override; virtual BString DebugMessage() const override;
ErrorType Type() const noexcept; ErrorType Type() const noexcept;
status_t ErrorCode() const noexcept; status_t ErrorCode() const noexcept;
const char* CustomMessage() const noexcept; const char* CustomMessage() const noexcept;
private: private:
ErrorType fErrorType; ErrorType fErrorType;
status_t fErrorCode = B_OK; status_t fErrorCode = B_OK;
BString fCustomMessage; BString fCustomMessage;
}; };
@@ -84,37 +81,33 @@ BString encode_to_base64(const BString& string);
namespace UrlEvent { namespace UrlEvent {
enum { enum {
HostNameResolved = '_NHR', HostNameResolved = '_NHR',
ConnectionOpened = '_NCO', ConnectionOpened = '_NCO',
UploadProgress = '_NUP', UploadProgress = '_NUP',
ResponseStarted = '_NRS', ResponseStarted = '_NRS',
DownloadProgress = '_NDP', DownloadProgress = '_NDP',
BytesWritten = '_NBW', BytesWritten = '_NBW',
RequestCompleted = '_NRC', RequestCompleted = '_NRC',
DebugMessage = '_NDB' DebugMessage = '_NDB'
}; };
} }
namespace UrlEventData { namespace UrlEventData {
extern const char* Id; extern const char* Id;
extern const char* HostName; extern const char* HostName;
extern const char* NumBytes; extern const char* NumBytes;
extern const char* TotalBytes; extern const char* TotalBytes;
extern const char* Success; extern const char* Success;
extern const char* DebugType; extern const char* DebugType;
extern const char* DebugMessage; extern const char* DebugMessage;
enum { enum { DebugInfo = '_DBI', DebugWarning = '_DBW', DebugError = '_DBE' };
DebugInfo = '_DBI', } // namespace UrlEventData
DebugWarning = '_DBW',
DebugError = '_DBE'
};
}
} } // namespace Network
} } // namespace BPrivate
#endif #endif
+34 -29
View File
@@ -18,16 +18,16 @@ using namespace BPrivate::Network;
BError::BError(const char* origin) BError::BError(const char* origin)
: fOrigin(BString(origin)) :
fOrigin(BString(origin))
{ {
} }
BError::BError(BString origin) BError::BError(BString origin)
: fOrigin(std::move(origin)) :
fOrigin(std::move(origin))
{ {
} }
@@ -40,12 +40,10 @@ BError::BError(const BError& error) = default;
BError::BError(BError&& error) noexcept = default; BError::BError(BError&& error) noexcept = default;
BError& BError& BError::operator=(const BError& error) = default;
BError::operator=(const BError& error) = default;
BError& BError& BError::operator=(BError&& error) noexcept = default;
BError::operator=(BError&& error) noexcept = default;
const char* const char*
@@ -76,18 +74,35 @@ BError::WriteToOutput(BDataIO* output) const
{ {
std::stringstream stream; std::stringstream stream;
WriteToStream(stream); WriteToStream(stream);
ssize_t result ssize_t result = output->Write(stream.str().c_str(), stream.str().length() + 1);
= output->Write(stream.str().c_str(), stream.str().length() + 1);
if (result < 0) if (result < 0)
throw BSystemError("BDataIO::Write()", result); throw BSystemError("BDataIO::Write()", result);
return static_cast<size_t>(result); return static_cast<size_t>(result);
} }
void BError::_ReservedError1() {} void
void BError::_ReservedError2() {} BError::_ReservedError1()
void BError::_ReservedError3() {} {
void BError::_ReservedError4() {} }
void
BError::_ReservedError2()
{
}
void
BError::_ReservedError3()
{
}
void
BError::_ReservedError4()
{
}
/* BRuntimeError */ /* BRuntimeError */
@@ -96,7 +111,6 @@ BRuntimeError::BRuntimeError(const char* origin, const char* message)
BError(origin), BError(origin),
fMessage(BString(message)) fMessage(BString(message))
{ {
} }
@@ -105,7 +119,6 @@ BRuntimeError::BRuntimeError(const char* origin, BString message)
BError(origin), BError(origin),
fMessage(std::move(message)) fMessage(std::move(message))
{ {
} }
@@ -114,7 +127,6 @@ BRuntimeError::BRuntimeError(BString origin, BString message)
BError(std::move(origin)), BError(std::move(origin)),
fMessage(std::move(message)) fMessage(std::move(message))
{ {
} }
@@ -124,12 +136,10 @@ BRuntimeError::BRuntimeError(const BRuntimeError& other) = default;
BRuntimeError::BRuntimeError(BRuntimeError&& other) noexcept = default; BRuntimeError::BRuntimeError(BRuntimeError&& other) noexcept = default;
BRuntimeError& BRuntimeError& BRuntimeError::operator=(const BRuntimeError& other) = default;
BRuntimeError::operator=(const BRuntimeError& other) = default;
BRuntimeError& BRuntimeError& BRuntimeError::operator=(BRuntimeError&& other) noexcept = default;
BRuntimeError::operator=(BRuntimeError&& other) noexcept = default;
const char* const char*
@@ -145,7 +155,6 @@ BSystemError::BSystemError(const char* origin, status_t error)
BError(origin), BError(origin),
fErrorCode(error) fErrorCode(error)
{ {
} }
@@ -154,7 +163,6 @@ BSystemError::BSystemError(BString origin, status_t error)
BError(std::move(origin)), BError(std::move(origin)),
fErrorCode(error) fErrorCode(error)
{ {
} }
@@ -164,12 +172,10 @@ BSystemError::BSystemError(const BSystemError& other) = default;
BSystemError::BSystemError(BSystemError&& other) noexcept = default; BSystemError::BSystemError(BSystemError&& other) noexcept = default;
BSystemError& BSystemError& BSystemError::operator=(const BSystemError& other) = default;
BSystemError::operator=(const BSystemError& other) = default;
BSystemError& BSystemError& BSystemError::operator=(BSystemError&& other) noexcept = default;
BSystemError::operator=(BSystemError&& other) noexcept = default;
const char* const char*
@@ -183,8 +189,7 @@ BString
BSystemError::DebugMessage() const BSystemError::DebugMessage() const
{ {
BString debugMessage; BString debugMessage;
debugMessage << "[" << Origin() << "] " << Message() << " (" << fErrorCode debugMessage << "[" << Origin() << "] " << Message() << " (" << fErrorCode << ")";
<< ")";
return debugMessage; return debugMessage;
} }
@@ -63,8 +63,8 @@ HttpBuffer::ReadFrom(BDataIO* source, std::optional<size_t> maxSize)
fBuffer.resize(currentSize); fBuffer.resize(currentSize);
return bytesRead; return bytesRead;
} else if (bytesRead < 0) { } else if (bytesRead < 0) {
throw BNetworkRequestError("BDataIO::Read()", BNetworkRequestError::NetworkError, throw BNetworkRequestError(
bytesRead); "BDataIO::Read()", BNetworkRequestError::NetworkError, bytesRead);
} }
// Adjust the buffer to the current size // Adjust the buffer to the current size
@@ -84,7 +84,7 @@ HttpBuffer::ReadFrom(BDataIO* source, std::optional<size_t> maxSize)
\returns the actual number of bytes written to the \a func. \returns the actual number of bytes written to the \a func.
*/ */
size_t size_t
HttpBuffer::WriteTo(HttpTransferFunction func , std::optional<size_t> maxSize) HttpBuffer::WriteTo(HttpTransferFunction func, std::optional<size_t> maxSize)
{ {
if (RemainingBytes() == 0) if (RemainingBytes() == 0)
return 0; return 0;
@@ -121,7 +121,8 @@ HttpBuffer::GetNextLine()
if (result == fBuffer.cend()) if (result == fBuffer.cend())
return std::nullopt; return std::nullopt;
BString line(reinterpret_cast<const char*>(std::addressof(*offset)), std::distance(offset, result)); BString line(
reinterpret_cast<const char*>(std::addressof(*offset)), std::distance(offset, result));
fCurrentOffset = std::distance(fBuffer.cbegin(), result) + 2; fCurrentOffset = std::distance(fBuffer.cbegin(), result) + 2;
return line; return line;
} }
@@ -173,8 +174,8 @@ std::string_view
HttpBuffer::Data() const noexcept HttpBuffer::Data() const noexcept
{ {
if (RemainingBytes() > 0) { if (RemainingBytes() > 0) {
return std::string_view(reinterpret_cast<const char*>(fBuffer.data()) + fCurrentOffset, return std::string_view(
RemainingBytes()); reinterpret_cast<const char*>(fBuffer.data()) + fCurrentOffset, RemainingBytes());
} else } else
return std::string_view(); return std::string_view();
} }
+18 -16
View File
@@ -19,33 +19,35 @@ namespace BPrivate {
namespace Network { namespace Network {
using HttpTransferFunction = std::function<size_t (const std::byte*, size_t)>; using HttpTransferFunction = std::function<size_t(const std::byte*, size_t)>;
class HttpBuffer { class HttpBuffer
{
public: public:
HttpBuffer(size_t capacity = 8*1024); HttpBuffer(size_t capacity = 8 * 1024);
ssize_t ReadFrom(BDataIO* source, std::optional<size_t> maxSize = std::nullopt); ssize_t ReadFrom(BDataIO* source,
size_t WriteTo(HttpTransferFunction func, std::optional<size_t> maxSize = std::nullopt);
std::optional<size_t> maxSize = std::nullopt); size_t WriteTo(HttpTransferFunction func,
void WriteExactlyTo(HttpTransferFunction func, std::optional<size_t> maxSize = std::nullopt);
std::optional<size_t> maxSize = std::nullopt); void WriteExactlyTo(HttpTransferFunction func,
std::optional<BString> GetNextLine(); std::optional<size_t> maxSize = std::nullopt);
std::optional<BString> GetNextLine();
size_t RemainingBytes() const noexcept; size_t RemainingBytes() const noexcept;
void Flush() noexcept; void Flush() noexcept;
void Clear() noexcept; void Clear() noexcept;
std::string_view Data() const noexcept; std::string_view Data() const noexcept;
// load data into the buffer // load data into the buffer
HttpBuffer& operator<<(const std::string_view& data); 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;
}; };
+28 -27
View File
@@ -52,12 +52,8 @@ validate_value_string(const std::string_view& string)
static inline bool static inline bool
iequals(const std::string_view& a, const std::string_view& b) iequals(const std::string_view& a, const std::string_view& b)
{ {
return std::equal( return std::equal(a.begin(), a.end(), b.begin(), b.end(),
a.begin(), a.end(), [](char a, char b) { return tolower(a) == tolower(b); });
b.begin(), b.end(),
[](char a, char b) {
return tolower(a) == tolower(b);
});
} }
@@ -79,7 +75,8 @@ trim(std::string_view in)
} }
auto right = in.end() - 1; auto right = in.end() - 1;
for (; right > left && isspace(*right); --right); for (; right > left && isspace(*right); --right)
;
return std::string_view(left, std::distance(left, right) + 1); return std::string_view(left, std::distance(left, right) + 1);
} }
@@ -93,7 +90,6 @@ BHttpFields::InvalidInput::InvalidInput(const char* origin, BString input)
BError(origin), BError(origin),
input(std::move(input)) input(std::move(input))
{ {
} }
@@ -117,16 +113,16 @@ BHttpFields::InvalidInput::DebugMessage() const
BHttpFields::FieldName::FieldName() noexcept BHttpFields::FieldName::FieldName() noexcept
: fName(std::string_view()) :
fName(std::string_view())
{ {
} }
BHttpFields::FieldName::FieldName(const std::string_view& name) noexcept BHttpFields::FieldName::FieldName(const std::string_view& name) noexcept
: fName(name) :
fName(name)
{ {
} }
@@ -144,17 +140,18 @@ BHttpFields::FieldName::FieldName(const FieldName& other) noexcept = default;
longer be used as an entry in a BHttpFields object. longer be used as an entry in a BHttpFields object.
*/ */
BHttpFields::FieldName::FieldName(FieldName&& other) noexcept BHttpFields::FieldName::FieldName(FieldName&& other) noexcept
: fName(std::move(other.fName)) :
fName(std::move(other.fName))
{ {
other.fName = std::string_view(); other.fName = std::string_view();
} }
/*! /*!
\brief Copy assignment; \brief Copy assignment;
*/ */
BHttpFields::FieldName& BHttpFields::FieldName& BHttpFields::FieldName::operator=(
BHttpFields::FieldName::operator=(const BHttpFields::FieldName& other) noexcept = default; const BHttpFields::FieldName& other) noexcept = default;
/*! /*!
@@ -204,9 +201,10 @@ BHttpFields::FieldName::operator std::string_view() const
BHttpFields::Field::Field() noexcept BHttpFields::Field::Field() noexcept
: fName(std::string_view()), fValue(std::string_view()) :
fName(std::string_view()),
fValue(std::string_view())
{ {
} }
@@ -250,7 +248,9 @@ BHttpFields::Field::Field(BString& field)
BHttpFields::Field::Field(const BHttpFields::Field& other) BHttpFields::Field::Field(const BHttpFields::Field& other)
: fName(std::string_view()), fValue(std::string_view()) :
fName(std::string_view()),
fValue(std::string_view())
{ {
if (other.IsEmpty()) { if (other.IsEmpty()) {
fRawField = BString(); fRawField = BString();
@@ -267,7 +267,10 @@ BHttpFields::Field::Field(const BHttpFields::Field& other)
BHttpFields::Field::Field(BHttpFields::Field&& other) noexcept BHttpFields::Field::Field(BHttpFields::Field&& other) noexcept
: fRawField(std::move(other.fRawField)), fName(std::move(other.fName)), fValue(std::move(other.fValue)) :
fRawField(std::move(other.fRawField)),
fName(std::move(other.fName)),
fValue(std::move(other.fValue))
{ {
other.fName.fName = std::string_view(); other.fName.fName = std::string_view();
other.fValue = std::string_view(); other.fValue = std::string_view();
@@ -341,7 +344,6 @@ BHttpFields::Field::IsEmpty() const noexcept
BHttpFields::BHttpFields() BHttpFields::BHttpFields()
{ {
} }
@@ -355,7 +357,8 @@ BHttpFields::BHttpFields(const BHttpFields& other) = default;
BHttpFields::BHttpFields(BHttpFields&& other) BHttpFields::BHttpFields(BHttpFields&& other)
: fFields(std::move(other.fFields)) :
fFields(std::move(other.fFields))
{ {
// Explicitly clear the other list, as the C++ standard does not specify that the other list // Explicitly clear the other list, as the C++ standard does not specify that the other list
// will be empty. // will be empty.
@@ -365,19 +368,17 @@ BHttpFields::BHttpFields(BHttpFields&& other)
BHttpFields::~BHttpFields() noexcept BHttpFields::~BHttpFields() noexcept
{ {
} }
BHttpFields& BHttpFields& BHttpFields::operator=(const BHttpFields& other) = default;
BHttpFields::operator=(const BHttpFields& other) = default;
BHttpFields& BHttpFields&
BHttpFields::operator=(BHttpFields&& other) noexcept BHttpFields::operator=(BHttpFields&& other) noexcept
{ {
fFields = std::move(other.fFields); fFields = std::move(other.fFields);
// Explicitly clear the other list, as the C++ standard does not specify that the other list // Explicitly clear the other list, as the C++ standard does not specify that the other list
// will be empty. // will be empty.
other.fFields.clear(); other.fFields.clear();
@@ -423,7 +424,7 @@ BHttpFields::AddFields(std::initializer_list<Field> fields)
void void
BHttpFields::RemoveField(const std::string_view& name) noexcept BHttpFields::RemoveField(const std::string_view& name) noexcept
{ {
for(auto it = FindField(name); it != end(); it = FindField(name)) { for (auto it = FindField(name); it != end(); it = FindField(name)) {
fFields.erase(it); fFields.erase(it);
} }
} }
+105 -107
View File
@@ -8,8 +8,8 @@
#include "HttpParser.h" #include "HttpParser.h"
#include <string>
#include <stdexcept> #include <stdexcept>
#include <string>
#include <HttpFields.h> #include <HttpFields.h>
#include <NetServicesDefs.h> #include <NetServicesDefs.h>
@@ -103,13 +103,13 @@ HttpParser::ParseFields(HttpBuffer& buffer, BHttpFields& fields)
auto fieldLine = buffer.GetNextLine(); auto fieldLine = buffer.GetNextLine();
while (fieldLine && !fieldLine.value().IsEmpty()){ while (fieldLine && !fieldLine.value().IsEmpty()) {
// Parse next header line // Parse next header line
fields.AddField(fieldLine.value()); fields.AddField(fieldLine.value());
fieldLine = buffer.GetNextLine(); fieldLine = buffer.GetNextLine();
} }
if (!fieldLine || (fieldLine && !fieldLine.value().IsEmpty())){ if (!fieldLine || (fieldLine && !fieldLine.value().IsEmpty())) {
// there is more to parse // there is more to parse
return false; return false;
} }
@@ -117,15 +117,14 @@ HttpParser::ParseFields(HttpBuffer& buffer, BHttpFields& fields)
// Determine the properties for the body // Determine the properties for the body
// RFC 7230 section 3.3.3 has a prioritized list of 7 rules around determining the body: // RFC 7230 section 3.3.3 has a prioritized list of 7 rules around determining the body:
std::optional<off_t> bodyBytesTotal = std::nullopt; std::optional<off_t> bodyBytesTotal = std::nullopt;
if (fBodyType == HttpBodyType::NoContent if (fBodyType == HttpBodyType::NoContent || fStatus.StatusCode() == BHttpStatusCode::NoContent
|| fStatus.StatusCode() == BHttpStatusCode::NoContent
|| fStatus.StatusCode() == BHttpStatusCode::NotModified) { || fStatus.StatusCode() == BHttpStatusCode::NotModified) {
// [1] In case of HEAD (set previously), status codes 1xx (TODO!), status code 204 or 304, no content // [1] In case of HEAD (set previously), status codes 1xx (TODO!), status code 204 or 304,
// [2] NOT SUPPORTED: when doing a CONNECT request, no content // no content [2] NOT SUPPORTED: when doing a CONNECT request, no content
fBodyType = HttpBodyType::NoContent; fBodyType = HttpBodyType::NoContent;
fStreamState = HttpInputStreamState::Done; fStreamState = HttpInputStreamState::Done;
} else if (auto header = fields.FindField("Transfer-Encoding"sv); } else if (auto header = fields.FindField("Transfer-Encoding"sv);
header != fields.end() && header->Value() == "chunked"sv) { header != fields.end() && header->Value() == "chunked"sv) {
// [3] If there is a Transfer-Encoding heading set to 'chunked' // [3] If there is a Transfer-Encoding heading set to 'chunked'
// TODO: support the more advanced rules in the RFC around the meaning of this field // TODO: support the more advanced rules in the RFC around the meaning of this field
fBodyType = HttpBodyType::Chunked; fBodyType = HttpBodyType::Chunked;
@@ -157,8 +156,7 @@ HttpParser::ParseFields(HttpBuffer& buffer, BHttpFields& fields)
fStreamState = HttpInputStreamState::Body; fStreamState = HttpInputStreamState::Body;
} }
} catch (const std::logic_error& e) { } catch (const std::logic_error& e) {
throw BNetworkRequestError(__PRETTY_FUNCTION__, throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError,
BNetworkRequestError::ProtocolError,
"Cannot parse Content-Length field value (logic_error)"); "Cannot parse Content-Length field value (logic_error)");
} }
} else { } else {
@@ -186,9 +184,7 @@ HttpParser::ParseFields(HttpBuffer& buffer, BHttpFields& fields)
// Check Content-Encoding for compression // Check Content-Encoding for compression
auto header = fields.FindField("Content-Encoding"sv); auto header = fields.FindField("Content-Encoding"sv);
if (header != fields.end() if (header != fields.end() && (header->Value() == "gzip" || header->Value() == "deflate")) {
&& (header->Value() == "gzip" || header->Value() == "deflate"))
{
fBodyParser = std::make_unique<HttpBodyDecompression>(std::move(fBodyParser)); fBodyParser = std::make_unique<HttpBodyDecompression>(std::move(fBodyParser));
} }
@@ -295,7 +291,6 @@ HttpBodyParser::TransferredBodySize() const noexcept
*/ */
HttpRawBodyParser::HttpRawBodyParser() HttpRawBodyParser::HttpRawBodyParser()
{ {
} }
@@ -306,7 +301,6 @@ HttpRawBodyParser::HttpRawBodyParser(off_t bodyBytesTotal)
: :
fBodyBytesTotal(bodyBytesTotal) fBodyBytesTotal(bodyBytesTotal)
{ {
} }
@@ -397,95 +391,96 @@ HttpChunkedBodyParser::ParseBody(HttpBuffer& buffer, HttpTransferFunction writeT
size_t totalBytesRead = 0; size_t totalBytesRead = 0;
while (buffer.RemainingBytes() > 0) { while (buffer.RemainingBytes() > 0) {
switch (fChunkParserState) { switch (fChunkParserState) {
case ChunkSize: case ChunkSize:
{ {
// Read the next chunk size from the buffer; if unsuccesful wait for more data // Read the next chunk size from the buffer; if unsuccesful wait for more data
auto chunkSizeString = buffer.GetNextLine(); auto chunkSizeString = buffer.GetNextLine();
if (!chunkSizeString) if (!chunkSizeString)
return {totalBytesRead, totalBytesRead, false}; return {totalBytesRead, totalBytesRead, false};
auto chunkSizeStr = std::string(chunkSizeString.value().String()); auto chunkSizeStr = std::string(chunkSizeString.value().String());
try { try {
size_t pos = 0; size_t pos = 0;
fRemainingChunkSize = std::stoll(chunkSizeStr, &pos, 16); fRemainingChunkSize = std::stoll(chunkSizeStr, &pos, 16);
if (pos < chunkSizeStr.size() && chunkSizeStr[pos] != ';'){ if (pos < chunkSizeStr.size() && chunkSizeStr[pos] != ';') {
throw BNetworkRequestError(__PRETTY_FUNCTION__, throw BNetworkRequestError(
BNetworkRequestError::ProtocolError); __PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError);
}
} catch (const std::invalid_argument&) {
throw BNetworkRequestError(
__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError);
} catch (const std::out_of_range&) {
throw BNetworkRequestError(
__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError);
} }
} catch (const std::invalid_argument&) {
throw BNetworkRequestError(__PRETTY_FUNCTION__, if (fRemainingChunkSize > 0)
BNetworkRequestError::ProtocolError); fChunkParserState = Chunk;
} catch (const std::out_of_range&) { else
throw BNetworkRequestError(__PRETTY_FUNCTION__, fChunkParserState = Trailers;
BNetworkRequestError::ProtocolError); break;
} }
if (fRemainingChunkSize > 0) case Chunk:
fChunkParserState = Chunk; {
else size_t bytesToRead;
fChunkParserState = Trailers; if (fRemainingChunkSize > static_cast<off_t>(buffer.RemainingBytes()))
break; bytesToRead = buffer.RemainingBytes();
} else
bytesToRead = fRemainingChunkSize;
case Chunk: auto bytesRead = buffer.WriteTo(writeToBody, bytesToRead);
{ if (bytesRead != bytesToRead) {
size_t bytesToRead; // Fail if not all expected bytes are written.
if (fRemainingChunkSize > static_cast<off_t>(buffer.RemainingBytes())) throw BNetworkRequestError(__PRETTY_FUNCTION__,
bytesToRead = buffer.RemainingBytes(); BNetworkRequestError::SystemError,
else "Could not write all available body bytes to the target.");
bytesToRead = fRemainingChunkSize; }
auto bytesRead = buffer.WriteTo(writeToBody, bytesToRead); fTransferredBodySize += bytesRead;
if (bytesRead != bytesToRead) { totalBytesRead += bytesRead;
// Fail if not all expected bytes are written. fRemainingChunkSize -= bytesRead;
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::SystemError, if (fRemainingChunkSize == 0)
"Could not write all available body bytes to the target."); fChunkParserState = ChunkEnd;
break;
} }
fTransferredBodySize += bytesRead; case ChunkEnd:
totalBytesRead += bytesRead; {
fRemainingChunkSize -= bytesRead; if (buffer.RemainingBytes() < 2) {
if (fRemainingChunkSize == 0) // not enough data in the buffer to finish the chunk
fChunkParserState = ChunkEnd; return {totalBytesRead, totalBytesRead, false};
break; }
} auto chunkEndString = buffer.GetNextLine();
if (!chunkEndString || chunkEndString.value().Length() != 0) {
// There should have been an empty chunk
throw BNetworkRequestError(
__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError);
}
case ChunkEnd: fChunkParserState = ChunkSize;
{ break;
if (buffer.RemainingBytes() < 2) {
// not enough data in the buffer to finish the chunk
return {totalBytesRead, totalBytesRead, false};
}
auto chunkEndString = buffer.GetNextLine();
if (!chunkEndString || chunkEndString.value().Length() != 0) {
// There should have been an empty chunk
throw BNetworkRequestError(__PRETTY_FUNCTION__,
BNetworkRequestError::ProtocolError);
} }
fChunkParserState = ChunkSize; case Trailers:
break; {
} auto trailerString = buffer.GetNextLine();
if (!trailerString) {
// More data to come
return {totalBytesRead, totalBytesRead, false};
}
case Trailers: if (trailerString.value().Length() > 0) {
{ // Ignore empty trailers for now
auto trailerString = buffer.GetNextLine(); // TODO: review if the API should support trailing headers
if (!trailerString) { } else {
// More data to come fChunkParserState = Complete;
return {totalBytesRead, totalBytesRead, false}; return {totalBytesRead, totalBytesRead, true};
}
break;
} }
if (trailerString.value().Length() > 0) { case Complete:
// Ignore empty trailers for now
// TODO: review if the API should support trailing headers
} else {
fChunkParserState = Complete;
return {totalBytesRead, totalBytesRead, true}; return {totalBytesRead, totalBytesRead, true};
}
break;
}
case Complete:
return {totalBytesRead, totalBytesRead, true};
} }
} }
return {totalBytesRead, totalBytesRead, false}; return {totalBytesRead, totalBytesRead, false};
@@ -501,12 +496,11 @@ HttpBodyDecompression::HttpBodyDecompression(std::unique_ptr<HttpBodyParser> bod
fDecompressorStorage = std::make_unique<BMallocIO>(); fDecompressorStorage = std::make_unique<BMallocIO>();
BDataIO* stream = nullptr; BDataIO* stream = nullptr;
auto result = BZlibCompressionAlgorithm() auto result = BZlibCompressionAlgorithm().CreateDecompressingOutputStream(
.CreateDecompressingOutputStream(fDecompressorStorage.get(), nullptr, stream); fDecompressorStorage.get(), nullptr, stream);
if (result != B_OK) { if (result != B_OK) {
throw BNetworkRequestError( throw BNetworkRequestError("BZlibCompressionAlgorithm().CreateCompressingOutputStream",
"BZlibCompressionAlgorithm().CreateCompressingOutputStream",
BNetworkRequestError::SystemError, result); BNetworkRequestError::SystemError, result);
} }
@@ -537,30 +531,34 @@ BodyParseResult
HttpBodyDecompression::ParseBody(HttpBuffer& buffer, HttpTransferFunction writeToBody, bool readEnd) HttpBodyDecompression::ParseBody(HttpBuffer& buffer, HttpTransferFunction writeToBody, bool readEnd)
{ {
// Get the underlying raw or chunked parser to write data to our decompressionstream // Get the underlying raw or chunked parser to write data to our decompressionstream
auto parseResults = fBodyParser->ParseBody(buffer, [this](const std::byte* buffer, size_t bufferSize){ auto parseResults = fBodyParser->ParseBody(
auto status = fDecompressingStream->WriteExactly(buffer, bufferSize); buffer,
if (status != B_OK) { [this](const std::byte* buffer, size_t bufferSize) {
throw BNetworkRequestError("BDataIO::WriteExactly()", auto status = fDecompressingStream->WriteExactly(buffer, bufferSize);
BNetworkRequestError::SystemError, status); if (status != B_OK) {
} throw BNetworkRequestError(
return bufferSize; "BDataIO::WriteExactly()", BNetworkRequestError::SystemError, status);
}, readEnd); }
return bufferSize;
},
readEnd);
fTransferredBodySize += parseResults.bytesParsed; fTransferredBodySize += parseResults.bytesParsed;
if (readEnd || parseResults.complete) { if (readEnd || parseResults.complete) {
// No more bytes expected so flush out the final bytes // No more bytes expected so flush out the final bytes
if (auto status = fDecompressingStream->Flush(); status != B_OK) { if (auto status = fDecompressingStream->Flush(); status != B_OK) {
throw BNetworkRequestError("BZlibDecompressionStream::Flush()", throw BNetworkRequestError(
BNetworkRequestError::SystemError, status); "BZlibDecompressionStream::Flush()", BNetworkRequestError::SystemError, status);
} }
} }
size_t bytesWritten = 0; size_t bytesWritten = 0;
if (auto bodySize = fDecompressorStorage->Position(); bodySize > 0) { if (auto bodySize = fDecompressorStorage->Position(); bodySize > 0) {
bytesWritten = writeToBody(static_cast<const std::byte*>(fDecompressorStorage->Buffer()), bodySize); bytesWritten
= writeToBody(static_cast<const std::byte*>(fDecompressorStorage->Buffer()), bodySize);
if (static_cast<off_t>(bytesWritten) != bodySize) { if (static_cast<off_t>(bytesWritten) != bodySize) {
throw BNetworkRequestError(__PRETTY_FUNCTION__, throw BNetworkRequestError(
BNetworkRequestError::SystemError, B_PARTIAL_WRITE); __PRETTY_FUNCTION__, BNetworkRequestError::SystemError, B_PARTIAL_WRITE);
} }
fDecompressorStorage->Seek(0, SEEK_SET); fDecompressorStorage->Seek(0, SEEK_SET);
} }
+54 -66
View File
@@ -20,124 +20,112 @@ namespace BPrivate {
namespace Network { namespace Network {
using HttpTransferFunction = std::function<size_t (const std::byte*, size_t)>; using HttpTransferFunction = std::function<size_t(const std::byte*, size_t)>;
enum class HttpInputStreamState { enum class HttpInputStreamState { StatusLine, Fields, Body, Done };
StatusLine,
Fields,
Body,
Done
};
enum class HttpBodyType { enum class HttpBodyType { NoContent, Chunked, FixedSize, VariableSize };
NoContent,
Chunked,
FixedSize,
VariableSize
};
struct BodyParseResult { struct BodyParseResult {
size_t bytesParsed; size_t bytesParsed;
size_t bytesWritten; size_t bytesWritten;
bool complete; bool complete;
}; };
class HttpBodyParser; class HttpBodyParser;
class HttpParser { class HttpParser
{
public: public:
HttpParser() {}; HttpParser(){};
// Explicitly mark request as having no content // Explicitly mark request as having no content
void SetNoContent() noexcept; void SetNoContent() noexcept;
// Parse data from response // Parse data from response
bool ParseStatus(HttpBuffer& buffer, BHttpStatus& status); bool ParseStatus(HttpBuffer& buffer, BHttpStatus& status);
bool ParseFields(HttpBuffer& buffer, BHttpFields& fields); bool ParseFields(HttpBuffer& buffer, BHttpFields& fields);
size_t ParseBody(HttpBuffer& buffer, HttpTransferFunction writeToBody, size_t ParseBody(HttpBuffer& buffer, HttpTransferFunction writeToBody,
bool readEnd); bool readEnd);
HttpInputStreamState State() const noexcept { return fStreamState; } HttpInputStreamState State() const noexcept { return fStreamState; }
// Details on the body status // Details on the body status
bool HasContent() const noexcept; bool HasContent() const noexcept;
std::optional<off_t> BodyBytesTotal() const noexcept; std::optional<off_t> BodyBytesTotal() const noexcept;
off_t BodyBytesTransferred() const noexcept; off_t BodyBytesTransferred() const noexcept;
bool Complete() const noexcept; bool Complete() const noexcept;
private: private:
off_t fHeaderBytes = 0; off_t fHeaderBytes = 0;
BHttpStatus fStatus; BHttpStatus fStatus;
HttpInputStreamState fStreamState = HttpInputStreamState::StatusLine; HttpInputStreamState fStreamState = HttpInputStreamState::StatusLine;
// Body // Body
HttpBodyType fBodyType = HttpBodyType::VariableSize; HttpBodyType fBodyType = HttpBodyType::VariableSize;
std::unique_ptr<HttpBodyParser> fBodyParser = nullptr; std::unique_ptr<HttpBodyParser> fBodyParser = nullptr;
}; };
class HttpBodyParser { class HttpBodyParser
{
public: public:
virtual BodyParseResult ParseBody(HttpBuffer& buffer, virtual BodyParseResult ParseBody(HttpBuffer& buffer,
HttpTransferFunction writeToBody, bool readEnd) = 0; HttpTransferFunction writeToBody, bool readEnd) = 0;
virtual std::optional<off_t> TotalBodySize() const noexcept; virtual std::optional<off_t> TotalBodySize() const noexcept;
off_t TransferredBodySize() const noexcept; off_t TransferredBodySize() const noexcept;
protected: protected:
off_t fTransferredBodySize = 0; off_t fTransferredBodySize = 0;
}; };
class HttpRawBodyParser : public HttpBodyParser { class HttpRawBodyParser : public HttpBodyParser
{
public: public:
HttpRawBodyParser(); HttpRawBodyParser();
HttpRawBodyParser(off_t bodyBytesTotal); HttpRawBodyParser(off_t bodyBytesTotal);
virtual BodyParseResult ParseBody(HttpBuffer& buffer, virtual BodyParseResult ParseBody(HttpBuffer& buffer, HttpTransferFunction writeToBody,
HttpTransferFunction writeToBody, bool readEnd) override; bool readEnd) override;
virtual std::optional<off_t> TotalBodySize() const noexcept override; virtual std::optional<off_t> TotalBodySize() const noexcept override;
private: private:
std::optional<off_t> fBodyBytesTotal; std::optional<off_t> fBodyBytesTotal;
}; };
class HttpChunkedBodyParser : public HttpBodyParser { class HttpChunkedBodyParser : public HttpBodyParser
{
public: public:
virtual BodyParseResult ParseBody(HttpBuffer& buffer, virtual BodyParseResult ParseBody(
HttpTransferFunction writeToBody, bool readEnd) override; HttpBuffer& buffer, HttpTransferFunction writeToBody, bool readEnd) override;
private: private:
enum { enum { ChunkSize, ChunkEnd, Chunk, Trailers, Complete } fChunkParserState = ChunkSize;
ChunkSize, off_t fRemainingChunkSize = 0;
ChunkEnd, bool fLastChunk = false;
Chunk,
Trailers,
Complete
} fChunkParserState = ChunkSize;
off_t fRemainingChunkSize = 0;
bool fLastChunk = false;
}; };
class HttpBodyDecompression : public HttpBodyParser { class HttpBodyDecompression : public HttpBodyParser
{
public: public:
HttpBodyDecompression( HttpBodyDecompression(std::unique_ptr<HttpBodyParser> bodyParser);
std::unique_ptr<HttpBodyParser> bodyParser); virtual BodyParseResult ParseBody(HttpBuffer& buffer, HttpTransferFunction writeToBody,
virtual BodyParseResult ParseBody(HttpBuffer& buffer, bool readEnd) override;
HttpTransferFunction writeToBody, bool readEnd) override;
virtual std::optional<off_t> TotalBodySize() const noexcept; virtual std::optional<off_t> TotalBodySize() const noexcept;
private: private:
std::unique_ptr<HttpBodyParser> fBodyParser; std::unique_ptr<HttpBodyParser> fBodyParser;
std::unique_ptr<BMallocIO> fDecompressorStorage; std::unique_ptr<BMallocIO> fDecompressorStorage;
std::unique_ptr<BDataIO> fDecompressingStream; std::unique_ptr<BDataIO> fDecompressingStream;
}; };
@@ -29,9 +29,9 @@ validate_http_token_string(const std::string_view& string)
{ {
for (auto it = string.cbegin(); it < string.cend(); it++) { for (auto it = string.cbegin(); it < string.cend(); it++) {
if (*it <= 31 || *it == 127 || *it == '(' || *it == ')' || *it == '<' || *it == '>' if (*it <= 31 || *it == 127 || *it == '(' || *it == ')' || *it == '<' || *it == '>'
|| *it == '@' || *it == ',' || *it == ';' || *it == '\\' || *it == '"' || *it == '@' || *it == ',' || *it == ';' || *it == '\\' || *it == '"' || *it == '/'
|| *it == '/' || *it == '[' || *it == ']' || *it == '?' || *it == '=' || *it == '[' || *it == ']' || *it == '?' || *it == '=' || *it == '{' || *it == '}'
|| *it == '{' || *it == '}' || *it == ' ') || *it == ' ')
return false; return false;
} }
return true; return true;
@@ -34,7 +34,6 @@ BHttpMethod::InvalidMethod::InvalidMethod(const char* origin, BString input)
BError(origin), BError(origin),
input(std::move(input)) input(std::move(input))
{ {
} }
@@ -62,17 +61,19 @@ BHttpMethod::InvalidMethod::DebugMessage() const
BHttpMethod::BHttpMethod(Verb verb) noexcept BHttpMethod::BHttpMethod(Verb verb) noexcept
: fMethod(verb) :
fMethod(verb)
{ {
} }
BHttpMethod::BHttpMethod(const std::string_view& verb) BHttpMethod::BHttpMethod(const std::string_view& verb)
: fMethod(BString(verb.data(), verb.length())) :
fMethod(BString(verb.data(), verb.length()))
{ {
if (verb.size() == 0 || !validate_http_token_string(verb)) if (verb.size() == 0 || !validate_http_token_string(verb))
throw BHttpMethod::InvalidMethod(__PRETTY_FUNCTION__, std::move(std::get<BString>(fMethod))); throw BHttpMethod::InvalidMethod(
__PRETTY_FUNCTION__, std::move(std::get<BString>(fMethod)));
} }
@@ -80,7 +81,8 @@ BHttpMethod::BHttpMethod(const BHttpMethod& other) = default;
BHttpMethod::BHttpMethod(BHttpMethod&& other) noexcept BHttpMethod::BHttpMethod(BHttpMethod&& other) noexcept
: fMethod(std::move(other.fMethod)) :
fMethod(std::move(other.fMethod))
{ {
other.fMethod = Get; other.fMethod = Get;
} }
@@ -89,8 +91,7 @@ BHttpMethod::BHttpMethod(BHttpMethod&& other) noexcept
BHttpMethod::~BHttpMethod() = default; BHttpMethod::~BHttpMethod() = default;
BHttpMethod& BHttpMethod& BHttpMethod::operator=(const BHttpMethod& other) = default;
BHttpMethod::operator=(const BHttpMethod& other) = default;
BHttpMethod& BHttpMethod&
@@ -127,25 +128,25 @@ BHttpMethod::Method() const noexcept
{ {
if (std::holds_alternative<Verb>(fMethod)) { if (std::holds_alternative<Verb>(fMethod)) {
switch (std::get<Verb>(fMethod)) { switch (std::get<Verb>(fMethod)) {
case Get: case Get:
return "GET"sv; return "GET"sv;
case Head: case Head:
return "HEAD"sv; return "HEAD"sv;
case Post: case Post:
return "POST"sv; return "POST"sv;
case Put: case Put:
return "PUT"sv; return "PUT"sv;
case Delete: case Delete:
return "DELETE"sv; return "DELETE"sv;
case Connect: case Connect:
return "CONNECT"sv; return "CONNECT"sv;
case Options: case Options:
return "OPTIONS"sv; return "OPTIONS"sv;
case Trace: case Trace:
return "TRACE"sv; return "TRACE"sv;
default: default:
// should never be reached // should never be reached
std::abort(); std::abort();
} }
} else { } else {
const auto& methodString = std::get<BString>(fMethod); const auto& methodString = std::get<BString>(fMethod);
@@ -161,14 +162,14 @@ static const BHttpMethod kDefaultMethod = BHttpMethod::Get;
static const BHttpFields kDefaultOptionalFields = BHttpFields(); static const BHttpFields kDefaultOptionalFields = BHttpFields();
struct BHttpRequest::Data { struct BHttpRequest::Data {
BUrl url = kDefaultUrl; BUrl url = kDefaultUrl;
BHttpMethod method = kDefaultMethod; BHttpMethod method = kDefaultMethod;
uint8 maxRedirections = 8; uint8 maxRedirections = 8;
BHttpFields optionalFields; BHttpFields optionalFields;
std::optional<BHttpAuthentication> authentication; std::optional<BHttpAuthentication> authentication;
bool stopOnError = false; bool stopOnError = false;
bigtime_t timeout = B_INFINITE_TIMEOUT; bigtime_t timeout = B_INFINITE_TIMEOUT;
std::optional<Body> requestBody; std::optional<Body> requestBody;
}; };
@@ -192,14 +193,15 @@ build_basic_http_header(const BString& username, const BString& password)
BHttpRequest::BHttpRequest() BHttpRequest::BHttpRequest()
: fData(std::make_unique<Data>()) :
fData(std::make_unique<Data>())
{ {
} }
BHttpRequest::BHttpRequest(const BUrl& url) BHttpRequest::BHttpRequest(const BUrl& url)
: fData(std::make_unique<Data>()) :
fData(std::make_unique<Data>())
{ {
SetUrl(url); SetUrl(url);
} }
@@ -211,8 +213,7 @@ BHttpRequest::BHttpRequest(BHttpRequest&& other) noexcept = default;
BHttpRequest::~BHttpRequest() = default; BHttpRequest::~BHttpRequest() = default;
BHttpRequest& BHttpRequest& BHttpRequest::operator=(BHttpRequest&&) noexcept = default;
BHttpRequest::operator=(BHttpRequest&&) noexcept = default;
bool bool
@@ -304,13 +305,8 @@ BHttpRequest::SetAuthentication(const BHttpAuthentication& authentication)
} }
static constexpr std::array<std::string_view, 6> fReservedOptionalFieldNames = { static constexpr std::array<std::string_view, 6> fReservedOptionalFieldNames
"Host"sv, = {"Host"sv, "Accept-Encoding"sv, "Connection"sv, "Content-Type"sv, "Content-Length"sv};
"Accept-Encoding"sv,
"Connection"sv,
"Content-Type"sv,
"Content-Length"sv
};
void void
@@ -321,10 +317,11 @@ BHttpRequest::SetFields(const BHttpFields& fields)
for (auto& field: fields) { for (auto& field: fields) {
if (std::find(fReservedOptionalFieldNames.begin(), fReservedOptionalFieldNames.end(), if (std::find(fReservedOptionalFieldNames.begin(), fReservedOptionalFieldNames.end(),
field.Name()) != fReservedOptionalFieldNames.end()) field.Name())
{ != fReservedOptionalFieldNames.end()) {
std::string_view fieldName = field.Name(); std::string_view fieldName = field.Name();
throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(fieldName.data(), fieldName.size())); throw BHttpFields::InvalidInput(
__PRETTY_FUNCTION__, BString(fieldName.data(), fieldName.size()));
} }
} }
fData->optionalFields = fields; fData->optionalFields = fields;
@@ -350,8 +347,8 @@ BHttpRequest::SetMethod(const BHttpMethod& method)
void void
BHttpRequest::SetRequestBody(std::unique_ptr<BDataIO> input, BString mimeType, BHttpRequest::SetRequestBody(
std::optional<off_t> size) std::unique_ptr<BDataIO> input, BString mimeType, std::optional<off_t> size)
{ {
if (input == nullptr) if (input == nullptr)
throw std::invalid_argument("input cannot be null"); throw std::invalid_argument("input cannot be null");
@@ -490,32 +487,33 @@ BHttpRequest::SerializeHeaderTo(HttpBuffer& buffer) const
host << ':' << fData->url.Port(); host << ':' << fData->url.Port();
outputFields.AddFields({ outputFields.AddFields({
{"Host"sv, std::string_view(host.String())}, {"Host"sv, std::string_view(host.String())}, {"Accept-Encoding"sv, "gzip"sv},
{"Accept-Encoding"sv, "gzip"sv}, // Allows the server to compress data using the "gzip" format.
// Allows the server to compress data using the "gzip" format. // "deflate" is not supported, because there are two interpretations
// "deflate" is not supported, because there are two interpretations // of what it means (the RFC and Microsoft products), and we don't
// of what it means (the RFC and Microsoft products), and we don't // want to handle this. Very few websites support only deflate,
// want to handle this. Very few websites support only deflate, // and most of them will send gzip, or at worst, uncompressed data.
// and most of them will send gzip, or at worst, uncompressed data.
{"Connection"sv, "close"sv} {"Connection"sv, "close"sv}
// Let the remote server close the connection after response since // Let the remote server close the connection after response since
// we don't handle multiple request on a single connection // we don't handle multiple request on a single connection
}); });
} }
if (fData->authentication) { if (fData->authentication) {
// This request will add a Basic authorization header // This request will add a Basic authorization header
BString authorization = build_basic_http_header(fData->authentication->username, BString authorization = build_basic_http_header(
fData->authentication->password); fData->authentication->username, fData->authentication->password);
outputFields.AddField("Authorization"sv, std::string_view(authorization.String())); outputFields.AddField("Authorization"sv, std::string_view(authorization.String()));
} }
if (fData->requestBody) { if (fData->requestBody) {
outputFields.AddField("Content-Type"sv, std::string_view(fData->requestBody->mimeType.String())); outputFields.AddField(
"Content-Type"sv, std::string_view(fData->requestBody->mimeType.String()));
if (fData->requestBody->size) if (fData->requestBody->size)
outputFields.AddField("Content-Length"sv, std::to_string(*fData->requestBody->size)); outputFields.AddField("Content-Length"sv, std::to_string(*fData->requestBody->size));
else else
throw BRuntimeError(__PRETTY_FUNCTION__, "Transfer body with unknown content length; chunked transfer not supported"); throw BRuntimeError(__PRETTY_FUNCTION__,
"Transfer body with unknown content length; chunked transfer not supported");
} }
for (const auto& field: outputFields) for (const auto& field: outputFields)
+102 -59
View File
@@ -23,13 +23,18 @@ BHttpStatusClass
BHttpStatus::StatusClass() const noexcept BHttpStatus::StatusClass() const noexcept
{ {
switch (code / 100) { switch (code / 100) {
case 1: return BHttpStatusClass::Informational; case 1:
case 2: return BHttpStatusClass::Success; return BHttpStatusClass::Informational;
case 3: return BHttpStatusClass::Redirection; case 2:
case 4: return BHttpStatusClass::ClientError; return BHttpStatusClass::Success;
case 5: return BHttpStatusClass::ServerError; case 3:
default: return BHttpStatusClass::Redirection;
break; case 4:
return BHttpStatusClass::ClientError;
case 5:
return BHttpStatusClass::ServerError;
default:
break;
} }
return BHttpStatusClass::Invalid; return BHttpStatusClass::Invalid;
} }
@@ -39,59 +44,98 @@ BHttpStatusCode
BHttpStatus::StatusCode() const noexcept BHttpStatus::StatusCode() const noexcept
{ {
switch (static_cast<BHttpStatusCode>(code)) { switch (static_cast<BHttpStatusCode>(code)) {
// 1xx // 1xx
case BHttpStatusCode::Continue: [[fallthrough]]; case BHttpStatusCode::Continue:
case BHttpStatusCode::SwitchingProtocols: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::SwitchingProtocols:
[[fallthrough]];
// 2xx // 2xx
case BHttpStatusCode::Ok: [[fallthrough]]; case BHttpStatusCode::Ok:
case BHttpStatusCode::Created: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::Accepted: [[fallthrough]]; case BHttpStatusCode::Created:
case BHttpStatusCode::NonAuthoritativeInformation: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::NoContent: [[fallthrough]]; case BHttpStatusCode::Accepted:
case BHttpStatusCode::ResetContent: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::PartialContent: [[fallthrough]]; case BHttpStatusCode::NonAuthoritativeInformation:
[[fallthrough]];
case BHttpStatusCode::NoContent:
[[fallthrough]];
case BHttpStatusCode::ResetContent:
[[fallthrough]];
case BHttpStatusCode::PartialContent:
[[fallthrough]];
// 3xx // 3xx
case BHttpStatusCode::MultipleChoice: [[fallthrough]]; case BHttpStatusCode::MultipleChoice:
case BHttpStatusCode::MovedPermanently: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::Found: [[fallthrough]]; case BHttpStatusCode::MovedPermanently:
case BHttpStatusCode::SeeOther: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::NotModified: [[fallthrough]]; case BHttpStatusCode::Found:
case BHttpStatusCode::UseProxy: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::TemporaryRedirect: [[fallthrough]]; case BHttpStatusCode::SeeOther:
case BHttpStatusCode::PermanentRedirect: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::NotModified:
[[fallthrough]];
case BHttpStatusCode::UseProxy:
[[fallthrough]];
case BHttpStatusCode::TemporaryRedirect:
[[fallthrough]];
case BHttpStatusCode::PermanentRedirect:
[[fallthrough]];
// 4xx // 4xx
case BHttpStatusCode::BadRequest: [[fallthrough]]; case BHttpStatusCode::BadRequest:
case BHttpStatusCode::Unauthorized: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::PaymentRequired: [[fallthrough]]; case BHttpStatusCode::Unauthorized:
case BHttpStatusCode::Forbidden: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::NotFound: [[fallthrough]]; case BHttpStatusCode::PaymentRequired:
case BHttpStatusCode::MethodNotAllowed: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::NotAcceptable: [[fallthrough]]; case BHttpStatusCode::Forbidden:
case BHttpStatusCode::ProxyAuthenticationRequired: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::RequestTimeout: [[fallthrough]]; case BHttpStatusCode::NotFound:
case BHttpStatusCode::Conflict: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::Gone: [[fallthrough]]; case BHttpStatusCode::MethodNotAllowed:
case BHttpStatusCode::LengthRequired: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::PreconditionFailed: [[fallthrough]]; case BHttpStatusCode::NotAcceptable:
case BHttpStatusCode::RequestEntityTooLarge: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::RequestUriTooLarge: [[fallthrough]]; case BHttpStatusCode::ProxyAuthenticationRequired:
case BHttpStatusCode::UnsupportedMediaType: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::RequestedRangeNotSatisfiable: [[fallthrough]]; case BHttpStatusCode::RequestTimeout:
case BHttpStatusCode::ExpectationFailed: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::Conflict:
[[fallthrough]];
case BHttpStatusCode::Gone:
[[fallthrough]];
case BHttpStatusCode::LengthRequired:
[[fallthrough]];
case BHttpStatusCode::PreconditionFailed:
[[fallthrough]];
case BHttpStatusCode::RequestEntityTooLarge:
[[fallthrough]];
case BHttpStatusCode::RequestUriTooLarge:
[[fallthrough]];
case BHttpStatusCode::UnsupportedMediaType:
[[fallthrough]];
case BHttpStatusCode::RequestedRangeNotSatisfiable:
[[fallthrough]];
case BHttpStatusCode::ExpectationFailed:
[[fallthrough]];
// 5xx // 5xx
case BHttpStatusCode::InternalServerError: [[fallthrough]]; case BHttpStatusCode::InternalServerError:
case BHttpStatusCode::NotImplemented: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::BadGateway: [[fallthrough]]; case BHttpStatusCode::NotImplemented:
case BHttpStatusCode::ServiceUnavailable: [[fallthrough]]; [[fallthrough]];
case BHttpStatusCode::GatewayTimeout: case BHttpStatusCode::BadGateway:
return static_cast<BHttpStatusCode>(code); [[fallthrough]];
case BHttpStatusCode::ServiceUnavailable:
[[fallthrough]];
case BHttpStatusCode::GatewayTimeout:
return static_cast<BHttpStatusCode>(code);
default: default:
break; break;
} }
return BHttpStatusCode::Unknown; return BHttpStatusCode::Unknown;
@@ -103,9 +147,9 @@ BHttpStatus::StatusCode() const noexcept
/*private*/ /*private*/
BHttpResult::BHttpResult(std::shared_ptr<HttpResultPrivate> data) BHttpResult::BHttpResult(std::shared_ptr<HttpResultPrivate> data)
: fData(data) :
fData(data)
{ {
} }
@@ -119,8 +163,7 @@ BHttpResult::~BHttpResult()
} }
BHttpResult& BHttpResult& BHttpResult::operator=(BHttpResult&& other) noexcept = default;
BHttpResult::operator=(BHttpResult&& other) noexcept = default;
const BHttpStatus& const BHttpStatus&
@@ -16,7 +16,7 @@
#include <DataIO.h> #include <DataIO.h>
#include <ExclusiveBorrow.h> #include <ExclusiveBorrow.h>
#include <OS.h> #include <OS.h>
#include <String.h> #include <String.h>
@@ -26,46 +26,40 @@ namespace Network {
struct HttpResultPrivate { struct HttpResultPrivate {
// Read-only properties (multi-thread safe) // Read-only properties (multi-thread safe)
const int32 id; const int32 id;
// Locking and atomic variables // Locking and atomic variables
sem_id data_wait; enum { kNoData = 0, kStatusReady, kHeadersReady, kBodyReady, kError };
enum { int32 requestStatus = kNoData;
kNoData = 0, int32 canCancel = 0;
kStatusReady, sem_id data_wait;
kHeadersReady,
kBodyReady,
kError
};
int32 requestStatus = kNoData;
int32 canCancel = 0;
// Data // Data
std::optional<BHttpStatus> status; std::optional<BHttpStatus> status;
std::optional<BHttpFields> fields; std::optional<BHttpFields> fields;
std::optional<BHttpBody> body; std::optional<BHttpBody> body;
std::optional<std::exception_ptr> error; std::optional<std::exception_ptr> error;
// Interim body storage (used while the request is running) // Interim body storage (used while the request is running)
BString bodyString; BString bodyString;
BBorrow<BDataIO> bodyTarget; BBorrow<BDataIO> bodyTarget;
// Utility functions // Utility functions
HttpResultPrivate(int32 identifier); HttpResultPrivate(int32 identifier);
int32 GetStatusAtomic(); int32 GetStatusAtomic();
bool CanCancel(); bool CanCancel();
void SetCancel(); void SetCancel();
void SetError(std::exception_ptr e); void SetError(std::exception_ptr e);
void SetStatus(BHttpStatus&& s); void SetStatus(BHttpStatus&& s);
void SetFields(BHttpFields&& f); void SetFields(BHttpFields&& f);
void SetBody(); void SetBody();
size_t WriteToBody(const void* buffer, size_t size); size_t WriteToBody(const void* buffer, size_t size);
}; };
inline inline HttpResultPrivate::HttpResultPrivate(int32 identifier)
HttpResultPrivate::HttpResultPrivate(int32 identifier) :
: id(identifier) id(identifier)
{ {
std::string name = "httpresult:" + std::to_string(identifier); std::string name = "httpresult:" + std::to_string(identifier);
data_wait = create_sem(1, name.c_str()); data_wait = create_sem(1, name.c_str());
@@ -63,8 +63,9 @@ HttpSerializer::Serialize(HttpBuffer& buffer, BDataIO* target)
fState = HttpSerializerState::Done; fState = HttpSerializerState::Done;
return 0; return 0;
} else if (_IsChunked()) } else if (_IsChunked())
//fState = HttpSerializerState::ChunkHeader; // fState = HttpSerializerState::ChunkHeader;
throw BRuntimeError(__PRETTY_FUNCTION__, "Chunked serialization not implemented"); throw BRuntimeError(
__PRETTY_FUNCTION__, "Chunked serialization not implemented");
else else
fState = HttpSerializerState::Body; fState = HttpSerializerState::Body;
break; break;
@@ -75,7 +76,8 @@ HttpSerializer::Serialize(HttpBuffer& buffer, BDataIO* target)
bodyBytesWritten += bytesWritten; bodyBytesWritten += bytesWritten;
fTransferredBodySize += bytesWritten; fTransferredBodySize += bytesWritten;
if (buffer.RemainingBytes() > 0) { if (buffer.RemainingBytes() > 0) {
// did not manage to write all the bytes in the buffer; continue in the next round // did not manage to write all the bytes in the buffer; continue in the next
// round
finishing = true; finishing = true;
break; break;
} }
@@ -115,15 +117,15 @@ size_t
HttpSerializer::_WriteToTarget(HttpBuffer& buffer, BDataIO* target) const HttpSerializer::_WriteToTarget(HttpBuffer& buffer, BDataIO* target) const
{ {
size_t bytesWritten = 0; size_t bytesWritten = 0;
buffer.WriteTo([target, &bytesWritten](const std::byte* buffer, size_t size){ buffer.WriteTo([target, &bytesWritten](const std::byte* buffer, size_t size) {
ssize_t result = B_INTERRUPTED; ssize_t result = B_INTERRUPTED;
while (result == B_INTERRUPTED) { while (result == B_INTERRUPTED) {
result = target->Write(buffer, size); result = target->Write(buffer, size);
} }
if (result <= 0 && result != B_WOULD_BLOCK) { if (result <= 0 && result != B_WOULD_BLOCK) {
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::NetworkError, throw BNetworkRequestError(
result); __PRETTY_FUNCTION__, BNetworkRequestError::NetworkError, result);
} else if (result > 0) { } else if (result > 0) {
bytesWritten += result; bytesWritten += result;
return size_t(result); return size_t(result);
@@ -19,43 +19,66 @@ namespace Network {
class BHttpRequest; class BHttpRequest;
class HttpBuffer; class HttpBuffer;
using HttpTransferFunction = std::function<size_t (const std::byte*, size_t)>; using HttpTransferFunction = std::function<size_t(const std::byte*, size_t)>;
enum class HttpSerializerState { enum class HttpSerializerState { Uninitialized, Header, ChunkHeader, Body, Done };
Uninitialized,
Header,
ChunkHeader,
Body,
Done
};
class HttpSerializer { class HttpSerializer
{
public: public:
HttpSerializer() {}; HttpSerializer(){};
void SetTo(HttpBuffer& buffer, const BHttpRequest& request); void SetTo(HttpBuffer& buffer, const BHttpRequest& request);
bool IsInitialized() const noexcept { return fState != HttpSerializerState::Uninitialized; } bool IsInitialized() const noexcept;
size_t Serialize(HttpBuffer& buffer, BDataIO* target); size_t Serialize(HttpBuffer& buffer, BDataIO* target);
std::optional<off_t> BodyBytesTotal() const noexcept { return fBodySize; }; std::optional<off_t> BodyBytesTotal() const noexcept;
off_t BodyBytesTransferred() const noexcept { return fTransferredBodySize; }; off_t BodyBytesTransferred() const noexcept;
bool Complete() const noexcept { return fState == HttpSerializerState::Done; }; bool Complete() const noexcept;
private: private:
bool _IsChunked() const noexcept; bool _IsChunked() const noexcept;
size_t _WriteToTarget(HttpBuffer& buffer, BDataIO* target) const; size_t _WriteToTarget(HttpBuffer& buffer, BDataIO* target) const;
private: private:
HttpSerializerState fState = HttpSerializerState::Uninitialized; HttpSerializerState fState = HttpSerializerState::Uninitialized;
BDataIO* fBody = nullptr; BDataIO* fBody = nullptr;
off_t fTransferredBodySize = 0; off_t fTransferredBodySize = 0;
std::optional<off_t> fBodySize; std::optional<off_t> fBodySize;
}; };
inline bool
HttpSerializer::IsInitialized() const noexcept
{
return fState != HttpSerializerState::Uninitialized;
}
inline std::optional<off_t>
HttpSerializer::BodyBytesTotal() const noexcept
{
return fBodySize;
}
inline off_t
HttpSerializer::BodyBytesTransferred() const noexcept
{
return fTransferredBodySize;
}
inline bool
HttpSerializer::Complete() const noexcept
{
return fState == HttpSerializerState::Done;
}
} // namespace Network } // namespace Network
} // namespace BPrivate } // namespace BPrivate
+278 -293
View File
@@ -33,8 +33,8 @@
#include "HttpBuffer.h" #include "HttpBuffer.h"
#include "HttpParser.h" #include "HttpParser.h"
#include "HttpSerializer.h"
#include "HttpResultPrivate.h" #include "HttpResultPrivate.h"
#include "HttpSerializer.h"
#include "NetServicesPrivate.h" #include "NetServicesPrivate.h"
using namespace std::literals; using namespace std::literals;
@@ -51,140 +51,128 @@ static constexpr ssize_t kMaxHeaderLineSize = 64 * 1024;
struct CounterDeleter { struct CounterDeleter {
void operator()(int32* counter) const noexcept void operator()(int32* counter) const noexcept { atomic_add(counter, -1); }
{
atomic_add(counter, -1);
}
}; };
class BHttpSession::Request { class BHttpSession::Request
{
public: public:
Request(BHttpRequest&& request, Request(BHttpRequest&& request, BBorrow<BDataIO> target, BMessenger observer);
BBorrow<BDataIO> target,
BMessenger observer);
Request(Request& original, const Redirect& redirect); Request(Request& original, const Redirect& redirect);
// States // States
enum RequestState { enum RequestState { InitialState, Connected, RequestSent, ContentReceived };
InitialState, RequestState State() const noexcept { return fRequestStatus; }
Connected,
RequestSent,
ContentReceived
};
RequestState State() const noexcept { return fRequestStatus; }
// Result Helpers // Result Helpers
std::shared_ptr<HttpResultPrivate> std::shared_ptr<HttpResultPrivate> Result() { return fResult; }
Result() { return fResult; } void SetError(std::exception_ptr e);
void SetError(std::exception_ptr e);
// Helpers for maintaining the connection count // Helpers for maintaining the connection count
std::pair<BString, int> GetHost() const; std::pair<BString, int> GetHost() const;
void SetCounter(int32* counter) noexcept; void SetCounter(int32* counter) noexcept;
// Operational methods // Operational methods
void ResolveHostName(); void ResolveHostName();
void OpenConnection(); void OpenConnection();
void TransferRequest(); void TransferRequest();
bool ReceiveResult(); bool ReceiveResult();
void Disconnect() noexcept; void Disconnect() noexcept;
// Object information // Object information
int Socket() const noexcept { return fSocket->Socket(); } int Socket() const noexcept { return fSocket->Socket(); }
int32 Id() const noexcept { return fResult->id; } int32 Id() const noexcept { return fResult->id; }
bool CanCancel() const noexcept { return fResult->CanCancel(); } bool CanCancel() const noexcept { return fResult->CanCancel(); }
// Message helper // Message helper
void SendMessage(uint32 what, void SendMessage(uint32 what, std::function<void(BMessage&)> dataFunc = nullptr) const;
std::function<void (BMessage&)> dataFunc = nullptr) const;
private: private:
BHttpRequest fRequest; BHttpRequest fRequest;
// Request state/events // Request state/events
RequestState fRequestStatus = InitialState; RequestState fRequestStatus = InitialState;
// Communication // Communication
BMessenger fObserver; BMessenger fObserver;
std::shared_ptr<HttpResultPrivate> fResult; std::shared_ptr<HttpResultPrivate> fResult;
// Connection // Connection
BNetworkAddress fRemoteAddress; BNetworkAddress fRemoteAddress;
std::unique_ptr<BSocket> fSocket; std::unique_ptr<BSocket> fSocket;
// Sending and receiving // Sending and receiving
HttpBuffer fBuffer; HttpBuffer fBuffer;
HttpSerializer fSerializer; HttpSerializer fSerializer;
HttpParser fParser; HttpParser fParser;
// Receive state // Receive state
BHttpStatus fStatus; BHttpStatus fStatus;
BHttpFields fFields; BHttpFields fFields;
// Redirection // Redirection
bool fMightRedirect = false; bool fMightRedirect = false;
int8 fRemainingRedirects; int8 fRemainingRedirects;
// Connection counter // Connection counter
std::unique_ptr<int32, CounterDeleter> std::unique_ptr<int32, CounterDeleter> fConnectionCounter;
fConnectionCounter;
}; };
class BHttpSession::Impl { class BHttpSession::Impl
{
public: public:
Impl(); Impl();
~Impl() noexcept; ~Impl() noexcept;
BHttpResult Execute(BHttpRequest&& request, BHttpResult Execute(BHttpRequest&& request, BBorrow<BDataIO> target, BMessenger observer);
BBorrow<BDataIO> target, void Cancel(int32 identifier);
BMessenger observer); void SetMaxConnectionsPerHost(size_t maxConnections);
void Cancel(int32 identifier); void SetMaxHosts(size_t maxConnections);
void SetMaxConnectionsPerHost(size_t maxConnections);
void SetMaxHosts(size_t maxConnections);
private: private:
// Thread functions // Thread functions
static status_t ControlThreadFunc(void* arg); static status_t ControlThreadFunc(void* arg);
static status_t DataThreadFunc(void* arg); static status_t DataThreadFunc(void* arg);
// Helper functions // Helper functions
std::vector<BHttpSession::Request> GetRequestsForControlThread(); std::vector<BHttpSession::Request> GetRequestsForControlThread();
private: private:
// constants (can be accessed unlocked) // constants (can be accessed unlocked)
const sem_id fControlQueueSem; const sem_id fControlQueueSem;
const sem_id fDataQueueSem; const sem_id fDataQueueSem;
const thread_id fControlThread; const thread_id fControlThread;
const thread_id fDataThread; const thread_id fDataThread;
// locking mechanism // locking mechanism
BLocker fLock; BLocker fLock;
std::atomic<bool> fQuitting = false; std::atomic<bool> fQuitting = false;
// queues & shared data // queues & shared data
std::list<BHttpSession::Request> fControlQueue; std::list<BHttpSession::Request> fControlQueue;
std::deque<BHttpSession::Request> fDataQueue; std::deque<BHttpSession::Request> fDataQueue;
std::vector<int32> fCancelList; std::vector<int32> fCancelList;
// data owned by the controlThread // data owned by the controlThread
using Host = std::pair<BString, int>; using Host = std::pair<BString, int>;
std::map<Host, int32> fConnectionCount; std::map<Host, int32> fConnectionCount;
// data that can only be accessed atomically // data that can only be accessed atomically
std::atomic<size_t> fMaxConnectionsPerHost = 2; std::atomic<size_t> fMaxConnectionsPerHost = 2;
std::atomic<size_t> fMaxHosts = 10; std::atomic<size_t> fMaxHosts = 10;
// data owned by the dataThread // data owned by the dataThread
std::map<int,BHttpSession::Request> connectionMap; std::map<int, BHttpSession::Request> connectionMap;
std::vector<object_wait_info> objectList; std::vector<object_wait_info> objectList;
}; };
struct BHttpSession::Redirect { struct BHttpSession::Redirect {
BUrl url; BUrl url;
bool redirectToGet; bool redirectToGet;
}; };
@@ -229,8 +217,7 @@ BHttpSession::Impl::~Impl() noexcept
BHttpResult BHttpResult
BHttpSession::Impl::Execute(BHttpRequest&& request, BBorrow<BDataIO> target, BHttpSession::Impl::Execute(BHttpRequest&& request, BBorrow<BDataIO> target, BMessenger observer)
BMessenger observer)
{ {
auto wRequest = Request(std::move(request), std::move(target), observer); auto wRequest = Request(std::move(request), std::move(target), observer);
@@ -247,7 +234,7 @@ BHttpSession::Impl::Cancel(int32 identifier)
{ {
auto lock = AutoLocker<BLocker>(fLock); auto lock = AutoLocker<BLocker>(fLock);
// Check if the item is on the control queue // Check if the item is on the control queue
fControlQueue.remove_if([&identifier](auto& request){ fControlQueue.remove_if([&identifier](auto& request) {
if (request.Id() == identifier) { if (request.Id() == identifier) {
try { try {
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::Canceled); throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::Canceled);
@@ -269,8 +256,8 @@ void
BHttpSession::Impl::SetMaxConnectionsPerHost(size_t maxConnections) BHttpSession::Impl::SetMaxConnectionsPerHost(size_t maxConnections)
{ {
if (maxConnections <= 0 || maxConnections >= INT32_MAX) { if (maxConnections <= 0 || maxConnections >= INT32_MAX) {
throw BRuntimeError(__PRETTY_FUNCTION__, throw BRuntimeError(
"MaxConnectionsPerHost must be between 1 and INT32_MAX"); __PRETTY_FUNCTION__, "MaxConnectionsPerHost must be between 1 and INT32_MAX");
} }
fMaxConnectionsPerHost.store(maxConnections, std::memory_order_relaxed); fMaxConnectionsPerHost.store(maxConnections, std::memory_order_relaxed);
} }
@@ -347,8 +334,8 @@ BHttpSession::Impl::ControlThreadFunc(void* arg)
} }
} }
} else { } else {
throw BRuntimeError(__PRETTY_FUNCTION__, throw BRuntimeError(
"Unknown reason that the controlQueueSem is deleted"); __PRETTY_FUNCTION__, "Unknown reason that the controlQueueSem is deleted");
} }
// Cleanup: wait for data thread // Cleanup: wait for data thread
@@ -365,12 +352,12 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
BHttpSession::Impl* data = static_cast<BHttpSession::Impl*>(arg); BHttpSession::Impl* data = static_cast<BHttpSession::Impl*>(arg);
// initial initialization of wait list // initial initialization of wait list
data->objectList.push_back(object_wait_info{data->fDataQueueSem, data->objectList.push_back(
B_OBJECT_TYPE_SEMAPHORE, B_EVENT_ACQUIRE_SEMAPHORE}); object_wait_info{data->fDataQueueSem, B_OBJECT_TYPE_SEMAPHORE, B_EVENT_ACQUIRE_SEMAPHORE});
while (true) { while (true) {
if (auto status = wait_for_objects(data->objectList.data(), data->objectList.size()); if (auto status = wait_for_objects(data->objectList.data(), data->objectList.size());
status == B_INTERRUPTED) status == B_INTERRUPTED)
continue; continue;
else if (status < 0) { else if (status < 0) {
// Something went inexplicably wrong // Something went inexplicably wrong
@@ -399,9 +386,8 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
data->connectionMap.insert(std::make_pair(socket, std::move(request))); data->connectionMap.insert(std::make_pair(socket, std::move(request)));
// Add to objectList // Add to objectList
data->objectList.push_back(object_wait_info{socket, data->objectList.push_back(
B_OBJECT_TYPE_FD, B_EVENT_WRITE object_wait_info{socket, B_OBJECT_TYPE_FD, B_EVENT_WRITE});
});
} }
for (auto id: data->fCancelList) { for (auto id: data->fCancelList) {
@@ -410,7 +396,8 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
// Also: the first item in the waitlist is always the semaphore // Also: the first item in the waitlist is always the semaphore
// so the fun starts at offset 1. // so the fun starts at offset 1.
size_t offset = 0; size_t offset = 0;
for (auto it = data->connectionMap.cbegin(); it != data->connectionMap.cend(); it++) { for (auto it = data->connectionMap.cbegin(); it != data->connectionMap.cend();
it++) {
offset++; offset++;
if (it->second.Id() == id) { if (it->second.Id() == id) {
data->objectList[offset].events = EVENT_CANCELLED; data->objectList[offset].events = EVENT_CANCELLED;
@@ -427,7 +414,7 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
// Process all objects that are ready // Process all objects that are ready
bool resizeObjectList = false; bool resizeObjectList = false;
for(auto& item: data->objectList) { for (auto& item: data->objectList) {
if (item.type != B_OBJECT_TYPE_FD) if (item.type != B_OBJECT_TYPE_FD)
continue; continue;
if ((item.events & B_EVENT_WRITE) == B_EVENT_WRITE) { if ((item.events & B_EVENT_WRITE) == B_EVENT_WRITE) {
@@ -480,7 +467,8 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
} else if ((item.events & B_EVENT_DISCONNECTED) == B_EVENT_DISCONNECTED) { } else if ((item.events & B_EVENT_DISCONNECTED) == B_EVENT_DISCONNECTED) {
auto& request = data->connectionMap.find(item.object)->second; auto& request = data->connectionMap.find(item.object)->second;
try { try {
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::NetworkError); throw BNetworkRequestError(
__PRETTY_FUNCTION__, BNetworkRequestError::NetworkError);
} catch (...) { } catch (...) {
request.SetError(std::current_exception()); request.SetError(std::current_exception());
} }
@@ -504,12 +492,12 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
} else { } else {
// Likely to be B_EVENT_INVALID. This should not happen // Likely to be B_EVENT_INVALID. This should not happen
auto& request = data->connectionMap.find(item.object)->second; auto& request = data->connectionMap.find(item.object)->second;
request.SendMessage(UrlEvent::DebugMessage, [](BMessage& msg){ request.SendMessage(UrlEvent::DebugMessage, [](BMessage& msg) {
msg.AddUInt32(UrlEventData::DebugType, UrlEventData::DebugError); msg.AddUInt32(UrlEventData::DebugType, UrlEventData::DebugError);
msg.AddString(UrlEventData::DebugMessage, "Unexpected event; socket deleted?"); msg.AddString(UrlEventData::DebugMessage, "Unexpected event; socket deleted?");
}); });
throw BRuntimeError(__PRETTY_FUNCTION__, throw BRuntimeError(
"Socket was deleted at an unexpected time"); __PRETTY_FUNCTION__, "Socket was deleted at an unexpected time");
} }
} }
@@ -539,10 +527,9 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
} catch (...) { } catch (...) {
it->second.SetError(std::current_exception()); it->second.SetError(std::current_exception());
} }
} }
} else { } else {
throw BRuntimeError(__PRETTY_FUNCTION__, throw BRuntimeError(__PRETTY_FUNCTION__, "Unknown reason that the dataQueueSem is deleted");
"Unknown reason that the dataQueueSem is deleted");
} }
return B_OK; return B_OK;
@@ -562,7 +549,7 @@ BHttpSession::Impl::GetRequestsForControlThread()
// Clean up connection list if it is at the max number of hosts // Clean up connection list if it is at the max number of hosts
if (fConnectionCount.size() >= fMaxHosts.load()) { if (fConnectionCount.size() >= fMaxHosts.load()) {
for (auto it = fConnectionCount.begin(); it != fConnectionCount.end(); ) { for (auto it = fConnectionCount.begin(); it != fConnectionCount.end();) {
if (atomic_get(std::addressof(it->second)) == 0) { if (atomic_get(std::addressof(it->second)) == 0) {
it = fConnectionCount.erase(it); it = fConnectionCount.erase(it);
} else { } else {
@@ -573,13 +560,13 @@ BHttpSession::Impl::GetRequestsForControlThread()
// Process the list of pending requests and review if they can be started. // Process the list of pending requests and review if they can be started.
auto lock = AutoLocker<BLocker>(fLock); auto lock = AutoLocker<BLocker>(fLock);
fControlQueue.remove_if([this, &requests](auto& request){ fControlQueue.remove_if([this, &requests](auto& request) {
auto host = request.GetHost(); auto host = request.GetHost();
auto it = fConnectionCount.find(host); auto it = fConnectionCount.find(host);
if (it != fConnectionCount.end()) { if (it != fConnectionCount.end()) {
if (static_cast<size_t>(atomic_get(std::addressof(it->second))) if (static_cast<size_t>(atomic_get(std::addressof(it->second)))
>= fMaxConnectionsPerHost.load(std::memory_order_relaxed)) { >= fMaxConnectionsPerHost.load(std::memory_order_relaxed)) {
request.SendMessage(UrlEvent::DebugMessage, [](BMessage& msg){ request.SendMessage(UrlEvent::DebugMessage, [](BMessage& msg) {
msg.AddUInt32(UrlEventData::DebugType, UrlEventData::DebugWarning); msg.AddUInt32(UrlEventData::DebugType, UrlEventData::DebugWarning);
msg.AddString(UrlEventData::DebugMessage, msg.AddString(UrlEventData::DebugMessage,
"Request is queued: too many active connections for host"); "Request is queued: too many active connections for host");
@@ -591,17 +578,16 @@ BHttpSession::Impl::GetRequestsForControlThread()
} }
} else { } else {
if (fConnectionCount.size() == fMaxHosts.load()) { if (fConnectionCount.size() == fMaxHosts.load()) {
request.SendMessage(UrlEvent::DebugMessage, [](BMessage& msg){ request.SendMessage(UrlEvent::DebugMessage, [](BMessage& msg) {
msg.AddUInt32(UrlEventData::DebugType, UrlEventData::DebugWarning); msg.AddUInt32(UrlEventData::DebugType, UrlEventData::DebugWarning);
msg.AddString(UrlEventData::DebugMessage, msg.AddString(UrlEventData::DebugMessage,
"Request is queued: maximum number of concurrent hosts"); "Request is queued: maximum number of concurrent hosts");
}); });
return false; return false;
} }
auto[newIt, success] = fConnectionCount.insert({host, 1}); auto [newIt, success] = fConnectionCount.insert({host, 1});
if (!success) { if (!success) {
throw BRuntimeError(__PRETTY_FUNCTION__, throw BRuntimeError(__PRETTY_FUNCTION__, "Cannot insert into fConnectionCount");
"Cannot insert into fConnectionCount");
} }
request.SetCounter(std::addressof(newIt->second)); request.SetCounter(std::addressof(newIt->second));
} }
@@ -627,8 +613,7 @@ BHttpSession::~BHttpSession() = default;
BHttpSession::BHttpSession(const BHttpSession&) noexcept = default; BHttpSession::BHttpSession(const BHttpSession&) noexcept = default;
BHttpSession& BHttpSession& BHttpSession::operator=(const BHttpSession&) noexcept = default;
BHttpSession::operator=(const BHttpSession&) noexcept = default;
BHttpResult BHttpResult
@@ -667,9 +652,10 @@ BHttpSession::SetMaxHosts(size_t maxConnections)
// #pragma mark -- BHttpSession::Request (helpers) // #pragma mark -- BHttpSession::Request (helpers)
BHttpSession::Request::Request(BHttpRequest&& request, BBorrow<BDataIO> target, BHttpSession::Request::Request(BHttpRequest&& request, BBorrow<BDataIO> target, BMessenger observer)
BMessenger observer) :
: fRequest(std::move(request)), fObserver(observer) fRequest(std::move(request)),
fObserver(observer)
{ {
auto identifier = get_netservices_request_identifier(); auto identifier = get_netservices_request_identifier();
@@ -690,8 +676,10 @@ BHttpSession::Request::Request(BHttpRequest&& request, BBorrow<BDataIO> target,
BHttpSession::Request::Request(Request& original, const BHttpSession::Redirect& redirect) BHttpSession::Request::Request(Request& original, const BHttpSession::Redirect& redirect)
: fRequest(std::move(original.fRequest)), fObserver(original.fObserver), :
fResult(original.fResult) fRequest(std::move(original.fRequest)),
fObserver(original.fObserver),
fResult(original.fResult)
{ {
// update the original request with the new location // update the original request with the new location
fRequest.SetUrl(redirect.url); fRequest.SetUrl(redirect.url);
@@ -717,7 +705,7 @@ void
BHttpSession::Request::SetError(std::exception_ptr e) BHttpSession::Request::SetError(std::exception_ptr e)
{ {
fResult->SetError(e); fResult->SetError(e);
SendMessage(UrlEvent::DebugMessage, [&e](BMessage& msg){ SendMessage(UrlEvent::DebugMessage, [&e](BMessage& msg) {
msg.AddUInt32(UrlEventData::DebugType, UrlEventData::DebugError); msg.AddUInt32(UrlEventData::DebugType, UrlEventData::DebugError);
try { try {
std::rethrow_exception(e); std::rethrow_exception(e);
@@ -729,9 +717,8 @@ BHttpSession::Request::SetError(std::exception_ptr e)
msg.AddString(UrlEventData::DebugMessage, "Unknown exception"); msg.AddString(UrlEventData::DebugMessage, "Unknown exception");
} }
}); });
SendMessage(UrlEvent::RequestCompleted, [](BMessage& msg){ SendMessage(UrlEvent::RequestCompleted,
msg.AddBool(UrlEventData::Success, false); [](BMessage& msg) { msg.AddBool(UrlEventData::Success, false); });
});
} }
@@ -765,13 +752,12 @@ BHttpSession::Request::ResolveHostName()
// TODO: proxy // TODO: proxy
if (auto status = fRemoteAddress.SetTo(fRequest.Url().Host(), port); status != B_OK) { if (auto status = fRemoteAddress.SetTo(fRequest.Url().Host(), port); status != B_OK) {
throw BNetworkRequestError("BNetworkAddress::SetTo()", throw BNetworkRequestError(
BNetworkRequestError::HostnameError, status); "BNetworkAddress::SetTo()", BNetworkRequestError::HostnameError, status);
} }
SendMessage(UrlEvent::HostNameResolved, [this](BMessage& msg) { SendMessage(UrlEvent::HostNameResolved,
msg.AddString(UrlEventData::HostName, fRequest.Url().Host()); [this](BMessage& msg) { msg.AddString(UrlEventData::HostName, fRequest.Url().Host()); });
});
} }
@@ -795,8 +781,8 @@ BHttpSession::Request::OpenConnection()
// Open connection // Open connection
if (auto status = fSocket->Connect(fRemoteAddress); status != B_OK) { if (auto status = fSocket->Connect(fRemoteAddress); status != B_OK) {
// TODO: inform listeners that the connection failed // TODO: inform listeners that the connection failed
throw BNetworkRequestError("BSocket::Connect()", throw BNetworkRequestError(
BNetworkRequestError::NetworkError, status); "BSocket::Connect()", BNetworkRequestError::NetworkError, status);
} }
// Make the rest of the interaction non-blocking // Make the rest of the interaction non-blocking
@@ -822,8 +808,8 @@ BHttpSession::Request::TransferRequest()
{ {
// Assert that we are in the right state // Assert that we are in the right state
if (fRequestStatus != Connected) if (fRequestStatus != Connected)
throw BRuntimeError(__PRETTY_FUNCTION__, throw BRuntimeError(
"Write request for object that is not in the Connected state"); __PRETTY_FUNCTION__, "Write request for object that is not in the Connected state");
if (!fSerializer.IsInitialized()) if (!fSerializer.IsInitialized())
fSerializer.SetTo(fBuffer, fRequest); fSerializer.SetTo(fBuffer, fRequest);
@@ -861,185 +847,185 @@ BHttpSession::Request::ReceiveResult()
// Parse the content in the buffer // Parse the content in the buffer
switch (fParser.State()) { switch (fParser.State()) {
case HttpInputStreamState::StatusLine: case HttpInputStreamState::StatusLine:
{ {
if (fBuffer.RemainingBytes() == static_cast<size_t>(bytesRead)) { if (fBuffer.RemainingBytes() == static_cast<size_t>(bytesRead)) {
// In the initial run, the bytes in the buffer will match the bytes read to indicate // In the initial run, the bytes in the buffer will match the bytes read to indicate
// the response has started. // the response has started.
SendMessage(UrlEvent::ResponseStarted); SendMessage(UrlEvent::ResponseStarted);
}
if (fParser.ParseStatus(fBuffer, fStatus)) {
// the status headers are now received, decide what to do next
// Determine if we can handle redirects; else notify of receiving status
if (fRemainingRedirects > 0) {
switch (fStatus.StatusCode()) {
case BHttpStatusCode::MovedPermanently:
case BHttpStatusCode::TemporaryRedirect:
case BHttpStatusCode::PermanentRedirect:
// These redirects require the request body to be sent again. It this is
// possible, BHttpRequest::RewindBody() will return true in which case
// we can handle the redirect.
if (!fRequest.RewindBody())
break;
[[fallthrough]];
case BHttpStatusCode::Found:
case BHttpStatusCode::SeeOther:
// These redirects redirect to GET, so we don't care if we can rewind
// the body; in this case redirect
fMightRedirect = true;
break;
default:
break;
}
}
if ((fStatus.StatusClass() == BHttpStatusClass::ClientError
|| fStatus.StatusClass() == BHttpStatusClass::ServerError)
&& fRequest.StopOnError()) {
fRequestStatus = ContentReceived;
fResult->SetStatus(std::move(fStatus));
fResult->SetFields(BHttpFields());
fResult->SetBody();
return true;
}
if (!fMightRedirect) {
// we are not redirecting and there is no error, so inform listeners
SendMessage(UrlEvent::HttpStatus, [this](BMessage& msg) {
msg.AddInt16(UrlEventData::HttpStatusCode, fStatus.code);
});
fResult->SetStatus(BHttpStatus{fStatus.code, std::move(fStatus.text)});
}
} else {
// We do not have enough data for the status line yet
if (readEnd) {
throw BNetworkRequestError(__PRETTY_FUNCTION__,
BNetworkRequestError::ProtocolError,
"Response did not include a complete status line");
}
return false;
}
[[fallthrough]];
} }
case HttpInputStreamState::Fields:
{
if (!fParser.ParseFields(fBuffer, fFields)) {
// there may be more headers to receive, throw an error if there will be no more
if (readEnd) {
throw BNetworkRequestError(__PRETTY_FUNCTION__,
BNetworkRequestError::ProtocolError,
"Response did not include a complete header section");
}
break;
}
if (fParser.ParseStatus(fBuffer, fStatus)) { // The headers have been received, now set up the rest of the response handling
// the status headers are now received, decide what to do next
// Determine if we can handle redirects; else notify of receiving status // Handle redirects
if (fRemainingRedirects > 0) { if (fMightRedirect) {
auto redirectToGet = false;
switch (fStatus.StatusCode()) { switch (fStatus.StatusCode()) {
case BHttpStatusCode::Found:
case BHttpStatusCode::SeeOther:
// 302 and 303 redirections convert all requests to GET request, except for
// HEAD
redirectToGet = true;
[[fallthrough]];
case BHttpStatusCode::MovedPermanently: case BHttpStatusCode::MovedPermanently:
case BHttpStatusCode::TemporaryRedirect: case BHttpStatusCode::TemporaryRedirect:
case BHttpStatusCode::PermanentRedirect: case BHttpStatusCode::PermanentRedirect:
// These redirects require the request body to be sent again. It this is {
// possible, BHttpRequest::RewindBody() will return true in which case we can auto locationField = fFields.FindField("Location");
// handle the redirect. if (locationField == fFields.end()) {
if (!fRequest.RewindBody()) throw BNetworkRequestError(__PRETTY_FUNCTION__,
break; BNetworkRequestError::ProtocolError,
[[fallthrough]]; "Redirect; the Location field must be present and cannot be found");
case BHttpStatusCode::Found: }
case BHttpStatusCode::SeeOther: auto locationString = BString(
// These redirects redirect to GET, so we don't care if we can rewind the (*locationField).Value().data(), (*locationField).Value().size());
// body; in this case redirect auto redirect = BHttpSession::Redirect{
fMightRedirect = true; BUrl(fRequest.Url(), locationString), redirectToGet};
break; if (!redirect.url.IsValid()) {
throw BNetworkRequestError(__PRETTY_FUNCTION__,
BNetworkRequestError::ProtocolError,
"Redirect; invalid URL in the Location field");
}
// Notify of redirect
SendMessage(UrlEvent::HttpRedirect, [&locationString](BMessage& msg) {
msg.AddString(UrlEventData::HttpRedirectUrl, locationString);
});
throw redirect;
}
default: default:
// ignore other status codes and continue regular processing
SendMessage(UrlEvent::HttpStatus, [this](BMessage& msg) {
msg.AddInt16(UrlEventData::HttpStatusCode, fStatus.code);
});
fResult->SetStatus(BHttpStatus{fStatus.code, std::move(fStatus.text)});
break; break;
} }
} }
if ((fStatus.StatusClass() == BHttpStatusClass::ClientError // TODO: Parse received cookies
|| fStatus.StatusClass() == BHttpStatusClass::ServerError)
&& fRequest.StopOnError()) // Move headers to the result and inform listener
{ fResult->SetFields(std::move(fFields));
fRequestStatus = ContentReceived; SendMessage(UrlEvent::HttpFields);
fResult->SetStatus(std::move(fStatus));
fResult->SetFields(BHttpFields()); if (!fParser.HasContent()) {
// Any requests with not content are finished
fResult->SetBody(); fResult->SetBody();
SendMessage(UrlEvent::RequestCompleted,
[](BMessage& msg) { msg.AddBool(UrlEventData::Success, true); });
fRequestStatus = ContentReceived;
return true; return true;
} }
[[fallthrough]];
if (!fMightRedirect) {
// we are not redirecting and there is no error, so inform listeners
SendMessage(UrlEvent::HttpStatus, [this](BMessage& msg) {
msg.AddInt16(UrlEventData::HttpStatusCode, fStatus.code);
});
fResult->SetStatus(BHttpStatus{fStatus.code, std::move(fStatus.text)});
}
} else {
// We do not have enough data for the status line yet
if (readEnd) {
throw BNetworkRequestError(__PRETTY_FUNCTION__,
BNetworkRequestError::ProtocolError,
"Response did not include a complete status line");
}
return false;
} }
[[fallthrough]]; case HttpInputStreamState::Body:
} {
case HttpInputStreamState::Fields: size_t bytesWrittenToBody;
{ // The bytesWrittenToBody may differ from the bytes parsed from the buffer when
if (!fParser.ParseFields(fBuffer, fFields)) { // there is compression on the incoming stream.
// there may be more headers to receive, throw an error if there will be no more bytesRead = fParser.ParseBody(
if (readEnd) { fBuffer,
throw BNetworkRequestError(__PRETTY_FUNCTION__, [this, &bytesWrittenToBody](const std::byte* buffer, size_t size) {
BNetworkRequestError::ProtocolError, bytesWrittenToBody = fResult->WriteToBody(buffer, size);
"Response did not include a complete header section"); return bytesWrittenToBody;
},
readEnd);
SendMessage(UrlEvent::DownloadProgress, [this, bytesRead](BMessage& msg) {
msg.AddInt64(UrlEventData::NumBytes, bytesRead);
if (fParser.BodyBytesTotal())
msg.AddInt64(UrlEventData::TotalBytes, fParser.BodyBytesTotal().value());
});
if (bytesWrittenToBody > 0) {
SendMessage(UrlEvent::BytesWritten, [bytesWrittenToBody](BMessage& msg) {
msg.AddInt64(UrlEventData::NumBytes, bytesWrittenToBody);
});
} }
if (fParser.Complete()) {
fResult->SetBody();
SendMessage(UrlEvent::RequestCompleted,
[](BMessage& msg) { msg.AddBool(UrlEventData::Success, true); });
fRequestStatus = ContentReceived;
return true;
} else if (readEnd) {
// the parsing of the body is not complete but we are at the end of the data
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError,
"Unexpected end of data: more data was expected");
}
break; break;
} }
default:
// The headers have been received, now set up the rest of the response handling throw BRuntimeError(__PRETTY_FUNCTION__, "Not reachable");
// Handle redirects
if (fMightRedirect) {
auto redirectToGet = false;
switch (fStatus.StatusCode()) {
case BHttpStatusCode::Found:
case BHttpStatusCode::SeeOther:
// 302 and 303 redirections convert all requests to GET request, except for HEAD
redirectToGet = true;
[[fallthrough]];
case BHttpStatusCode::MovedPermanently:
case BHttpStatusCode::TemporaryRedirect:
case BHttpStatusCode::PermanentRedirect:
{
auto locationField = fFields.FindField("Location");
if (locationField == fFields.end()) {
throw BNetworkRequestError(__PRETTY_FUNCTION__,
BNetworkRequestError::ProtocolError,
"Redirect; the Location field must be present and cannot be found");
}
auto locationString = BString((*locationField).Value().data(),
(*locationField).Value().size());
auto redirect =
BHttpSession::Redirect{BUrl(fRequest.Url(), locationString), redirectToGet};
if (!redirect.url.IsValid()) {
throw BNetworkRequestError(__PRETTY_FUNCTION__,
BNetworkRequestError::ProtocolError,
"Redirect; invalid URL in the Location field");
}
// Notify of redirect
SendMessage(UrlEvent::HttpRedirect, [&locationString](BMessage& msg) {
msg.AddString(UrlEventData::HttpRedirectUrl, locationString);
});
throw redirect;
}
default:
// ignore other status codes and continue regular processing
SendMessage(UrlEvent::HttpStatus, [this](BMessage& msg) {
msg.AddInt16(UrlEventData::HttpStatusCode, fStatus.code);
});
fResult->SetStatus(BHttpStatus{fStatus.code, std::move(fStatus.text)});
break;
}
}
// TODO: Parse received cookies
// Move headers to the result and inform listener
fResult->SetFields(std::move(fFields));
SendMessage(UrlEvent::HttpFields);
if (!fParser.HasContent()) {
// Any requests with not content are finished
fResult->SetBody();
SendMessage(UrlEvent::RequestCompleted, [](BMessage& msg) {
msg.AddBool(UrlEventData::Success, true);
});
fRequestStatus = ContentReceived;
return true;
}
[[fallthrough]];
}
case HttpInputStreamState::Body:
{
size_t bytesWrittenToBody;
// The bytesWrittenToBody may differ from the bytes parsed from the buffer when
// there is compression on the incoming stream.
bytesRead = fParser.ParseBody(fBuffer, [this, &bytesWrittenToBody](const std::byte* buffer, size_t size) {
bytesWrittenToBody = fResult->WriteToBody(buffer, size);
return bytesWrittenToBody;
}, readEnd);
SendMessage(UrlEvent::DownloadProgress, [this, bytesRead](BMessage& msg) {
msg.AddInt64(UrlEventData::NumBytes, bytesRead);
if (fParser.BodyBytesTotal())
msg.AddInt64(UrlEventData::TotalBytes, fParser.BodyBytesTotal().value());
});
if (bytesWrittenToBody > 0) {
SendMessage(UrlEvent::BytesWritten, [bytesWrittenToBody](BMessage& msg) {
msg.AddInt64(UrlEventData::NumBytes, bytesWrittenToBody);
});
}
if (fParser.Complete()) {
fResult->SetBody();
SendMessage(UrlEvent::RequestCompleted, [](BMessage& msg) {
msg.AddBool(UrlEventData::Success, true);
});
fRequestStatus = ContentReceived;
return true;
} else if (readEnd) {
// the parsing of the body is not complete but we are at the end of the data
throw BNetworkRequestError(__PRETTY_FUNCTION__,
BNetworkRequestError::ProtocolError,
"Unexpected end of data: more data was expected");
}
break;
}
default:
throw BRuntimeError(__PRETTY_FUNCTION__, "Not reachable");
} }
// There is more to receive // There is more to receive
@@ -1047,7 +1033,6 @@ BHttpSession::Request::ReceiveResult()
} }
/*! /*!
\brief Disconnect the socket. Does not validate if it actually succeeded. \brief Disconnect the socket. Does not validate if it actually succeeded.
*/ */
@@ -1065,7 +1050,7 @@ BHttpSession::Request::Disconnect() noexcept
\param dataFunc Optional function that adds additional data to the message. \param dataFunc Optional function that adds additional data to the message.
*/ */
void void
BHttpSession::Request::SendMessage(uint32 what, std::function<void (BMessage&)> dataFunc) const BHttpSession::Request::SendMessage(uint32 what, std::function<void(BMessage&)> dataFunc) const
{ {
if (fObserver.IsValid()) { if (fObserver.IsValid()) {
BMessage msg(what); BMessage msg(what);
@@ -1081,8 +1066,8 @@ BHttpSession::Request::SendMessage(uint32 what, std::function<void (BMessage&)>
namespace BPrivate::Network::UrlEventData { namespace BPrivate::Network::UrlEventData {
const char* HttpStatusCode = "url:httpstatuscode"; const char* HttpStatusCode = "url:httpstatuscode";
const char* SSLCertificate = "url:sslcertificate"; const char* SSLCertificate = "url:sslcertificate";
const char* SSLMessage = "url:sslmessage"; const char* SSLMessage = "url:sslmessage";
const char* HttpRedirectUrl = "url:httpredirecturl"; const char* HttpRedirectUrl = "url:httpredirecturl";
} } // namespace BPrivate::Network::UrlEventData
+10 -12
View File
@@ -35,15 +35,15 @@ using namespace BPrivate::Network;
// - Invalid weekday // - Invalid weekday
static const std::list<std::pair<BHttpTimeFormat, const char*>> kDateFormats = { static const std::list<std::pair<BHttpTimeFormat, const char*>> kDateFormats = {
// RFC822 // RFC822
{BHttpTimeFormat::RFC1123, "%a, %d %b %Y %H:%M:%S GMT"},// canonical {BHttpTimeFormat::RFC1123, "%a, %d %b %Y %H:%M:%S GMT"}, // canonical
{BHttpTimeFormat::RFC1123, "%a, %d %b %Y %H:%M:%S"}, // without timezone {BHttpTimeFormat::RFC1123, "%a, %d %b %Y %H:%M:%S"}, // without timezone
// Standard RFC850 // Standard RFC850
{BHttpTimeFormat::RFC850, "%A, %d-%b-%y %H:%M:%S GMT"}, // canonical {BHttpTimeFormat::RFC850, "%A, %d-%b-%y %H:%M:%S GMT"}, // canonical
{BHttpTimeFormat::RFC850, "%A, %d-%b-%y %H:%M:%S"}, // without timezone {BHttpTimeFormat::RFC850, "%A, %d-%b-%y %H:%M:%S"}, // without timezone
// RFC 850 with 4 digit year // RFC 850 with 4 digit year
{BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S"}, // without timezone {BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S"}, // without timezone
{BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S GMT"}, // with 4-digit year {BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S GMT"}, // with 4-digit year
{BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S UTC"}, // "UTC" timezone {BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S UTC"}, // "UTC" timezone
// asctime // asctime
{BHttpTimeFormat::AscTime, "%a %b %e %H:%M:%S %Y"}, {BHttpTimeFormat::AscTime, "%a %b %e %H:%M:%S %Y"},
}; };
@@ -57,7 +57,6 @@ BHttpTime::InvalidInput::InvalidInput(const char* origin, BString input)
BError(origin), BError(origin),
input(std::move(input)) input(std::move(input))
{ {
} }
@@ -174,8 +173,8 @@ BHttpTime::ToString(BHttpTimeFormat outputFormat) const
char expirationString[kTimetToStringMaxLength + 1]; char expirationString[kTimetToStringMaxLength + 1];
size_t strLength; size_t strLength;
strLength = strftime(expirationString, kTimetToStringMaxLength, formatString, strLength
&expirationTm); = strftime(expirationString, kTimetToStringMaxLength, formatString, &expirationTm);
expirationFinal.SetTo(expirationString, strLength); expirationFinal.SetTo(expirationString, strLength);
break; break;
@@ -210,8 +209,7 @@ BHttpTime::_Parse(const BString& dateString)
// Now convert the struct tm from strptime into a BDateTime. // Now convert the struct tm from strptime into a BDateTime.
BTime time(expireTime.tm_hour, expireTime.tm_min, expireTime.tm_sec); BTime time(expireTime.tm_hour, expireTime.tm_min, expireTime.tm_sec);
BDate date(expireTime.tm_year + 1900, expireTime.tm_mon + 1, BDate date(expireTime.tm_year + 1900, expireTime.tm_mon + 1, expireTime.tm_mday);
expireTime.tm_mday);
fDate = BDateTime(date, time); fDate = BDateTime(date, time);
} }
@@ -17,25 +17,22 @@ namespace Network {
// #pragma mark -- BUnsupportedProtocol // #pragma mark -- BUnsupportedProtocol
BUnsupportedProtocol::BUnsupportedProtocol(const char* origin, BUnsupportedProtocol::BUnsupportedProtocol(
BUrl url, BStringList supportedProtocols) const char* origin, BUrl url, BStringList supportedProtocols)
: :
BError(origin), BError(origin),
fUrl(std::move(url)), fUrl(std::move(url)),
fSupportedProtocols(std::move(supportedProtocols)) fSupportedProtocols(std::move(supportedProtocols))
{ {
} }
BUnsupportedProtocol::BUnsupportedProtocol(BString origin, BUnsupportedProtocol::BUnsupportedProtocol(BString origin, BUrl url, BStringList supportedProtocols)
BUrl url, BStringList supportedProtocols)
: :
BError(std::move(origin)), BError(std::move(origin)),
fUrl(std::move(url)), fUrl(std::move(url)),
fSupportedProtocols(std::move(supportedProtocols)) fSupportedProtocols(std::move(supportedProtocols))
{ {
} }
@@ -68,7 +65,6 @@ BInvalidUrl::BInvalidUrl(const char* origin, BUrl url)
BError(origin), BError(origin),
fUrl(std::move(url)) fUrl(std::move(url))
{ {
} }
@@ -77,7 +73,6 @@ BInvalidUrl::BInvalidUrl(BString origin, BUrl url)
BError(std::move(origin)), BError(std::move(origin)),
fUrl(std::move(origin)) fUrl(std::move(origin))
{ {
} }
@@ -98,19 +93,24 @@ BInvalidUrl::Url() const
// #pragma mark -- BNetworkRequestError // #pragma mark -- BNetworkRequestError
BNetworkRequestError::BNetworkRequestError(const char* origin, ErrorType type, status_t errorCode, BNetworkRequestError::BNetworkRequestError(
const BString& customMessage) const char* origin, ErrorType type, status_t errorCode, const BString& customMessage)
: BError(origin), fErrorType(type), fErrorCode(errorCode), fCustomMessage(customMessage) :
BError(origin),
fErrorType(type),
fErrorCode(errorCode),
fCustomMessage(customMessage)
{ {
} }
BNetworkRequestError::BNetworkRequestError(const char* origin, ErrorType type, BNetworkRequestError::BNetworkRequestError(
const BString& customMessage) const char* origin, ErrorType type, const BString& customMessage)
: BError(origin), fErrorType(type), fCustomMessage(customMessage) :
BError(origin),
fErrorType(type),
fCustomMessage(customMessage)
{ {
} }
@@ -118,16 +118,16 @@ const char*
BNetworkRequestError::Message() const noexcept BNetworkRequestError::Message() const noexcept
{ {
switch (fErrorType) { switch (fErrorType) {
case HostnameError: case HostnameError:
return "Cannot resolving hostname"; return "Cannot resolving hostname";
case NetworkError: case NetworkError:
return "Network error during operation"; return "Network error during operation";
case ProtocolError: case ProtocolError:
return "Protocol error"; return "Protocol error";
case SystemError: case SystemError:
return "System error"; return "System error";
case Canceled: case Canceled:
return "Network request was canceled"; return "Network request was canceled";
} }
// Unreachable // Unreachable
return "Network request error"; return "Network request error";
@@ -141,7 +141,7 @@ BNetworkRequestError::DebugMessage() const
debugMessage << "[" << Origin() << "] " << Message(); debugMessage << "[" << Origin() << "] " << Message();
if (fErrorCode != B_OK) { if (fErrorCode != B_OK) {
debugMessage << "\n\tUnderlying System Error: " << fErrorCode << " (" debugMessage << "\n\tUnderlying System Error: " << fErrorCode << " ("
<< strerror(fErrorCode) << ")"; << strerror(fErrorCode) << ")";
} }
if (fCustomMessage.Length() > 0) { if (fCustomMessage.Length() > 0) {
debugMessage << "\n\tAdditional Info: " << fCustomMessage; debugMessage << "\n\tAdditional Info: " << fCustomMessage;
@@ -185,8 +185,8 @@ encode_to_base64(const BString& string)
BString tmpString = string; BString tmpString = string;
while (tmpString.Length()) { while (tmpString.Length()) {
char in[3] = { 0, 0, 0 }; char in[3] = {0, 0, 0};
char out[4] = { 0, 0, 0, 0 }; char out[4] = {0, 0, 0, 0};
int8 remaining = tmpString.Length(); int8 remaining = tmpString.Length();
tmpString.MoveInto(in, 0, 3); tmpString.MoveInto(in, 0, 3);
@@ -197,7 +197,7 @@ encode_to_base64(const BString& string)
out[3] = in[2] & 0x3F; out[3] = in[2] & 0x3F;
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
out[i] = kBase64Symbols[(int)out[i]]; out[i] = kBase64Symbols[(int) out[i]];
// Add padding if the input length is not a multiple // Add padding if the input length is not a multiple
// of 3 // of 3
@@ -219,14 +219,14 @@ encode_to_base64(const BString& string)
// #pragma mark -- message constants // #pragma mark -- message constants
namespace UrlEventData { namespace UrlEventData {
const char* Id = "url:identifier"; const char* Id = "url:identifier";
const char* HostName = "url:hostname"; const char* HostName = "url:hostname";
const char* NumBytes = "url:numbytes"; const char* NumBytes = "url:numbytes";
const char* TotalBytes = "url:totalbytes"; const char* TotalBytes = "url:totalbytes";
const char* Success = "url:success"; const char* Success = "url:success";
const char* DebugType = "url:debugtype"; const char* DebugType = "url:debugtype";
const char* DebugMessage = "url:debugmessage"; const char* DebugMessage = "url:debugmessage";
} } // namespace UrlEventData
// #pragma mark -- Private functions and data // #pragma mark -- Private functions and data
@@ -26,64 +26,46 @@ class DeleteTestHelper
{ {
public: public:
DeleteTestHelper(std::atomic<bool>& deleted) DeleteTestHelper(std::atomic<bool>& deleted)
: fDeleted(deleted) :
fDeleted(deleted)
{ {
} }
~DeleteTestHelper() ~DeleteTestHelper() { fDeleted.store(true); }
{
fDeleted.store(true);
}
private: private:
std::atomic<bool>& fDeleted; std::atomic<bool>& fDeleted;
}; };
class Base { class Base
{
public: public:
Base() Base() {}
{
}
virtual ~Base() virtual ~Base() {}
{
}
virtual bool IsDerived() virtual bool IsDerived() { return false; }
{
return false;
}
}; };
class Derived : public Base { class Derived : public Base
{
public: public:
Derived() { Derived() {}
}
virtual ~Derived() { virtual ~Derived() {}
}
virtual bool IsDerived() override virtual bool IsDerived() override { return true; }
{
return true;
}
}; };
ExclusiveBorrowTest::ExclusiveBorrowTest() ExclusiveBorrowTest::ExclusiveBorrowTest()
{ {
} }
@@ -9,18 +9,18 @@
#include <TestSuite.h> #include <TestSuite.h>
class ExclusiveBorrowTest: public BTestCase { class ExclusiveBorrowTest : public BTestCase
{
public: public:
ExclusiveBorrowTest(); ExclusiveBorrowTest();
void ObjectDeleteTest(); void ObjectDeleteTest();
void OwnershipTest(); void OwnershipTest();
void PolymorphismTest(); void PolymorphismTest();
void ReleaseTest(); void ReleaseTest();
static void AddTests(BTestSuite& suite); static void AddTests(BTestSuite& suite);
}; };
#endif // EXCLUSIVE_BORROW_TEST_H #endif // EXCLUSIVE_BORROW_TEST_H
@@ -18,9 +18,9 @@ using namespace BPrivate::Network;
HttpDebugLogger::HttpDebugLogger() HttpDebugLogger::HttpDebugLogger()
: BLooper("HttpDebugLogger") :
BLooper("HttpDebugLogger")
{ {
} }
@@ -39,6 +39,7 @@ HttpDebugLogger::SetFileLogging(const char* path)
throw BSystemError("BFile::SetTo()", status); throw BSystemError("BFile::SetTo()", status);
} }
void void
HttpDebugLogger::MessageReceived(BMessage* message) HttpDebugLogger::MessageReceived(BMessage* message)
{ {
@@ -50,102 +51,102 @@ HttpDebugLogger::MessageReceived(BMessage* message)
output << "[" << id << "] "; output << "[" << id << "] ";
switch (message->what) { switch (message->what) {
case UrlEvent::HostNameResolved: case UrlEvent::HostNameResolved:
{ {
BString hostname; BString hostname;
message->FindString(UrlEventData::HostName, &hostname); message->FindString(UrlEventData::HostName, &hostname);
output << "<HostNameResolved> " << hostname; output << "<HostNameResolved> " << hostname;
break; break;
}
case UrlEvent::ConnectionOpened:
output << "<ConnectionOpened>";
break;
case UrlEvent::UploadProgress:
{
off_t numBytes = message->GetInt64(UrlEventData::NumBytes, -1);
off_t totalBytes = message->GetInt64(UrlEventData::TotalBytes, -1);
output << "<UploadProgress> bytes uploaded " << numBytes;
if (totalBytes == -1)
output << " (total unknown)";
else
output << " (" << totalBytes << " total)";
break;
}
case UrlEvent::ResponseStarted:
{
output << "<ResponseStarted>";
break;
}
case UrlEvent::HttpRedirect:
{
BString redirectUrl;
message->FindString(UrlEventData::HttpRedirectUrl, &redirectUrl);
output << "<HttpRedirect> to: " << redirectUrl;
break;
}
case UrlEvent::HttpStatus:
{
int16 status = message->FindInt16(UrlEventData::HttpStatusCode);
output << "<HttpStatus> code: " << status;
break;
}
case UrlEvent::HttpFields:
{
output << "<HttpFields> All fields parsed";
break;
}
case UrlEvent::DownloadProgress:
{
off_t numBytes = message->GetInt64(UrlEventData::NumBytes, -1);
off_t totalBytes = message->GetInt64(UrlEventData::TotalBytes, -1);
output << "<DownloadProgress> bytes downloaded " << numBytes;
if (totalBytes == -1)
output << " (total unknown)";
else
output << " (" << totalBytes << " total)";
break;
}
case UrlEvent::BytesWritten:
{
off_t numBytes = message->GetInt64(UrlEventData::NumBytes, -1);
output << "<BytesWritten> bytes written to output: " << numBytes;
break;
}
case UrlEvent::RequestCompleted:
{
bool success = message->GetBool(UrlEventData::Success, false);
output << "<RequestCompleted> success: ";
if (success)
output << "true";
else
output << "false";
break;
}
case UrlEvent::DebugMessage:
{
uint32 debugType = message->GetUInt32(UrlEventData::DebugType, 0);
BString debugMessage;
message->FindString(UrlEventData::DebugMessage, &debugMessage);
output << "<DebugMessage> ";
switch (debugType) {
case UrlEventData::DebugInfo:
output << "INFO: ";
break;
case UrlEventData::DebugWarning:
output << "WARNING: ";
break;
case UrlEventData::DebugError:
output << "ERROR: ";
break;
default:
output << "UNKNOWN: ";
break;
} }
output << debugMessage; case UrlEvent::ConnectionOpened:
break; output << "<ConnectionOpened>";
} break;
default: case UrlEvent::UploadProgress:
return BLooper::MessageReceived(message); {
off_t numBytes = message->GetInt64(UrlEventData::NumBytes, -1);
off_t totalBytes = message->GetInt64(UrlEventData::TotalBytes, -1);
output << "<UploadProgress> bytes uploaded " << numBytes;
if (totalBytes == -1)
output << " (total unknown)";
else
output << " (" << totalBytes << " total)";
break;
}
case UrlEvent::ResponseStarted:
{
output << "<ResponseStarted>";
break;
}
case UrlEvent::HttpRedirect:
{
BString redirectUrl;
message->FindString(UrlEventData::HttpRedirectUrl, &redirectUrl);
output << "<HttpRedirect> to: " << redirectUrl;
break;
}
case UrlEvent::HttpStatus:
{
int16 status = message->FindInt16(UrlEventData::HttpStatusCode);
output << "<HttpStatus> code: " << status;
break;
}
case UrlEvent::HttpFields:
{
output << "<HttpFields> All fields parsed";
break;
}
case UrlEvent::DownloadProgress:
{
off_t numBytes = message->GetInt64(UrlEventData::NumBytes, -1);
off_t totalBytes = message->GetInt64(UrlEventData::TotalBytes, -1);
output << "<DownloadProgress> bytes downloaded " << numBytes;
if (totalBytes == -1)
output << " (total unknown)";
else
output << " (" << totalBytes << " total)";
break;
}
case UrlEvent::BytesWritten:
{
off_t numBytes = message->GetInt64(UrlEventData::NumBytes, -1);
output << "<BytesWritten> bytes written to output: " << numBytes;
break;
}
case UrlEvent::RequestCompleted:
{
bool success = message->GetBool(UrlEventData::Success, false);
output << "<RequestCompleted> success: ";
if (success)
output << "true";
else
output << "false";
break;
}
case UrlEvent::DebugMessage:
{
uint32 debugType = message->GetUInt32(UrlEventData::DebugType, 0);
BString debugMessage;
message->FindString(UrlEventData::DebugMessage, &debugMessage);
output << "<DebugMessage> ";
switch (debugType) {
case UrlEventData::DebugInfo:
output << "INFO: ";
break;
case UrlEventData::DebugWarning:
output << "WARNING: ";
break;
case UrlEventData::DebugError:
output << "ERROR: ";
break;
default:
output << "UNKNOWN: ";
break;
}
output << debugMessage;
break;
}
default:
return BLooper::MessageReceived(message);
} }
if (fConsoleLogging) if (fConsoleLogging)
@@ -12,16 +12,16 @@
class HttpDebugLogger : public BLooper class HttpDebugLogger : public BLooper
{ {
public: public:
HttpDebugLogger(); HttpDebugLogger();
void SetConsoleLogging(bool enabled = true); void SetConsoleLogging(bool enabled = true);
void SetFileLogging(const char* path); void SetFileLogging(const char* path);
protected: protected:
virtual void MessageReceived(BMessage* message) override; virtual void MessageReceived(BMessage* message) override;
private: private:
bool fConsoleLogging = false; bool fConsoleLogging = false;
BFile fLogFile; BFile fLogFile;
}; };
#endif // HTTP_DEBUG_LOGGER_H #endif // HTTP_DEBUG_LOGGER_H
@@ -47,7 +47,6 @@ constexpr bool LOG_TO_CONSOLE = false;
HttpProtocolTest::HttpProtocolTest() HttpProtocolTest::HttpProtocolTest()
{ {
} }
@@ -147,14 +146,11 @@ HttpProtocolTest::HttpFieldsTest()
} }
// Set up a generic set of headers for further use // Set up a generic set of headers for further use
const BHttpFields defaultFields = { const BHttpFields defaultFields = {{"Host"sv, "haiku-os.org"sv}, {"Accept"sv, "*/*"sv},
{"Host"sv, "haiku-os.org"sv},
{"Accept"sv, "*/*"sv},
{"Set-Cookie"sv, "qwerty=494793ddkl; Domain=haiku-os.co.uk"sv}, {"Set-Cookie"sv, "qwerty=494793ddkl; Domain=haiku-os.co.uk"sv},
{"Set-Cookie"sv, "afbzyi=0kdnke0lyv; Domain=haiku-os.co.uk"sv}, {"Set-Cookie"sv, "afbzyi=0kdnke0lyv; Domain=haiku-os.co.uk"sv},
{}, // Empty; should be ignored by the constructor {}, // Empty; should be ignored by the constructor
{"Accept-Encoding"sv, "gzip"sv} {"Accept-Encoding"sv, "gzip"sv}};
};
// Validate std::initializer_list constructor // Validate std::initializer_list constructor
CPPUNIT_ASSERT_EQUAL(5, defaultFields.CountFields()); CPPUNIT_ASSERT_EQUAL(5, defaultFields.CountFields());
@@ -216,12 +212,8 @@ HttpProtocolTest::HttpFieldsTest()
// Iterate through the fields using a constant iterator // Iterate through the fields using a constant iterator
{ {
const BHttpFields fields = { const BHttpFields fields = {{"key1"sv, "value1"sv}, {"key2"sv, "value2"sv},
{"key1"sv, "value1"sv}, {"key3"sv, "value3"sv}, {"key4"sv, "value4"sv}};
{"key2"sv, "value2"sv},
{"key3"sv, "value3"sv},
{"key4"sv, "value4"sv}
};
auto count = 0L; auto count = 0L;
for (const auto& field: fields) { for (const auto& field: fields) {
@@ -283,12 +275,11 @@ HttpProtocolTest::HttpMethodTest()
} }
constexpr std::string_view kExpectedRequestText = 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-Encoding: gzip\r\n"
"Accept-Encoding: gzip\r\n" "Connection: close\r\n"
"Connection: close\r\n" "Api-Key: 01234567890abcdef\r\n\r\n";
"Api-Key: 01234567890abcdef\r\n\r\n";
void void
@@ -323,16 +314,11 @@ HttpProtocolTest::HttpRequestTest()
void void
HttpProtocolTest::HttpTimeTest() HttpProtocolTest::HttpTimeTest()
{ {
const std::vector<BString> kValidTimeStrings = { const std::vector<BString> kValidTimeStrings
"Sun, 07 Dec 2003 16:01:00 GMT", = {"Sun, 07 Dec 2003 16:01:00 GMT", "Sun, 07 Dec 2003 16:01:00",
"Sun, 07 Dec 2003 16:01:00", "Sunday, 07-Dec-03 16:01:00 GMT", "Sunday, 07-Dec-03 16:01:00 GMT",
"Sunday, 07-Dec-03 16:01:00 GMT", "Sunday, 07-Dec-2003 16:01:00", "Sunday, 07-Dec-2003 16:01:00 GMT",
"Sunday, 07-Dec-03 16:01:00 GMT", "Sunday, 07-Dec-2003 16:01:00 UTC", "Sun Dec 7 16:01:00 2003"};
"Sunday, 07-Dec-2003 16:01:00",
"Sunday, 07-Dec-2003 16:01:00 GMT",
"Sunday, 07-Dec-2003 16:01:00 UTC",
"Sun Dec 7 16:01:00 2003"
};
const BDateTime kExpectedDateTime = {BDate{2003, 12, 7}, BTime{16, 01, 0}}; const BDateTime kExpectedDateTime = {BDate{2003, 12, 7}, BTime{16, 01, 0}};
for (const auto& timeString: kValidTimeStrings) { for (const auto& timeString: kValidTimeStrings) {
@@ -340,9 +326,9 @@ HttpProtocolTest::HttpTimeTest()
} }
const std::vector<BString> kInvalidTimeStrings = { const std::vector<BString> kInvalidTimeStrings = {
"Sun, 07 Dec 2003", // Date only "Sun, 07 Dec 2003", // Date only
"Sun, 07 Dec 2003 16:01:00 BST", // Invalid timezone "Sun, 07 Dec 2003 16:01:00 BST", // Invalid timezone
"On Sun, 07 Dec 2003 16:01:00 GMT", // Extra data in front of the string "On Sun, 07 Dec 2003 16:01:00 GMT", // Extra data in front of the string
}; };
for (const auto& timeString: kInvalidTimeStrings) { for (const auto& timeString: kInvalidTimeStrings) {
@@ -357,8 +343,8 @@ HttpProtocolTest::HttpTimeTest()
} }
// Validate format_http_time() // Validate format_http_time()
CPPUNIT_ASSERT_EQUAL(BString("Sun, 07 Dec 2003 16:01:00 GMT"), CPPUNIT_ASSERT_EQUAL(
format_http_time(kExpectedDateTime)); BString("Sun, 07 Dec 2003 16:01:00 GMT"), format_http_time(kExpectedDateTime));
CPPUNIT_ASSERT_EQUAL(BString("Sunday, 07-Dec-03 16:01:00 GMT"), CPPUNIT_ASSERT_EQUAL(BString("Sunday, 07-Dec-03 16:01:00 GMT"),
format_http_time(kExpectedDateTime, BHttpTimeFormat::RFC850)); format_http_time(kExpectedDateTime, BHttpTimeFormat::RFC850));
CPPUNIT_ASSERT_EQUAL(BString("Sun Dec 7 16:01:00 2003"), CPPUNIT_ASSERT_EQUAL(BString("Sun Dec 7 16:01:00 2003"),
@@ -387,16 +373,18 @@ HttpProtocolTest::AddTests(BTestSuite& parent)
// Observer test // Observer test
#include <iostream> #include <iostream>
class ObserverHelper : public BLooper { class ObserverHelper : public BLooper
{
public: public:
ObserverHelper() ObserverHelper()
: BLooper("ObserverHelper") {} :
BLooper("ObserverHelper")
void MessageReceived(BMessage* msg) override { {
messages.emplace_back(*msg);
} }
std::vector<BMessage> messages; void MessageReceived(BMessage* msg) override { messages.emplace_back(*msg); }
std::vector<BMessage> messages;
}; };
@@ -404,7 +392,8 @@ public:
HttpIntegrationTest::HttpIntegrationTest(TestServerMode mode) HttpIntegrationTest::HttpIntegrationTest(TestServerMode mode)
: fTestServer(mode) :
fTestServer(mode)
{ {
// increase number of concurrent connections to 4 (from 2) // increase number of concurrent connections to 4 (from 2)
fSession.SetMaxConnectionsPerHost(4); fSession.SetMaxConnectionsPerHost(4);
@@ -425,10 +414,7 @@ HttpIntegrationTest::HttpIntegrationTest(TestServerMode mode)
void void
HttpIntegrationTest::setUp() HttpIntegrationTest::setUp()
{ {
CPPUNIT_ASSERT_EQUAL_MESSAGE( CPPUNIT_ASSERT_EQUAL_MESSAGE("Starting up test server", B_OK, fTestServer.Start());
"Starting up test server",
B_OK,
fTestServer.Start());
} }
@@ -454,8 +440,8 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
= new BThreadedTestCaller<HttpIntegrationTest>("HttpTest::", httpIntegrationTest); = new BThreadedTestCaller<HttpIntegrationTest>("HttpTest::", httpIntegrationTest);
// HTTP // HTTP
testCaller->addThread("HostAndNetworkFailTest", testCaller->addThread(
&HttpIntegrationTest::HostAndNetworkFailTest); "HostAndNetworkFailTest", &HttpIntegrationTest::HostAndNetworkFailTest);
testCaller->addThread("GetTest", &HttpIntegrationTest::GetTest); testCaller->addThread("GetTest", &HttpIntegrationTest::GetTest);
testCaller->addThread("GetWithBufferTest", &HttpIntegrationTest::GetWithBufferTest); testCaller->addThread("GetWithBufferTest", &HttpIntegrationTest::GetWithBufferTest);
testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest); testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest);
@@ -479,16 +465,16 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
= new BThreadedTestCaller<HttpIntegrationTest>("HttpsTest::", httpsIntegrationTest); = new BThreadedTestCaller<HttpIntegrationTest>("HttpsTest::", httpsIntegrationTest);
// HTTPS // HTTPS
testCaller->addThread("HostAndNetworkFailTest", testCaller->addThread(
&HttpIntegrationTest::HostAndNetworkFailTest); "HostAndNetworkFailTest", &HttpIntegrationTest::HostAndNetworkFailTest);
testCaller->addThread("GetTest", &HttpIntegrationTest::GetTest); testCaller->addThread("GetTest", &HttpIntegrationTest::GetTest);
testCaller->addThread("GetWithBufferTest", &HttpIntegrationTest::GetWithBufferTest); testCaller->addThread("GetWithBufferTest", &HttpIntegrationTest::GetWithBufferTest);
testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest); testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest);
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest); testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest); testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
// testCaller->addThread("BasicAuthTest", &HttpIntegrationTest::BasicAuthTest); // testCaller->addThread("BasicAuthTest", &HttpIntegrationTest::BasicAuthTest);
// Skip BasicAuthTest for HTTPS: it seems like it does not close the socket properly, // Skip BasicAuthTest for HTTPS: it seems like it does not close the socket properly,
// raising a SSL EOF error. // raising a SSL EOF error.
testCaller->addThread("StopOnErrorTest", &HttpIntegrationTest::StopOnErrorTest); testCaller->addThread("StopOnErrorTest", &HttpIntegrationTest::StopOnErrorTest);
testCaller->addThread("RequestCancelTest", &HttpIntegrationTest::RequestCancelTest); testCaller->addThread("RequestCancelTest", &HttpIntegrationTest::RequestCancelTest);
testCaller->addThread("PostTest", &HttpIntegrationTest::PostTest); testCaller->addThread("PostTest", &HttpIntegrationTest::PostTest);
@@ -538,15 +524,13 @@ static const BHttpFields kExpectedGetFields = {
}; };
constexpr std::string_view kExpectedGetBody = { constexpr std::string_view kExpectedGetBody = {"Path: /\r\n"
"Path: /\r\n" "\r\n"
"\r\n" "Headers:\r\n"
"Headers:\r\n" "--------\r\n"
"--------\r\n" "Host: 127.0.0.1:PORT\r\n"
"Host: 127.0.0.1:PORT\r\n" "Accept-Encoding: gzip\r\n"
"Accept-Encoding: gzip\r\n" "Connection: close\r\n"};
"Connection: close\r\n"
};
void void
@@ -583,8 +567,8 @@ HttpIntegrationTest::GetWithBufferTest()
auto result = fSession.Execute(std::move(request), BBorrow<BDataIO>(body), fLoggerMessenger); auto result = fSession.Execute(std::move(request), BBorrow<BDataIO>(body), fLoggerMessenger);
try { try {
result.Body(); result.Body();
auto bodyString = std::string(reinterpret_cast<const char*>(body->Buffer()), auto bodyString
body->BufferLength()); = std::string(reinterpret_cast<const char*>(body->Buffer()), body->BufferLength());
CPPUNIT_ASSERT_EQUAL(kExpectedGetBody, bodyString); CPPUNIT_ASSERT_EQUAL(kExpectedGetBody, bodyString);
} catch (const BPrivate::Network::BError& e) { } catch (const BPrivate::Network::BError& e) {
CPPUNIT_FAIL(e.DebugMessage().String()); CPPUNIT_FAIL(e.DebugMessage().String());
@@ -597,7 +581,7 @@ HttpIntegrationTest::HeadTest()
{ {
auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/")); auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/"));
request.SetMethod(BHttpMethod::Head); request.SetMethod(BHttpMethod::Head);
auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger); auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger);
try { try {
auto receivedFields = result.Fields(); auto receivedFields = result.Fields();
CPPUNIT_ASSERT_EQUAL_MESSAGE("Mismatch in number of headers", CPPUNIT_ASSERT_EQUAL_MESSAGE("Mismatch in number of headers",
@@ -627,7 +611,7 @@ void
HttpIntegrationTest::NoContentTest() HttpIntegrationTest::NoContentTest()
{ {
auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/204")); auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/204"));
auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger); auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger);
try { try {
auto receivedStatus = result.Status(); auto receivedStatus = result.Status();
CPPUNIT_ASSERT_EQUAL(204, receivedStatus.code); CPPUNIT_ASSERT_EQUAL(204, receivedStatus.code);
@@ -654,7 +638,7 @@ void
HttpIntegrationTest::AutoRedirectTest() HttpIntegrationTest::AutoRedirectTest()
{ {
auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/302")); auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/302"));
auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger); auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger);
try { try {
auto receivedFields = result.Fields(); auto receivedFields = result.Fields();
@@ -682,15 +666,15 @@ HttpIntegrationTest::BasicAuthTest()
// Basic Authentication // Basic Authentication
auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/auth/basic/walter/secret")); auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/auth/basic/walter/secret"));
request.SetAuthentication({"walter", "secret"}); request.SetAuthentication({"walter", "secret"});
auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger); auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger);
CPPUNIT_ASSERT(result.Status().code == 200); CPPUNIT_ASSERT(result.Status().code == 200);
// Basic Authentication with incorrect credentials // Basic Authentication with incorrect credentials
try { try {
request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/auth/basic/walter/secret")); request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/auth/basic/walter/secret"));
request.SetAuthentication({"invaliduser", "invalidpassword"}); request.SetAuthentication({"invaliduser", "invalidpassword"});
result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger); result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger);
CPPUNIT_ASSERT(result.Status().code == 401); CPPUNIT_ASSERT(result.Status().code == 401);
} catch (const BPrivate::Network::BError& e) { } catch (const BPrivate::Network::BError& e) {
CPPUNIT_FAIL(e.DebugMessage().String()); CPPUNIT_FAIL(e.DebugMessage().String());
} }
@@ -703,7 +687,7 @@ HttpIntegrationTest::StopOnErrorTest()
// Test the Stop on Error functionality // Test the Stop on Error functionality
auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/400")); auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/400"));
request.SetStopOnError(true); request.SetStopOnError(true);
auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger); auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger);
CPPUNIT_ASSERT(result.Status().code == 400); CPPUNIT_ASSERT(result.Status().code == 400);
CPPUNIT_ASSERT(result.Fields().CountFields() == 0); CPPUNIT_ASSERT(result.Fields().CountFields() == 0);
CPPUNIT_ASSERT(result.Body().text->Length() == 0); CPPUNIT_ASSERT(result.Body().text->Length() == 0);
@@ -718,7 +702,7 @@ HttpIntegrationTest::RequestCancelTest()
// processed. In practise, the cancellation always comes first. When the server // processed. In practise, the cancellation always comes first. When the server
// supports a wait parameter, then this test can be made more robust. // supports a wait parameter, then this test can be made more robust.
auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/")); auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/"));
auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger); auto result = fSession.Execute(std::move(request), nullptr, fLoggerMessenger);
fSession.Cancel(result); fSession.Cancel(result);
try { try {
result.Body(); result.Body();
@@ -729,46 +713,45 @@ HttpIntegrationTest::RequestCancelTest()
} }
static const BString kPostText = static const BString kPostText
"The MIT License\n" = "The MIT License\n"
"\n" "\n"
"Copyright (c) <year> <copyright holders>\n" "Copyright (c) <year> <copyright holders>\n"
"\n" "\n"
"Permission is hereby granted, free of charge, to any person obtaining a copy\n" "Permission is hereby granted, free of charge, to any person obtaining a copy\n"
"of this software and associated documentation files (the \"Software\"), to deal\n" "of this software and associated documentation files (the \"Software\"), to deal\n"
"in the Software without restriction, including without limitation the rights\n" "in the Software without restriction, including without limitation the rights\n"
"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n" "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
"copies of the Software, and to permit persons to whom the Software is\n" "copies of the Software, and to permit persons to whom the Software is\n"
"furnished to do so, subject to the following conditions:\n" "furnished to do so, subject to the following conditions:\n"
"\n" "\n"
"The above copyright notice and this permission notice shall be included in\n" "The above copyright notice and this permission notice shall be included in\n"
"all copies or substantial portions of the Software.\n" "all copies or substantial portions of the Software.\n"
"\n" "\n"
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n" "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n" "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n" "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n" "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n" "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n" "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"
"THE SOFTWARE.\n" "THE SOFTWARE.\n"
"\n"; "\n";
static BString kExpectedPostBody static BString kExpectedPostBody = BString().SetToFormat("Path: /post\r\n"
= BString().SetToFormat( "\r\n"
"Path: /post\r\n" "Headers:\r\n"
"\r\n" "--------\r\n"
"Headers:\r\n" "Host: 127.0.0.1:PORT\r\n"
"--------\r\n" "Accept-Encoding: gzip\r\n"
"Host: 127.0.0.1:PORT\r\n" "Connection: close\r\n"
"Accept-Encoding: gzip\r\n" "Content-Type: text/plain\r\n"
"Connection: close\r\n" "Content-Length: 1083\r\n"
"Content-Type: text/plain\r\n" "\r\n"
"Content-Length: 1083\r\n" "Request body:\r\n"
"\r\n" "-------------\r\n"
"Request body:\r\n" "%s\r\n",
"-------------\r\n" kPostText.String());
"%s\r\n", kPostText.String());
void void
@@ -796,16 +779,15 @@ HttpIntegrationTest::PostTest()
usleep(2000); // give some time to catch up on receiving all messages usleep(2000); // give some time to catch up on receiving all messages
observer->Lock(); observer->Lock();
while (observer->IsMessageWaiting()) while (observer->IsMessageWaiting()) {
{
observer->Unlock(); observer->Unlock();
usleep(1000); // give some time to catch up on receiving all messages usleep(1000); // give some time to catch up on receiving all messages
observer->Lock(); observer->Lock();
} }
// Assert that the messages have the right contents. // Assert that the messages have the right contents.
CPPUNIT_ASSERT_MESSAGE("Expected at least 8 observer messages for this request.", CPPUNIT_ASSERT_MESSAGE(
observer->messages.size() >= 8); "Expected at least 8 observer messages for this request.", observer->messages.size() >= 8);
uint32 previousMessage = 0; uint32 previousMessage = 0;
for (const auto& message: observer->messages) { for (const auto& message: observer->messages) {
@@ -817,20 +799,20 @@ HttpIntegrationTest::PostTest()
continue; continue;
} }
switch(previousMessage) { switch (previousMessage) {
case 0: case 0:
CPPUNIT_ASSERT_MESSAGE("message should be HostNameResolved", CPPUNIT_ASSERT_MESSAGE(
HostNameResolved == message.what); "message should be HostNameResolved", HostNameResolved == message.what);
break; break;
case HostNameResolved: case HostNameResolved:
CPPUNIT_ASSERT_MESSAGE("message should be ConnectionOpened", CPPUNIT_ASSERT_MESSAGE(
ConnectionOpened == message.what); "message should be ConnectionOpened", ConnectionOpened == message.what);
break; break;
case ConnectionOpened: case ConnectionOpened:
CPPUNIT_ASSERT_MESSAGE("message should be UploadProgress", CPPUNIT_ASSERT_MESSAGE(
UploadProgress == message.what); "message should be UploadProgress", UploadProgress == message.what);
[[fallthrough]]; [[fallthrough]];
case UploadProgress: case UploadProgress:
@@ -851,20 +833,18 @@ HttpIntegrationTest::PostTest()
break; break;
case ResponseStarted: case ResponseStarted:
CPPUNIT_ASSERT_MESSAGE("message should be HttpStatus", CPPUNIT_ASSERT_MESSAGE("message should be HttpStatus", HttpStatus == message.what);
HttpStatus == message.what);
CPPUNIT_ASSERT_MESSAGE("message must have UrlEventData::HttpStatusCode data", CPPUNIT_ASSERT_MESSAGE("message must have UrlEventData::HttpStatusCode data",
message.HasInt16(HttpStatusCode)); message.HasInt16(HttpStatusCode));
break; break;
case HttpStatus: case HttpStatus:
CPPUNIT_ASSERT_MESSAGE("message should be HttpFields", CPPUNIT_ASSERT_MESSAGE("message should be HttpFields", HttpFields == message.what);
HttpFields == message.what);
break; break;
case HttpFields: case HttpFields:
CPPUNIT_ASSERT_MESSAGE("message should be DownloadProgress", CPPUNIT_ASSERT_MESSAGE(
DownloadProgress == message.what); "message should be DownloadProgress", DownloadProgress == message.what);
[[fallthrough]]; [[fallthrough]];
case DownloadProgress: case DownloadProgress:
@@ -883,12 +863,12 @@ HttpIntegrationTest::PostTest()
case RequestCompleted: case RequestCompleted:
CPPUNIT_ASSERT_MESSAGE("message must have UrlEventData::Success data", CPPUNIT_ASSERT_MESSAGE("message must have UrlEventData::Success data",
message.HasBool(Success)); message.HasBool(Success));
CPPUNIT_ASSERT_MESSAGE("UrlEventData::Success must be true", CPPUNIT_ASSERT_MESSAGE(
message.GetBool(Success)); "UrlEventData::Success must be true", message.GetBool(Success));
break; break;
default: default:
CPPUNIT_FAIL("Expected DownloadProgress, BytesWritten or HttpStatus " CPPUNIT_FAIL("Expected DownloadProgress, BytesWritten or HttpStatus "
"message"); "message");
} }
break; break;
@@ -17,16 +17,17 @@
using BPrivate::Network::BHttpSession; using BPrivate::Network::BHttpSession;
class HttpProtocolTest: public BTestCase { class HttpProtocolTest : public BTestCase
{
public: public:
HttpProtocolTest(); HttpProtocolTest();
void HttpFieldsTest(); void HttpFieldsTest();
void HttpMethodTest(); void HttpMethodTest();
void HttpRequestTest(); void HttpRequestTest();
void HttpTimeTest(); void HttpTimeTest();
static void AddTests(BTestSuite& suite); static void AddTests(BTestSuite& suite);
}; };
@@ -36,7 +37,7 @@ public:
HttpIntegrationTest(TestServerMode mode); HttpIntegrationTest(TestServerMode mode);
virtual void setUp() override; virtual void setUp() override;
virtual void tearDown() override; virtual void tearDown() override;
void HostAndNetworkFailTest(); void HostAndNetworkFailTest();
void GetTest(); void GetTest();
+43 -61
View File
@@ -23,8 +23,10 @@
namespace { namespace {
template <typename T>
std::string to_string(T value) template<typename T>
std::string
to_string(T value)
{ {
std::ostringstream s; std::ostringstream s;
s << value; s << value;
@@ -32,7 +34,8 @@ std::string to_string(T value)
} }
void exec(const std::vector<std::string>& args) void
exec(const std::vector<std::string>& args)
{ {
const char** argv = new const char*[args.size() + 1]; const char** argv = new const char*[args.size() + 1];
ArrayDeleter<const char*> _(argv); ArrayDeleter<const char*> _(argv);
@@ -47,9 +50,10 @@ void exec(const std::vector<std::string>& args)
// Return the path of a file path relative to this source file. // Return the path of a file path relative to this source file.
std::string TestFilePath(const std::string& relativePath) std::string
TestFilePath(const std::string& relativePath)
{ {
char *testFileSource = strdup(__FILE__); char* testFileSource = strdup(__FILE__);
MemoryDeleter _(testFileSource); MemoryDeleter _(testFileSource);
std::string testSrcDir(::dirname(testFileSource)); std::string testSrcDir(::dirname(testFileSource));
@@ -57,7 +61,7 @@ std::string TestFilePath(const std::string& relativePath)
return testSrcDir + "/" + relativePath; return testSrcDir + "/" + relativePath;
} }
} } // namespace
RandomTCPServerPort::RandomTCPServerPort() RandomTCPServerPort::RandomTCPServerPort()
@@ -70,10 +74,7 @@ RandomTCPServerPort::RandomTCPServerPort()
// kernel. // kernel.
int socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); int socket_fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd == -1) { if (socket_fd == -1) {
fprintf( fprintf(stderr, "ERROR: Unable to create socket: %s\n", strerror(errno));
stderr,
"ERROR: Unable to create socket: %s\n",
strerror(errno));
fInitStatus = B_ERROR; fInitStatus = B_ERROR;
return; return;
} }
@@ -84,18 +85,10 @@ RandomTCPServerPort::RandomTCPServerPort()
// for reuse. // for reuse.
{ {
int reuse = 1; int reuse = 1;
int result = ::setsockopt( int result = ::setsockopt(socket_fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));
socket_fd,
SOL_SOCKET,
SO_REUSEPORT,
&reuse,
sizeof(reuse));
if (result == -1) { if (result == -1) {
fInitStatus = errno; fInitStatus = errno;
fprintf( fprintf(stderr, "ERROR: Unable to set socket options on fd %d: %s\n", socket_fd,
stderr,
"ERROR: Unable to set socket options on fd %d: %s\n",
socket_fd,
strerror(fInitStatus)); strerror(fInitStatus));
return; return;
} }
@@ -106,15 +99,10 @@ RandomTCPServerPort::RandomTCPServerPort()
server_address.sin_family = AF_INET; server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
int bind_result = ::bind( int bind_result = ::bind(
socket_fd, socket_fd, reinterpret_cast<struct sockaddr*>(&server_address), sizeof(server_address));
reinterpret_cast<struct sockaddr*>(&server_address),
sizeof(server_address));
if (bind_result == -1) { if (bind_result == -1) {
fInitStatus = errno; fInitStatus = errno;
fprintf( fprintf(stderr, "ERROR: Unable to bind to loopback interface: %s\n", strerror(fInitStatus));
stderr,
"ERROR: Unable to bind to loopback interface: %s\n",
strerror(fInitStatus));
return; return;
} }
@@ -129,9 +117,7 @@ RandomTCPServerPort::RandomTCPServerPort()
// Now get the port from the socket. // Now get the port from the socket.
socklen_t server_address_length = sizeof(server_address); socklen_t server_address_length = sizeof(server_address);
::getsockname( ::getsockname(
socket_fd, socket_fd, reinterpret_cast<struct sockaddr*>(&server_address), &server_address_length);
reinterpret_cast<struct sockaddr*>(&server_address),
&server_address_length);
fServerPort = ntohs(server_address.sin_port); fServerPort = ntohs(server_address.sin_port);
fInitStatus = B_OK; fInitStatus = B_OK;
@@ -148,19 +134,22 @@ RandomTCPServerPort::~RandomTCPServerPort()
} }
status_t RandomTCPServerPort::InitCheck() const status_t
RandomTCPServerPort::InitCheck() const
{ {
return fInitStatus; return fInitStatus;
} }
int RandomTCPServerPort::FileDescriptor() const int
RandomTCPServerPort::FileDescriptor() const
{ {
return fSocketFd; return fSocketFd;
} }
uint16_t RandomTCPServerPort::Port() const uint16_t
RandomTCPServerPort::Port() const
{ {
return fServerPort; return fServerPort;
} }
@@ -188,7 +177,8 @@ ChildProcess::~ChildProcess()
// The job of this method is to spawn a child process that will later be killed // The job of this method is to spawn a child process that will later be killed
// by the destructor. // by the destructor.
status_t ChildProcess::Start(const std::vector<std::string>& args) status_t
ChildProcess::Start(const std::vector<std::string>& args)
{ {
if (fChildPid != -1) { if (fChildPid != -1) {
return B_ALREADY_RUNNING; return B_ALREADY_RUNNING;
@@ -209,17 +199,11 @@ status_t ChildProcess::Start(const std::vector<std::string>& args)
// If we reach this point we failed to load the Python image. // If we reach this point we failed to load the Python image.
std::ostringstream ostr; std::ostringstream ostr;
for (std::vector<std::string>::const_iterator iter = args.begin(); for (std::vector<std::string>::const_iterator iter = args.begin(); iter != args.end(); ++iter) {
iter != args.end();
++iter) {
ostr << " " << *iter; ostr << " " << *iter;
} }
fprintf( fprintf(stderr, "Unable to spawn `%s': %s\n", ostr.str().c_str(), strerror(errno));
stderr,
"Unable to spawn `%s': %s\n",
ostr.str().c_str(),
strerror(errno));
exit(1); exit(1);
} }
@@ -233,7 +217,8 @@ TestServer::TestServer(TestServerMode mode)
// Start a child testserver.py process with the random TCP port chosen by // Start a child testserver.py process with the random TCP port chosen by
// fPort. // fPort.
status_t TestServer::Start() status_t
TestServer::Start()
{ {
if (fPort.InitCheck() != B_OK) { if (fPort.InitCheck() != B_OK) {
return fPort.InitCheck(); return fPort.InitCheck();
@@ -241,10 +226,7 @@ status_t TestServer::Start()
auto testFilePath = TestFilePath("testserver.py"); auto testFilePath = TestFilePath("testserver.py");
if (::access(testFilePath.data(), R_OK) != 0) { if (::access(testFilePath.data(), R_OK) != 0) {
fprintf( fprintf(stderr, "ERROR: No access to the test server script at: %s\n", testFilePath.data());
stderr,
"ERROR: No access to the test server script at: %s\n",
testFilePath.data());
return B_IO_ERROR; return B_IO_ERROR;
} }
@@ -272,17 +254,18 @@ status_t TestServer::Start()
} }
BUrl TestServer::BaseUrl() const BUrl
TestServer::BaseUrl() const
{ {
std::string scheme; std::string scheme;
switch(fMode) { switch (fMode) {
case TestServerMode::Http: case TestServerMode::Http:
scheme = "http://"; scheme = "http://";
break; break;
case TestServerMode::Https: case TestServerMode::Https:
scheme = "https://"; scheme = "https://";
break; break;
} }
std::string port_string = to_string(fPort.Port()); std::string port_string = to_string(fPort.Port());
@@ -293,7 +276,8 @@ BUrl TestServer::BaseUrl() const
// Start a child proxy.py process using the random TCP port chosen by fPort. // Start a child proxy.py process using the random TCP port chosen by fPort.
status_t TestProxyServer::Start() status_t
TestProxyServer::Start()
{ {
if (fPort.InitCheck() != B_OK) { if (fPort.InitCheck() != B_OK) {
return fPort.InitCheck(); return fPort.InitCheck();
@@ -301,10 +285,7 @@ status_t TestProxyServer::Start()
auto testFilePath = TestFilePath("proxy.py"); auto testFilePath = TestFilePath("proxy.py");
if (::access(testFilePath.data(), R_OK) != 0) { if (::access(testFilePath.data(), R_OK) != 0) {
fprintf( fprintf(stderr, "ERROR: No access to the test server script at: %s\n", testFilePath.data());
stderr,
"ERROR: No access to the test server script at: %s\n",
testFilePath.data());
return B_IO_ERROR; return B_IO_ERROR;
} }
@@ -327,7 +308,8 @@ status_t TestProxyServer::Start()
} }
uint16_t TestProxyServer::Port() const uint16_t
TestProxyServer::Port() const
{ {
return fPort.Port(); return fPort.Port();
} }
+30 -25
View File
@@ -16,30 +16,33 @@
// Binds to a random unused TCP port. // Binds to a random unused TCP port.
class RandomTCPServerPort { class RandomTCPServerPort
{
public: public:
RandomTCPServerPort(); RandomTCPServerPort();
~RandomTCPServerPort(); ~RandomTCPServerPort();
status_t InitCheck() const; status_t InitCheck() const;
int FileDescriptor() const; int FileDescriptor() const;
uint16_t Port() const; uint16_t Port() const;
private: private:
status_t fInitStatus; status_t fInitStatus;
int fSocketFd; int fSocketFd;
uint16_t fServerPort; uint16_t fServerPort;
}; };
class ChildProcess { class ChildProcess
{
public: public:
ChildProcess(); ChildProcess();
~ChildProcess(); ~ChildProcess();
status_t Start(const std::vector<std::string>& args);
status_t Start(const std::vector<std::string>& args);
private: private:
pid_t fChildPid; pid_t fChildPid;
}; };
@@ -49,28 +52,30 @@ enum class TestServerMode {
}; };
class TestServer { class TestServer
{
public: public:
TestServer(TestServerMode mode); TestServer(TestServerMode mode);
status_t Start(); status_t Start();
BUrl BaseUrl() const; BUrl BaseUrl() const;
private: private:
TestServerMode fMode; TestServerMode fMode;
ChildProcess fChildProcess; ChildProcess fChildProcess;
RandomTCPServerPort fPort; RandomTCPServerPort fPort;
}; };
class TestProxyServer { class TestProxyServer
{
public: public:
status_t Start(); status_t Start();
uint16_t Port() const; uint16_t Port() const;
private: private:
ChildProcess fChildProcess; ChildProcess fChildProcess;
RandomTCPServerPort fPort; RandomTCPServerPort fPort;
}; };