4#include "Stroika/Frameworks/StroikaPreComp.h"
10#include "Stroika/Foundation/Characters/String2Int.h"
12#include "Stroika/Foundation/Common/GUID.h"
13#include "Stroika/Foundation/Containers/Sequence.h"
14#include "Stroika/Foundation/DataExchange/XML/WriterUtils.h"
18#include "Stroika/Foundation/IO/Network/HTTP/Status.h"
19#include "Stroika/Foundation/IO/Network/Transfer/Connection.h"
29using namespace Stroika::Frameworks;
30using namespace Stroika::Frameworks::UPnP;
31using namespace Stroika::Frameworks::UPnP::GENA;
38 sb <<
"<?xml version=\"1.0\"?>\r\n<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">"sv;
40 sb <<
"<e:property><"sv << v.fKey <<
">"sv << DataExchange::XML::QuoteForXMLW (v.fValue) <<
"</"sv << v.fKey <<
"></e:property>"sv;
42 sb <<
"</e:propertyset>\r\n"sv;
50 for (
size_t at = 0; optional<size_t> start = callback.
Find (
'<', at);) {
51 optional<size_t> end = callback.
Find (
'>', *start);
71 optional<Time::DurationSeconds> Timeout_ (
const optional<String>& timeout)
73 if (timeout and timeout->StartsWith (
"Second-"sv, eCaseInsensitive)) {
74 if (int64_t seconds = String2Int<int64_t> (timeout->SubString (7)); seconds > 0) {
83 return "Second-{}"_f(std::llround (timeout.count ()));
92class Publisher::Rep_ {
95 : fCurrentState_{currentState}
101 struct Subscription_ {
104 uint32_t fNextSEQ{0};
118 optional<Time::DurationSeconds> t = Timeout_ (asked);
119 return t and *t < fOptions_.fTimeout ? *t : fOptions_.fTimeout;
125 uint32_t seq = s->fNextSEQ;
126 s->fNextSEQ = seq == numeric_limits<uint32_t>::max () ? 1 : seq + 1;
127 fSender_.AddTask ([sid, callbacks = s->fCallbacks, seq, variables, currentState = fCurrentState_,
128 connectionFactory = fOptions_.fConnectionFactory] () {
129 Deliver_ (connectionFactory, sid, callbacks, seq, PropertySet_ (variables ? *variables : currentState ()));
134 const Sequence<URI>& callbacks, uint32_t seq,
const string& body)
136 for (
const URI& u : callbacks) {
141 r.fMethod =
"NOTIFY"sv;
144 {
"NT"sv,
"upnp:event"sv},
145 {
"NTS"sv,
"upnp:propchange"sv},
147 {
"SEQ"sv,
"{}"_f(seq)}};
154 DbgTrace (
"GENA NOTIFY to {} failed: {}"_f, u, current_exception ());
157 DbgTrace (
"GENA NOTIFY {} (SEQ {}) went to none of its callbacks"_f, sid, seq);
161 const function<Mapping<String, String> ()> fCurrentState_;
162 const Options fOptions_;
165 ThreadPool fSender_{ThreadPool::Options{.fThreadCount = 1}};
174 : fRep_{make_unique<Rep_> (currentState, options)}
178Publisher::~Publisher () =
default;
182 using HTTP::StatusCodes::kBadRequest;
183 using HTTP::StatusCodes::kPreconditionFailed;
185 WebServer::Response& response = m.
rwResponse ();
186 const optional<String> sid = request.
headers ().LookupOne (
"SID"sv);
187 const optional<String> nt = request.
headers ().LookupOne (
"NT"sv);
188 const optional<String> callback = request.
headers ().LookupOne (
"CALLBACK"sv);
190 response.rwHeaders ().Set (
"SID"sv, subscriptionID);
191 response.rwHeaders ().Set (
"TIMEOUT"sv, TimeoutHeader_ (timeout));
194 if (sid and (nt or callback)) {
195 response.status = kBadRequest;
198 if (method ==
"SUBSCRIBE"sv and not sid) {
200 if (nt !=
"upnp:event"sv or callbacks.
empty ()) {
201 response.status = kPreconditionFailed;
207 auto subscriptions = fRep_->fSubscriptions_.rwget ();
208 Rep_::DropExpired_ (&subscriptions.rwref ());
209 subscriptions->
Add (newSID, Rep_::Subscription_{.fCallbacks = callbacks, .fExpiresAt = Time::GetTickCount () + timeout});
211 answer (newSID, timeout);
213 bool answered = response.End ();
214 auto subscriptions = fRep_->fSubscriptions_.rwget ();
215 if (optional<Rep_::Subscription_> s = subscriptions->
Lookup (newSID)) {
218 fRep_->Send_ (newSID, &*s, nullopt);
219 subscriptions->
Add (newSID, *s);
222 subscriptions->
Remove (newSID);
226 else if (method ==
"SUBSCRIBE"sv) {
227 auto subscriptions = fRep_->fSubscriptions_.rwget ();
228 Rep_::DropExpired_ (&subscriptions.rwref ());
229 optional<Rep_::Subscription_> s = subscriptions->
Lookup (*sid);
231 response.status = kPreconditionFailed;
235 s->fExpiresAt = Time::GetTickCount () + timeout;
236 subscriptions->
Add (*sid, *s);
237 answer (*sid, timeout);
239 else if (method ==
"UNSUBSCRIBE"sv) {
240 auto subscriptions = fRep_->fSubscriptions_.rwget ();
241 Rep_::DropExpired_ (&subscriptions.rwref ());
242 if (not sid or not subscriptions->
RemoveIf (*sid)) {
243 response.status = kPreconditionFailed;
247 response.status = HTTP::StatusCodes::kMethodNotAllowed;
253 auto subscriptions = fRep_->fSubscriptions_.rwget ();
254 Rep_::DropExpired_ (&subscriptions.rwref ());
256 if (not i.fValue.fPending) {
257 Rep_::Subscription_ s = i.fValue;
258 fRep_->Send_ (i.fKey, &s, changed);
259 subscriptions->
Add (i.fKey, s);
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual String str() const
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual T AsUTF8() const
nonvirtual String SubString(SZ from) const
nonvirtual optional< size_t > Find(Character c, CompareOptions co=eWithCase) const
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)
nonvirtual optional< mapped_type > Lookup(ArgByValueType< key_type > key) const
nonvirtual bool RemoveIf(ArgByValueType< key_type > key)
Remove the given item, if it exists. Return true if found and removed.
nonvirtual void RemoveAll()
RemoveAll removes all, or all matching (predicate, iterator range, equals comparer or whatever) items...
nonvirtual void Remove(ArgByValueType< key_type > key)
Remove the given item (which must exist).
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
Common::Property< String > httpMethod
typically HTTP::Methods::kGet
Common::ReadOnlyProperty< const Headers & > headers
nonvirtual void SetSchemeAndAuthority(const URI &url)
nonvirtual bool GetSucceeded() const
static URI Parse(const String &rawURL)
nonvirtual optional< SchemeType > GetScheme() 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.
nonvirtual bool empty() const
Returns true iff size() == 0.
nonvirtual void Notify(const Mapping< String, String > &changed)
Send every subscriber these state variables' new values, by name.
nonvirtual void HandleRequest(WebServer::Message &m)
A SUBSCRIBE - a new subscription, or the renewal of one by its SID - or UNSUBSCRIBE,...
Publisher(const function< Mapping< String, String >()> ¤tState, const Options &options=kDefaultOptions)
Common::ReadOnlyProperty< Response & > rwResponse
Common::ReadOnlyProperty< const Request & > request
this represents a HTTP request object for the WebServer module
static GUID GenerateNew() noexcept