4#include "Stroika/Frameworks/StroikaPreComp.h"
12#include "Stroika/Foundation/Characters/String2Int.h"
13#include "Stroika/Foundation/DataExchange/BadFormatException.h"
15#if qStroika_Foundation_DataExchange_XML_SupportDOM
16#include "Stroika/Foundation/DataExchange/XML/DOM.h"
19#include "Stroika/Foundation/Execution/Exceptions.h"
22#include "Stroika/Foundation/IO/Network/ConnectionOrientedStreamSocket.h"
24#include "Stroika/Foundation/IO/Network/HTTP/Status.h"
25#include "Stroika/Foundation/IO/Network/Transfer/Connection.h"
34using namespace Stroika::Frameworks;
35using namespace Stroika::Frameworks::UPnP;
36using namespace Stroika::Frameworks::UPnP::GENA;
47 return h.fValue.Trim ();
54 optional<Time::DurationSeconds> Timeout_ (
const optional<String>& timeout)
56 if (timeout and timeout->StartsWith (
"Second-"sv, eCaseInsensitive)) {
57 if (int64_t seconds = String2Int<int64_t> (timeout->SubString (7)); seconds > 0) {
69#if qStroika_Foundation_DataExchange_XML_SupportDOM
70 using namespace DataExchange::XML::DOM;
72 if (Element::Ptr propertySet = doc.GetRootElement (); propertySet !=
nullptr) {
73 for (
const Element::Ptr& property : propertySet.GetChildElements ()) {
74 for (
const Element::Ptr& variable : property.GetChildElements ()) {
75 result.
Add (variable.GetName ().fName, variable.GetValue ());
89class Subscriber::Rep_ {
91 Rep_ (
const URI& eventSubURL,
const URI& callbackURL,
const function<
void (
const Event&)>& onEvent,
const Options& options)
92 : fEventSubURL_{eventSubURL}
93 , fCallbackURL_{callbackURL}
106#if not qStroika_Foundation_DataExchange_XML_SupportDOM
109 lock_guard l{fLifecycleMutex_};
115 auto [sid, timeout] = Subscribe_ (nullopt);
123 lock_guard r{fRenewalMutex_};
126 RenewIn_ (fGranted_ / 2);
130 lock_guard l{fLifecycleMutex_};
135 optional<IntervalTimer::TimerID> renewal;
137 lock_guard r{fRenewalMutex_};
139 renewal = exchange (fRenewal_, nullopt);
142 IntervalTimer::Manager::sThe.Remove (*renewal);
144 if (optional<String> sid = fSID_.load ()) {
149 DbgTrace (
"GENA UNSUBSCRIBE {} failed (it lapses at its TIMEOUT): {}"_f, *sid, current_exception ());
152 fSID_.store (nullopt);
164 r.fOverrideHeaders = headers;
165 return c.SendAndThrowOnFailure (r);
168 pair<String, Time::DurationSeconds> Subscribe_ (
const optional<String>& sid)
170 const String timeout =
"Second-{}"_f(std::llround (fOptions_.fTimeout.count ()));
172 Send_ (
"SUBSCRIBE"sv,
174 :
Mapping<String, String>{{
"CALLBACK"sv,
"<{}>"_f(fCallbackURL_)}, {
"NT"sv,
"upnp:event"sv}, {
"TIMEOUT"sv, timeout}});
175 optional<String> newSID = Header_ (r,
"SID"sv);
179 fGranted_ = Timeout_ (Header_ (r,
"TIMEOUT"sv)).value_or (fOptions_.fTimeout);
180 return {*newSID, fGranted_};
185 lock_guard l{fRenewalMutex_};
187 fRenewal_ = IntervalTimer::Manager::sThe.AddOneShot ([
this] () { Renew_ (); }, after);
194 auto [sid, timeout] = Subscribe_ (fSID_.load ());
196 RenewIn_ (timeout / 2);
200 DbgTrace (
"GENA renewal of {} failed: {} - subscribing again"_f, fSID_.load (), current_exception ());
203 fSID_.store (nullopt);
204 auto [sid, timeout] = Subscribe_ (nullopt);
206 RenewIn_ (timeout / 2);
209 DbgTrace (
"GENA SUBSCRIBE to {} failed: {} - trying again later"_f, fEventSubURL_, current_exception ());
210 RenewIn_ (kRetryAfter_);
215 const URI fEventSubURL_;
216 const URI fCallbackURL_;
217 const function<void (
const Event&)> fOnEvent_;
218 const Options fOptions_;
219 mutex fLifecycleMutex_;
220 atomic<bool> fActive_{
false};
223 mutex fRenewalMutex_;
224 bool fStopped_{
true};
225 optional<IntervalTimer::TimerID> fRenewal_;
234 : fRep_{make_unique<Rep_> (eventSubURL, callbackURL, onEvent, options)}
252 return fRep_->fSID_.load ();
258 WebServer::Response& response = m.
rwResponse ();
259 const optional<String> nt = request.
headers ().LookupOne (
"NT"sv);
260 const optional<String> nts = request.
headers ().LookupOne (
"NTS"sv);
261 if (not nt or not nts) {
262 response.status = HTTP::StatusCodes::kBadRequest;
265 const optional<String> sid = request.
headers ().LookupOne (
"SID"sv);
266 const optional<String> current = fRep_->fSID_.load ();
267 if (not fRep_->fActive_ or nt !=
"upnp:event"sv or nts !=
"upnp:propchange"sv or (current and sid != current)) {
268 response.status = HTTP::StatusCodes::kPreconditionFailed;
271 Event e{.fSEQ = String2Int<uint32_t> (request.
headers ().LookupOne (
"SEQ"sv).value_or (
String{}))};
273 e.fVariables = PropertySet_ (m.
rwRequest ().GetBody (), fRep_->fOptions_.fXMLOptions);
276 response.status = HTTP::StatusCodes::kBadRequest;
279 fRep_->fOnEvent_ (e);
284 optional<URI::Authority> a = eventSubURL.
GetAuthority ();
285 Require (a and a->GetHost ());
287 if (optional<InternetAddress> ia = a->GetHost ()->AsInternetAddress ()) {
293 exception_ptr lastFailure;
304 lastFailure = current_exception ();
308 rethrow_exception (lastFailure);
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
String is like std::u32string, except it is much easier to use, often much more space efficient,...
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
nonvirtual void Connect(const SocketAddress &sockAddr) const
Connects to the argument sockAddr;.
nonvirtual Sequence< InternetAddress > GetHostAddresses(const String &hostNameOrAddress) const
simple wrapper on GetHostEntry - looking up the hostname/ip address and returning the list of associa...
Common::ReadOnlyProperty< const Headers & > headers
nonvirtual optional< IO::Network::SocketAddress > GetLocalAddress() const
nonvirtual void SetSchemeAndAuthority(const URI &url)
nonvirtual PortType GetPortValue() const
nonvirtual optional< Authority > GetAuthority() const
nonvirtual RETURN_TYPE GetAuthorityRelativeResource() const
Return the (PCT etc encoded if a string) data AFTER the authority, but not including the fragment.
Subscriber(const URI &eventSubURL, const URI &callbackURL, const function< void(const Event &)> &onEvent, const Options &options=kDefaultOptions)
nonvirtual optional< String > GetSID() const
nonvirtual void Start()
Subscribe - throwing if the service does not take it - and keep it renewed. Started already: does not...
nonvirtual void Stop()
Unsubscribe - failing quietly, as the service then drops it at its TIMEOUT - so no more events are re...
nonvirtual void HandleNotify(WebServer::Message &m)
A NOTIFY at the callback URL - answered as UPnP Device Architecture 1.1, section 4....
static URI MakeCallbackURL(const URI &eventSubURL, Foundation::IO::Network::PortType port, const String &path)
A callback URL for the service at eventSubURL: http, at this machine's address on the network the ser...
Common::ReadOnlyProperty< Response & > rwResponse
Common::ReadOnlyProperty< const Request & > request
Common::ReadOnlyProperty< Request & > rwRequest
this represents a HTTP request object for the WebServer module
CONTAINER::value_type * Start(CONTAINER &c)
For a contiguous container (such as a vector or basic_string) - find the pointer to the start of the ...
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
How XML is read and written - for now, with which provider - taken last by each XML function (DOM::Do...