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