NetServices: Implement asynchronous status update messages.
The integration PostTest has a basic test that the expected messages are sent and have the expected data fields. The gist is documented in book.dox. To do are the messages around SSL. However, that functionality is also not implemented yet, so there is nothing to send. Change-Id: Ib8f36ed32f9854d643d8256338b71af7067059f0
This commit is contained in:
parent
b74e852fd9
commit
60355daec9
@ -603,6 +603,177 @@ snooze_until(time - Latency(), B_SYSTEM_TIMEBASE);
|
||||
application to <code>libnetservices2.a</code>. The new API is only
|
||||
available for modern platforms (x86 and x86_64), and not for the legacy
|
||||
platform (x86_gcc2). The compiler needs to support C++17 or higher.
|
||||
|
||||
<h3>Asynchronous handling of the result.</h3>
|
||||
|
||||
In GUI applications, networking operations are often triggered by a user action. For example,
|
||||
downloading a file will be initiated by the user clicking a button. When you initiate that
|
||||
action in the window's thread, and you block the message loop until the request is finished,
|
||||
the user will be left with a non-responsive UI. That is why one would usually run a network
|
||||
request asynchronously. And instead of checking the status every few CPU cycles, you'd want
|
||||
to be proactively informed when something important happens, like the progress of the download
|
||||
or a signal when the request is finished.
|
||||
|
||||
The Network Services kit support using the Haiku API's Looper and Handler system to keep you up
|
||||
to date about relevant events that happen to the requests.
|
||||
|
||||
The following messages are available for all requests (HTTP and other). The messages below are
|
||||
in the order that they will arrive (when applicable).
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Message Constant</th>
|
||||
<th>Description</th>
|
||||
<th>Applies to</th>
|
||||
<th> Additional Data</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::HostNameResolved "UrlEvent::HostNameResolved"</td>
|
||||
<td>
|
||||
The hostname has been resolved. This message is even sent when you set an
|
||||
IP-address in the URL object
|
||||
</td>
|
||||
<td>All protocols that use network connections.</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::HostName "UrlEventData::HostName"
|
||||
\ref BString
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::ConnectionOpened "UrlEvent::ConnectionOpened"</td>
|
||||
<td>
|
||||
The connection to the remote server is opened. After this event, data will be
|
||||
written.
|
||||
</td>
|
||||
<td>All protocols that use network connections.</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::UploadProgress "UrlEvent::UploadProgress"</td>
|
||||
<td>
|
||||
If there is a request body to be sent, this informs you of the progress. When the
|
||||
total size of the request body is known, this will be part of the message.
|
||||
</td>
|
||||
<td>
|
||||
All protocols that use network connections and support writing data to the server
|
||||
(like HTTP(S)).
|
||||
</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::NumBytes "UrlEventData::NumBytes"
|
||||
\c int64 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::TotalBytes "UrlEventData::TotalBytes"
|
||||
\c int64 (optional)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::ResponseStarted "UrlEvent::ResponseStarted"</td>
|
||||
<td>The server has started transmitting the response.</td>
|
||||
<td>All Protocols</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32 <br/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::HttpRedirect "UrlEvent::HttpRedirect</td>
|
||||
<td>
|
||||
The network services kit is handling a HTTP redirect. The request will be repeated
|
||||
for a new URL.
|
||||
</td>
|
||||
<td>HTTP/HTTPS</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::HttpRedirectUrl
|
||||
"UrlEventData::HttpRedirectUrl" \ref BString
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::HttpStatus "UrlEvent::HttpStatus"</td>
|
||||
<td>
|
||||
The response status is available. This means it can also be accessed through
|
||||
\ref BPrivate::Network::BHttpResult::Status() "BHttpResult::Status()" without
|
||||
blocking the system.
|
||||
</td>
|
||||
<td>HTTP/HTTPS</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::HttpStatusCode "UrlEventData::HttpStatusCode"
|
||||
\c int16
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::HttpFields "UrlEvent::HttpFields"</td>
|
||||
<td>
|
||||
The HTTP header block has been fully received, and the HTTP fields can be accessed
|
||||
using \ref BPrivate::Network::BHttpResult::Fields() "BHttpResult::Fields()" without
|
||||
blocking the system.
|
||||
</td>
|
||||
<td>HTTP/HTTPS</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::DownloadProgress "UrlEvent::DownloadProgress"</td>
|
||||
<td>
|
||||
If there is a response body to be received, this informs you of the progress. If
|
||||
the total size of the body is known, this will be included in the message as well.
|
||||
</td>
|
||||
<td>All protocols that use network connections.</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::NumBytes "UrlEventData::NumBytes"
|
||||
\c int64 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::TotalBytes "UrlEventData::TotalBytes"
|
||||
\c int64 (optional)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::BytesWritten "UrlEvent::BytesWritten"</td>
|
||||
<td>
|
||||
An interim update on how many bytes have been written to the target. This message
|
||||
is only sent when you supplied a custom target to store the body of the request in.
|
||||
Note that the number of bytes written to the target may differ from the network
|
||||
transfer size, due to compression in the protocol.
|
||||
</td>
|
||||
<td>All protocols.</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::NumBytes "UrlEventData::NumBytes"
|
||||
\c int64
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::RequestCompleted "UrlEvent::RequestCompleted"</td>
|
||||
<td>
|
||||
The request is completed and all the data is written to the target, or there was
|
||||
an error.
|
||||
</td>
|
||||
<td>All protocols.</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::Success "UrlEventData::Success" \c bool
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\ref BPrivate::Network::UrlEvent::DebugMessage "UrlEvent::DebugMessage"</td>
|
||||
<td>
|
||||
Additional debug information on the request. This is enabled or disabled per
|
||||
request. See the details in the protocol description.
|
||||
</td>
|
||||
<td>All protocols.</td>
|
||||
<td>
|
||||
\ref BPrivate::Network::UrlEventData::Id "UrlEventData::Id" \c int32 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::DebugType "UrlEventData::DebugType"
|
||||
\c int32 <br/>
|
||||
\ref BPrivate::Network::UrlEventData::DebugMessage "UrlEventData::DebugMessage"
|
||||
\ref BString
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
*/
|
||||
#endif
|
||||
|
||||
|
@ -219,6 +219,71 @@ namespace Network {
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::HttpStatus
|
||||
\brief The HTTP status code has been received, and can be accessed through the result object.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::HttpFields
|
||||
\brief The HTTP header block has been received, and the status and fields can be accessed
|
||||
through the result object.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::CertificateError
|
||||
\brief There was an error communicating with the server because of an SSL certificate issue.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::HttpRedirect
|
||||
\brief The Http request was redirected, and this redirect was handled by the kit.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::HttpStatusCode
|
||||
\brief An \c int16 value that contains the HTTP status code for this request.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::SSLCertificate
|
||||
\brief The SSL certificate that causes the issue.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::SSLMessage
|
||||
\brief A \ref BString message about the error while processing the SSL certificate.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::HttpRedirectUrl
|
||||
\brief A \ref BString with the URL that the HTTP request was redirected to.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
} // namespace Network
|
||||
|
||||
} // namespace BPrivate
|
||||
|
@ -248,6 +248,156 @@ namespace Network {
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\fn BString encode_to_base64(const BString& string)
|
||||
\brief Utility function that encodes a \a string to base64 and returns the result.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\namespace BPrivate::Network::UrlEvent
|
||||
\brief Contains the message constants that are sent by the various protocols.
|
||||
|
||||
Please see the \link netservices kit documentation \endlink for details which messages are sent
|
||||
at which stage, and what data they contain.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::HostNameResolved
|
||||
\brief The hostname for the request is resolved.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::ConnectionOpened
|
||||
\brief The connection for the request is opened and the request will be sent.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::UploadProgress
|
||||
\brief There is progress sending the body for the request.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::ResponseStarted
|
||||
\brief The request was sent, and the response is now incoming.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::DownloadProgress
|
||||
\brief There is progress receiving the body of the request.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::BytesWritten
|
||||
\brief There are bytes written to the target of the body.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::RequestCompleted
|
||||
\brief The request was completed.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var UrlEvent::DebugMessage
|
||||
\brief There is a debug message for a request or for a protocol.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\namespace BPrivate::Network::UrlEventData
|
||||
\brief Contains the names of the data in the messages that are sent by the various protocols.
|
||||
|
||||
Please see the \link netservices kit documentation \endlink for details which messages are sent
|
||||
at which stage, and what data they contain.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::Id
|
||||
\brief An \c int32 that identifies the request the message pertains to.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::HostName
|
||||
\brief A \ref BString that represents the hostname that was resolved.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::NumBytes
|
||||
\brief An \c int64/off_t represening the number of bytes transferred to now.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::TotalBytes
|
||||
\brief An \c int64/off_t representing the total number of bytes that will be sent/received.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::Success
|
||||
\brief A \c bool that indicates whether an activity was succesful.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::DebugType
|
||||
\brief An \c int32 representing a debug type constant.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\var const char* UrlEventData::DebugMessage
|
||||
\brief A \ref BString that contains the debug message.
|
||||
|
||||
\since Haiku R1
|
||||
*/
|
||||
|
||||
|
||||
} // namespace Network
|
||||
|
||||
} // namespace BPrivate
|
||||
|
@ -48,6 +48,23 @@ private:
|
||||
};
|
||||
|
||||
|
||||
namespace UrlEvent {
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
|
||||
} // namespace BPrivate
|
||||
|
@ -78,6 +78,30 @@ private:
|
||||
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'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -36,18 +36,6 @@
|
||||
using namespace BPrivate::Network;
|
||||
|
||||
|
||||
/*!
|
||||
\brief Size of subsequent reads
|
||||
|
||||
Curl 7.82.0 sets the default to 512 kB (524288 bytes)
|
||||
https://github.com/curl/curl/blob/64db5c575d9c5536bd273a890f50777ad1ca7c13/include/curl/curl.h#L232
|
||||
Libsoup sets it to 8 kB, though the buffer may grow beyond that if there are leftover bytes.
|
||||
The absolute maximum seems to be 64 kB (HEADER_SIZE_LIMIT)
|
||||
https://gitlab.gnome.org/GNOME/libsoup/-/blob/master/libsoup/http1/soup-client-message-io-http1.c#L58
|
||||
The previous iteration set it to 4 kB, though the input buffer would dynamically grow.
|
||||
*/
|
||||
static constexpr ssize_t kMaxReadSize = 8192;
|
||||
|
||||
/*!
|
||||
\brief Maximum size of the HTTP Header lines of the message.
|
||||
|
||||
@ -88,7 +76,7 @@ public:
|
||||
// Result Helpers
|
||||
std::shared_ptr<HttpResultPrivate>
|
||||
Result() { return fResult; }
|
||||
void SetError(std::exception_ptr e) { fResult->SetError(e); }
|
||||
void SetError(std::exception_ptr e);
|
||||
|
||||
// Operational methods
|
||||
void ResolveHostName();
|
||||
@ -102,6 +90,10 @@ public:
|
||||
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;
|
||||
|
||||
private:
|
||||
BHttpRequest fRequest;
|
||||
|
||||
@ -332,14 +324,6 @@ BHttpSession::Impl::ControlThreadFunc(void* arg)
|
||||
} catch (...) {
|
||||
request.SetError(std::current_exception());
|
||||
}
|
||||
/* TODO
|
||||
if (request.observer.IsValid()) {
|
||||
BMessage msg(UrlEvent::RequestCompleted);
|
||||
msg.AddInt32(UrlEventData::Id, request.result->id);
|
||||
msg.AddBool(UrlEventData::Success, false);
|
||||
request.observer.SendMessage(&msg);
|
||||
}
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
throw BRuntimeError(__PRETTY_FUNCTION__,
|
||||
@ -449,13 +433,11 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
|
||||
auto& request = data->connectionMap.find(item.object)->second;
|
||||
std::cout << "DataThreadFunc() [" << request.Id() << "] ready for receiving the response" << std::endl;
|
||||
auto finished = false;
|
||||
auto success = false;
|
||||
try {
|
||||
if (request.CanCancel())
|
||||
finished = true;
|
||||
else
|
||||
finished = request.ReceiveResult();
|
||||
success = true;
|
||||
} catch (const Redirect& r) {
|
||||
// Request is redirected, send back to the controlThread
|
||||
std::cout << "DataThreadFunc() [" << request.Id() << "] will be redirected to " << r.url.UrlString().String() << std::endl;
|
||||
@ -474,14 +456,6 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
|
||||
if (finished) {
|
||||
// Clean up finished requests; including redirected requests
|
||||
request.Disconnect();
|
||||
/* TODO: implement this somewhere else; only notify if we are not redirecting
|
||||
if (request.observer.IsValid()) {
|
||||
BMessage msg(UrlEvent::RequestCompleted);
|
||||
msg.AddInt32(UrlEventData::Id, request.result->id);
|
||||
msg.AddBool(UrlEventData::Success, success);
|
||||
request.observer.SendMessage(&msg);
|
||||
}
|
||||
*/
|
||||
data->connectionMap.erase(item.object);
|
||||
resizeObjectList = true;
|
||||
}
|
||||
@ -492,13 +466,6 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
|
||||
} catch (...) {
|
||||
request.SetError(std::current_exception());
|
||||
}
|
||||
/* TODO: move to BHttpSession::Request::SetError???
|
||||
if (request.observer.IsValid()) {
|
||||
BMessage msg(UrlEvent::RequestCompleted);
|
||||
msg.AddInt32(UrlEventData::Id, request.result->id);
|
||||
msg.AddBool(UrlEventData::Success, false);
|
||||
request.observer.SendMessage(&msg);
|
||||
} */
|
||||
data->connectionMap.erase(item.object);
|
||||
resizeObjectList = true;
|
||||
} else if ((item.events & EVENT_CANCELLED) == EVENT_CANCELLED) {
|
||||
@ -509,13 +476,6 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
|
||||
} catch (...) {
|
||||
request.SetError(std::current_exception());
|
||||
}
|
||||
/* TODO: move to SetError()?
|
||||
if (request.observer.IsValid()) {
|
||||
BMessage msg(UrlEvent::RequestCompleted);
|
||||
msg.AddInt32(UrlEventData::Id, request.result->id);
|
||||
msg.AddBool(UrlEventData::Success, false);
|
||||
request.observer.SendMessage(&msg);
|
||||
}*/
|
||||
data->connectionMap.erase(item.object);
|
||||
resizeObjectList = true;
|
||||
} else if (item.events == 0) {
|
||||
@ -562,13 +522,6 @@ BHttpSession::Impl::DataThreadFunc(void* arg)
|
||||
} catch (...) {
|
||||
it->second.SetError(std::current_exception());
|
||||
}
|
||||
/* TODO: should be part of SetError()
|
||||
if (it->second.observer.IsValid()) {
|
||||
BMessage msg(UrlEvent::RequestCompleted);
|
||||
msg.AddInt32(UrlEventData::Id, it->second.result->id);
|
||||
msg.AddBool(UrlEventData::Success, false);
|
||||
it->second.observer.SendMessage(&msg);
|
||||
}*/
|
||||
}
|
||||
} else {
|
||||
std::cout << "DataThreadFunc(): Unknown reason that the dataQueueSem is deleted" << std::endl;
|
||||
@ -654,13 +607,25 @@ BHttpSession::Request::Request(Request& original, const BHttpSession::Redirect&
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\brief Helper that sets the error in the result to \a e and notifies the listeners.
|
||||
*/
|
||||
void
|
||||
BHttpSession::Request::SetError(std::exception_ptr e)
|
||||
{
|
||||
fResult->SetError(e);
|
||||
SendMessage(UrlEvent::RequestCompleted, [](BMessage& msg){
|
||||
msg.AddBool(UrlEventData::Success, false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\brief Resolve the hostname for a request
|
||||
*/
|
||||
void
|
||||
BHttpSession::Request::ResolveHostName()
|
||||
{
|
||||
std::cout << "BHttpSession::Request::ResolveHostName() [" << Id() << "] for URL: " << fRequest.Url().UrlString() << std::endl;
|
||||
int port;
|
||||
if (fRequest.Url().HasPort())
|
||||
port = fRequest.Url().Port();
|
||||
@ -674,7 +639,10 @@ BHttpSession::Request::ResolveHostName()
|
||||
throw BNetworkRequestError("BNetworkAddress::SetTo()",
|
||||
BNetworkRequestError::HostnameError, status);
|
||||
}
|
||||
std::cout << "ResolveHostName() [" << Id() << "] Hostname resolved" << std::endl;
|
||||
|
||||
SendMessage(UrlEvent::HostNameResolved, [this](BMessage& msg) {
|
||||
msg.AddString(UrlEventData::HostName, fRequest.Url().Host());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -684,8 +652,6 @@ BHttpSession::Request::ResolveHostName()
|
||||
void
|
||||
BHttpSession::Request::OpenConnection()
|
||||
{
|
||||
std::cout << "OpenConnection() [" << Id() << "] Opening Connection" << std::endl;
|
||||
|
||||
// Set up the socket
|
||||
if (fRequest.Url().Protocol() == "https") {
|
||||
// To do: secure socket with callbacks to check certificates
|
||||
@ -711,7 +677,7 @@ BHttpSession::Request::OpenConnection()
|
||||
if (fcntl(fSocket->Socket(), F_SETFL, flags | O_NONBLOCK) != 0)
|
||||
throw BRuntimeError("fcntl()", "Error setting non-blocking flag on socket");
|
||||
|
||||
// TODO: inform the listeners that the connection was opened.
|
||||
SendMessage(UrlEvent::ConnectionOpened);
|
||||
|
||||
fRequestStatus = Connected;
|
||||
}
|
||||
@ -738,7 +704,14 @@ BHttpSession::Request::TransferRequest()
|
||||
auto [currentBytesWritten, totalBytesWritten, totalSize, complete]
|
||||
= fDataStream->Transfer(fSocket.get());
|
||||
|
||||
// TODO: notification
|
||||
// TODO: make nicer after replacing transferinfo
|
||||
off_t vTotalBytesWritten = totalBytesWritten;
|
||||
off_t vTotalSize = totalSize;
|
||||
SendMessage(UrlEvent::UploadProgress, [vTotalBytesWritten, vTotalSize](BMessage& msg) {
|
||||
msg.AddInt64(UrlEventData::NumBytes, vTotalBytesWritten);
|
||||
msg.AddInt64(UrlEventData::TotalBytes, vTotalSize);
|
||||
// TODO: handle case with unknown total size
|
||||
});
|
||||
|
||||
if (complete)
|
||||
fRequestStatus = RequestSent;
|
||||
@ -756,17 +729,11 @@ BHttpSession::Request::TransferRequest()
|
||||
bool
|
||||
BHttpSession::Request::ReceiveResult()
|
||||
{
|
||||
bool receiveEnd = false;
|
||||
|
||||
// First: stream data from the socket
|
||||
auto bytesRead = fBuffer.ReadFrom(fSocket.get());
|
||||
|
||||
if (bytesRead == B_WOULD_BLOCK || bytesRead == B_INTERRUPTED) {
|
||||
return false;
|
||||
} else if (bytesRead == 0) {
|
||||
// This may occur when the connection is closed (and the transfer is finished).
|
||||
// Later on, there is a check to determine whether the request is finished as expected.
|
||||
receiveEnd = true;
|
||||
}
|
||||
|
||||
std::cout << "ReceiveResult() [" << Id() << "] read " << bytesRead << " from socket" << std::endl;
|
||||
@ -780,6 +747,12 @@ BHttpSession::Request::ReceiveResult()
|
||||
"Read function called for object that is not yet connected or sent");
|
||||
case RequestSent:
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
BHttpStatus status;
|
||||
if (fParser.ParseStatus(fBuffer, status)) {
|
||||
// the status headers are now received, decide what to do next
|
||||
@ -824,6 +797,9 @@ BHttpSession::Request::ReceiveResult()
|
||||
|
||||
if (!fRedirectStatus) {
|
||||
// we are not redirecting and there is no error, so inform listeners
|
||||
SendMessage(UrlEvent::HttpStatus, [&status](BMessage& msg) {
|
||||
msg.AddInt16(UrlEventData::HttpStatusCode, status.code);
|
||||
});
|
||||
fResult->SetStatus(std::move(status));
|
||||
}
|
||||
|
||||
@ -868,10 +844,18 @@ BHttpSession::Request::ReceiveResult()
|
||||
if (!redirect.url.IsValid())
|
||||
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError);
|
||||
|
||||
// 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, fRedirectStatus->code);
|
||||
});
|
||||
fResult->SetStatus(std::move(fRedirectStatus.value()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -915,14 +899,18 @@ BHttpSession::Request::ReceiveResult()
|
||||
throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError);
|
||||
}
|
||||
|
||||
// TODO: move headers to the result and inform listener
|
||||
// Move headers to the result and inform listener
|
||||
fResult->SetFields(std::move(fFields));
|
||||
SendMessage(UrlEvent::HttpFields);
|
||||
fRequestStatus = HeadersReceived;
|
||||
|
||||
if (fRequest.Method() == BHttpMethod::Head || fNoContent) {
|
||||
// HEAD requests and requests with status 204 (No content) are finished
|
||||
std::cout << "ReceiveResult() [" << Id() << "] Request is completing without content" << std::endl;
|
||||
fResult->SetBody();
|
||||
SendMessage(UrlEvent::RequestCompleted, [](BMessage& msg) {
|
||||
msg.AddBool(UrlEventData::Success, true);
|
||||
});
|
||||
fRequestStatus = ContentReceived;
|
||||
return true;
|
||||
}
|
||||
@ -930,16 +918,35 @@ BHttpSession::Request::ReceiveResult()
|
||||
}
|
||||
case HeadersReceived:
|
||||
{
|
||||
bytesRead = fParser.ParseBody(fBuffer, [this](const std::byte* buffer, size_t size) {
|
||||
return fResult->WriteToBody(buffer, size);
|
||||
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;
|
||||
});
|
||||
|
||||
std::cout << "ReceiveResult() [" << Id() << "] body bytes current read/total received/total expected: " <<
|
||||
bytesRead << "/" << fParser.BodyBytesTransferred() << "/" << fParser.BodyBytesTotal().value_or(0) << std::endl;
|
||||
|
||||
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()) {
|
||||
std::cout << "ReceiveResult() [" << Id() << "] received all body bytes: " << fParser.BodyBytesTransferred() << std::endl;
|
||||
fResult->SetBody();
|
||||
SendMessage(UrlEvent::RequestCompleted, [](BMessage& msg) {
|
||||
msg.AddBool(UrlEventData::Success, true);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -962,6 +969,34 @@ void
|
||||
BHttpSession::Request::Disconnect() noexcept
|
||||
{
|
||||
fSocket->Disconnect();
|
||||
|
||||
// TODO: inform listeners that the request has ended
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\brief Send a message to the observer, if one is present
|
||||
|
||||
\param what The code of the message to be sent
|
||||
\param dataFunc Optional function that adds additional data to the message.
|
||||
*/
|
||||
void
|
||||
BHttpSession::Request::SendMessage(uint32 what, std::function<void (BMessage&)> dataFunc) const
|
||||
{
|
||||
if (fObserver.IsValid()) {
|
||||
BMessage msg(what);
|
||||
msg.AddInt32(UrlEventData::Id, fResult->id);
|
||||
if (dataFunc)
|
||||
dataFunc(msg);
|
||||
fObserver.SendMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -- Message constants
|
||||
|
||||
|
||||
namespace BPrivate::Network::UrlEventData {
|
||||
const char* HttpStatusCode = "url:httpstatuscode";
|
||||
const char* SSLCertificate = "url:sslcertificate";
|
||||
const char* SSLMessage = "url:sslmessage";
|
||||
const char* HttpRedirectUrl = "url:httpredirecturl";
|
||||
}
|
||||
|
@ -198,6 +198,18 @@ 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";
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -- Private functions and data
|
||||
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <HttpResult.h>
|
||||
#include <HttpStream.h>
|
||||
#include <HttpTime.h>
|
||||
#include <Looper.h>
|
||||
#include <NetServicesDefs.h>
|
||||
#include <Url.h>
|
||||
|
||||
@ -432,6 +433,21 @@ HttpProtocolTest::AddTests(BTestSuite& parent)
|
||||
}
|
||||
|
||||
|
||||
// Observer test
|
||||
|
||||
#include <iostream>
|
||||
class ObserverHelper : public BLooper {
|
||||
public:
|
||||
ObserverHelper()
|
||||
: BLooper("ObserverHelper") {}
|
||||
|
||||
void MessageReceived(BMessage* msg) override {
|
||||
messages.emplace_back(*msg);
|
||||
}
|
||||
|
||||
std::vector<BMessage> messages;
|
||||
};
|
||||
|
||||
|
||||
// HttpIntegrationTest
|
||||
|
||||
@ -760,6 +776,9 @@ static BString kExpectedPostBody
|
||||
void
|
||||
HttpIntegrationTest::PostTest()
|
||||
{
|
||||
using namespace BPrivate::Network::UrlEvent;
|
||||
using namespace BPrivate::Network::UrlEventData;
|
||||
|
||||
auto postBody = std::make_unique<BMallocIO>();
|
||||
postBody->Write(kPostText.String(), kPostText.Length());
|
||||
postBody->Seek(0, SEEK_SET);
|
||||
@ -767,8 +786,106 @@ HttpIntegrationTest::PostTest()
|
||||
request.SetMethod(BHttpMethod::Post);
|
||||
request.SetRequestBody(std::move(postBody), "text/plain", kPostText.Length());
|
||||
|
||||
auto result = fSession.Execute(std::move(request));
|
||||
auto observer = new ObserverHelper();
|
||||
observer->Run();
|
||||
|
||||
auto result = fSession.Execute(std::move(request), nullptr, BMessenger(observer));
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(kExpectedPostBody.Length(), result.Body().text.Length());
|
||||
CPPUNIT_ASSERT(result.Body().text == kExpectedPostBody);
|
||||
|
||||
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);
|
||||
|
||||
uint32 previousMessage = 0;
|
||||
for (const auto& message: observer->messages) {
|
||||
auto id = observer->messages[0].GetInt32(BPrivate::Network::UrlEventData::Id, -1);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("message Id does not match", result.Identity(), id);
|
||||
|
||||
switch(previousMessage) {
|
||||
case 0:
|
||||
CPPUNIT_ASSERT_MESSAGE("message should be HostNameResolved",
|
||||
HostNameResolved == message.what);
|
||||
break;
|
||||
|
||||
case HostNameResolved:
|
||||
CPPUNIT_ASSERT_MESSAGE("message should be ConnectionOpened",
|
||||
ConnectionOpened == message.what);
|
||||
break;
|
||||
|
||||
case ConnectionOpened:
|
||||
CPPUNIT_ASSERT_MESSAGE("message should be UploadProgress",
|
||||
UploadProgress == message.what);
|
||||
[[fallthrough]];
|
||||
|
||||
case UploadProgress:
|
||||
switch (message.what) {
|
||||
case UploadProgress:
|
||||
CPPUNIT_ASSERT_MESSAGE("message must have UrlEventData::NumBytes data",
|
||||
message.HasInt64(NumBytes));
|
||||
CPPUNIT_ASSERT_MESSAGE("message must have UrlEventData::TotalBytes data",
|
||||
message.HasInt64(TotalBytes));
|
||||
CPPUNIT_ASSERT_MESSAGE("UrlEventData::TotalBytes size does not match",
|
||||
kPostText.Length() == message.GetInt64(TotalBytes, 0));
|
||||
break;
|
||||
case ResponseStarted:
|
||||
break;
|
||||
default:
|
||||
CPPUNIT_FAIL("Expected UploadProgress or ResponseStarted message");
|
||||
}
|
||||
break;
|
||||
|
||||
case ResponseStarted:
|
||||
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);
|
||||
break;
|
||||
|
||||
case HttpFields:
|
||||
CPPUNIT_ASSERT_MESSAGE("message should be DownloadProgress",
|
||||
DownloadProgress == message.what);
|
||||
[[fallthrough]];
|
||||
|
||||
case DownloadProgress:
|
||||
case BytesWritten:
|
||||
switch (message.what) {
|
||||
case DownloadProgress:
|
||||
CPPUNIT_ASSERT_MESSAGE("message must have UrlEventData::NumBytes data",
|
||||
message.HasInt64(NumBytes));
|
||||
CPPUNIT_ASSERT_MESSAGE("message must have UrlEventData::TotalBytes data",
|
||||
message.HasInt64(TotalBytes));
|
||||
break;
|
||||
case BytesWritten:
|
||||
CPPUNIT_ASSERT_MESSAGE("message must have UrlEventData::NumBytes data",
|
||||
message.HasInt64(NumBytes));
|
||||
break;
|
||||
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));
|
||||
break;
|
||||
default:
|
||||
CPPUNIT_FAIL("Expected DownloadProgress, BytesWritten or HttpStatus "
|
||||
"message");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
CPPUNIT_FAIL("Unexpected message");
|
||||
}
|
||||
previousMessage = message.what;
|
||||
}
|
||||
|
||||
observer->Quit();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user