When I wrote the small app "SSE_clock" I was searching a replacement for a "long polling javascript code" that I was using in order to push db's table update notifications to clients. I abandoned the project by lack of browser's support. Anyway, the application is a simple translation from php to python. Original demo target is to show that SSEs reconnect automatically and that it possible send multiple events on a single connection. Here attached you'll find original code in php to compare with python version. However SSE has other features not discussed in the clock example. Below some links that I collected during my research:
http://blog.samshull.com/2010/10/ajax-push-in-ios-safari-and-chrome-with.html http://my.opera.com/WebApplications/blog/show.dml/438711 http://dev.w3.org/html5/eventsource/ http://www.html5rocks.com/en/tutorials/eventsource/basics/ https://github.com/rwldrn/jquery.eventsource http://www.igvita.com/2011/08/26/server-sent-event-notifications-with-html5/ http://dsheiko.com/weblog/html5-and-server-sent-events http://blog.abourget.net/2010/6/16/html5-eventsource-in-pylons-read-comet-ajax-polling/ http://en.wikipedia.org/wiki/Server-sent_events https://msmvps.com/blogs/theproblemsolver/archive/2011/11/07/html-5-server-sent-events.aspx http://peter.sh/examples/?/javascript/event-source.html http://weblog.bocoup.com/chrome-6-server-sent-events-with-new-eventsource https://developer.mozilla.org/en/Server-sent_events/Using_server-sent_events http://stevenhollidge.blogspot.com/2011/10/html5-server-side-events.html http://www.dotnetage.com/publishing/home/2011/09/23/6932/html5-server-sent-events.html http://moshhard.wordpress.com/2011/10/11/html5-server-sent-event-combine-with-web-notification/ http://www.codeproject.com/Questions/405117/Sending-Html5-SSE-to-specific-clients Il giorno lunedì 8 aprile 2013 17:48:31 UTC+2, Arnon Marcus ha scritto: > > Look, I appreciate you're trying to help-out, but it seems you are > answering the questions you know the answers to, instead of the questions I > ask. > It's OK to say that you don't know the answer. You are not alone in this > user-group, perhaps someone else does. > > We all got that. it's an external process, but it's implemented already, >> it "just works", has a simple yet powerful routing algo and its secure. >> With SSE you have to do it yourself. >> >> > > I know that there is a "somewhat-working" solution for web-sockets, using > Tornado. > I know it would be better to use it, instead of trying to make SSE work in > web2py by myself. > In the long-term I'll probably do something like that. > > But as you said, not in all scenarios, a web-socket is requited - > sometimes an SSE does what I need. > And as it is HTTP-based, I thought it should have been easy to implement > in web2py. > > This is exactly the example shown on the videos about >> websocket_messaging.py . the user receives updates through the ws, and he >> sends to the default web2py installation with a simple ajax post its >> message. web2py then queues that message to tornado, that informs all >> connected users of the new message on the ws channel. >> >> > Again, that is not an answer to my questions. My questions where referring > to how web2py can implement SSE, not how Tornado can implement web-sockets > and have web2py push stuff into it. > >> >> On the SSE side, you'd have some controller that basically does: >> >> def events(): >> initialization_of_sse >> while True: >> yield send_a_message >> >> you have to think to security, routing, etc by yourself. >> >> Basically in that while True loop you'd likely want to inspect your >> "storage" (redis, ram, dict, database, whatever) if there's a new message >> for the user. >> You can't "exit" from there and resume it....all the logic needs to >> happen inside that yield(ing) loop. >> > > That is answering the question : "How does web2py keep a long-lasting > connection". > That is NOT answering the question: "How can a different controller-action > activate this" > > I found a way to extract the web2py-SSE example, here are the relevant > parts (I *bold*'ed the important stuff): > > *Controller:* > > > # -*- coding: utf-8 -*- > import time > from gluon.contenttype import contenttype > > ### required - do no delete > def user(): return dict(form=auth()) > def download(): return response.download(request,db) > def call(): return service() > ### end requires > > def index(): > return dict() > > def error(): > return dict() > > def sse(): > return dict() > > def buildMsg(eid , msg): > mmsg = "id: %s\n" %eid > mmsg += "data: {\n" > mmsg += "data: \"msg\": \"%s\", \n" %msg > mmsg += "data: \"id\": %s\n" %eid > mmsg += "data: }\n\n" > return mmsg > > *def sent_server_event():* > response.headers['Content-Type'] = 'text/event-stream' > response.headers['Cache-Control'] = 'no-cache' > * def sendMsg():* > startedAt = time.time(); #http://www.epochconverter.com/ > * while True:* > messaggio = buildMsg(startedAt , time.time()) > * yield messaggio* > * time.sleep(5)* > * if ((time.time() - startedAt) > 10):break* > * return sendMsg()* > > def event_sender(): > response.headers['Content-Type'] = 'text/event-stream' > response.headers['Cache-Control'] = 'no-cache' > mtime = time.time() > return 'data:' + str(mtime) > > > *View (script-part):* > > * > * > if (!window.DOMTokenList) { > Element.prototype.containsClass = function(name) { > return new RegExp("(?:^|\\s+)" + name + > "(?:\\s+|$)").test(this.className); > }; > > Element.prototype.addClass = function(name) { > if (!this.containsClass(name)) { > var c = this.className; > this.className = c ? [c, name].join(' ') : name; > } > }; > > Element.prototype.removeClass = function(name) { > if (this.containsClass(name)) { > var c = this.className; > this.className = c.replace( > new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)", "g"), ""); > } > }; > } > > // sse.php sends messages with text/event-stream mimetype. > *var source = new EventSource('{{=URL("sent_server_event")}}');* > > function Logger(id) { > this.el = document.getElementById(id); > } > > Logger.prototype.log = function(msg, opt_class) { > var fragment = document.createDocumentFragment(); > var p = document.createElement('p'); > p.className = opt_class || 'info'; > p.textContent = msg; > fragment.appendChild(p); > this.el.appendChild(fragment); > }; > > Logger.prototype.clear = function() { > this.el.textContent = ''; > }; > > var logger = new Logger('log'); > > *function closeConnection() {* > * source.close();* > logger.log('> Connection was closed'); > updateConnectionStatus('Disconnected', false); > } > > function updateConnectionStatus(msg, connected) { > var el = document.querySelector('#connection'); > if (connected) { > if (el.classList) { > el.classList.add('connected'); > el.classList.remove('disconnected'); > } else { > el.addClass('connected'); > el.removeClass('disconnected'); > } > } else { > if (el.classList) { > el.classList.remove('connected'); > el.classList.add('disconnected'); > } else { > el.removeClass('connected'); > el.addClass('disconnected'); > } > } > el.innerHTML = msg + '<div></div>'; > } > > *source.addEventListener('message', function(event) {* > //console.log(event.data) > * var data = JSON.parse(event.data);* > > var d = new Date(data.msg * 1e3); > var timeStr = [d.getHours(), d.getMinutes(), d.getSeconds()].join(':'); > > coolclock.render(d.getHours(), d.getMinutes(), d.getSeconds()); > > logger.log('lastEventID: ' + event.lastEventId + > ', server time: ' + timeStr, 'msg'); > }, false); > > *source.addEventListener('open', function(event) {* > logger.log('> Connection was opened'); > updateConnectionStatus('Connected', true); > }, false); > > *source.addEventListener('error', function(event) {* > if (event.eventPhase == 2) { //EventSource.CLOSED > logger.log('> Connection was closed'); > updateConnectionStatus('Disconnected', false); > } > }, false); > > var coolclock = CoolClock.findAndCreateClocks(); > > > > Now, I can see that it's ported from php, and that there are some unused > stuff in the controller - probably as this is a ruff proof-of-concept > only... > Now, what this example is doing, basically, is establishing an SSE > connection with a web2py controller, that yields a time-stamp, twice, than > exits out of the loop. > Meaning, it generates 2 responses for each single-connection, while > sleeping 5 seconds in between, then the loop is broken, so web2py stops > sending more responses. > This closes the connection, and 3 seconds later (as is defined in the SSE > spec), the connection re-establishes itself, and so on. > There is also an option to close the connection manually, from the client > side. > > That's all fine and dandy... > > But it answers NONE of the questions I asked... > > There is *no inter-controller/action communication* in here, there is no > way to *POST* something *from the client to the server*, that will *call > a different action* in web2py, which will *then invoke another yield* of > the SSE action, thus* intentionally-spawning another response over the > existing connection....* > And what if there are multiple connections to multiple clients? the only > way to differentiate between them would be via their sessions. > Now, the way I understand this, it's a *fundamental "executional" * > limitation > of web2py - it has no concurrency, so each invocation of web2py's > wsgi-handler, is in fact a single-process-single-thread type of scenario, > so that there could never exist multiple sessions that are handled at the > same time... Unless another process/thread is being spawned by the > web-server itself. In that case, there would have to be some sort of > inter-process/inter-thread communication going on, in order for one session > in one thread, to invoke an action in a separate session on a different > thread. The only way around this, would obviously have to be using web2py > over something like gEvent. But the question would then still remain: > Whether an in-proc/in-thread/cross-sub-routine communication, or an > inter-proc/inter-thread communication, there would STILL exist a need to > rout across "sessions". In a sense, the controller-action's > execution-run-time would have to be bound to the session that invoked it. > Am I understanding this correctly? > If so, it's not a small matter - is a mismatch of fundamental > execution-architecture. There is a critical component missing. > If it INDEED does not exist in web2py, I would like it to be said > up-front CLEARLY. > This way, a discussion about future possibilities can be started, perhaps for > web3py. > I would then also not have to waste time and effort digging through > un-documented territories, and half-ass'ed "proof-of-concept" that, > evidently, prove that the concept does not work, and go around it to show > some completely useless use-case... > > This is all very disappointing... > -- --- You received this message because you are subscribed to the Google Groups "web2py-users" group. To unsubscribe from this group and stop receiving emails from it, send an email to web2py+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/groups/opt_out.
sse.7z
Description: application/7z-compressed