Spring BootでWebSocketの続き
この投稿の続きです。
対話っぽいことができるように拡張しました。
Slackみたいな機能を実装できるように拡張してみたいと思います。
ソースコードはGitHubにあります。
View On WordPress

seen from Netherlands
seen from United States

seen from United States

seen from Germany
seen from United Kingdom
seen from China
seen from United States
seen from Ukraine

seen from Russia
seen from China
seen from China
seen from United Kingdom
seen from Türkiye
seen from United States
seen from United Kingdom
seen from Yemen

seen from United States

seen from Malaysia
seen from China

seen from United Kingdom
Spring BootでWebSocketの続き
この投稿の続きです。
対話っぽいことができるように拡張しました。
Slackみたいな機能を実装できるように拡張してみたいと思います。
ソースコードはGitHubにあります。
View On WordPress

Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
Free to watch • No registration required • HD streaming
SockJS and Tornado for Python real-time web projects
At Djangocon EU last year, I gave a talk about how you could easily get up and running with real-time web applications using Django and Socket.IO. I have now been running an application using this principle for a year and I am everything but a fan of it. I decided i had to switch to something else.
My first impression of SockJS wasn't very good. I thought the documentation was poor and the commit on the main (client) library wasn't very new. However, after reviewing other possibilites I decided that SockJS with sockjs-tornado had to be the best option at the moment.
I found a great gist that showed most of the way. From this gist you get the basic transport and just need to implement some sort of authentication. I also had to set it up for running on Heroku, which unfortunately means waiving one of the essential features of SockJS, namely Websockets (btw, heroku - please add support for this!). In this case I was not too concerned about authorization, just authentication. If your usecase is different, you might want to implement another/your own authentication.
from tornado import web, ioloop from sockjs.tornado import SockJSRouter, SockJSConnection import json import os import tornadoredis import urlparse from django.core import signing import logging class Connection(SockJSConnection): clients = set() def send_error(self, message, error_type=None): """ Standard format for all errors """ return self.send(json.dumps({ 'data_type': 'error' if not error_type else '%s_error' % error_type, 'data': { 'message': message } })) def send_message(self, message, data_type): """ Standard format for all messages """ return self.send(json.dumps({ 'data_type': data_type, 'data': message, })) def on_open(self, request): """ Request the client to authenticate and add them to client pool. """ self.authenticated = False self.channel = None self.send_message({}, 'request_auth') self.clients.add(self) def on_message(self, msg): """ Handle authentication and notify the client if anything is not ok, but don't give too many details """ try: message = json.loads(msg) except ValueError: self.send_error("Invalid JSON") return if message['data_type'] == 'auth' and not self.authenticated: try: channel = signing.loads( message['data']['token'], key=SECRET_KEY, salt=message['data']['salt'], max_age=40 # Long time out for heroku idling processes. # For other cases, reduce to 10 ) except (signing.BadSignature, KeyError) as e: self.send_error("Token invalid", 'auth') return self.authenticated = True self.channel = channel self.send_message({'message': 'success'}, 'auth') logging.debug("Client authenticated for %s" % channel) else: self.send_error("Invalid data type %s" % message['data_type']) logging.debug("Invalid data type %s" % message['data_type']) def on_close(self): """ Remove client from pool. Unlike Socket.IO connections are not re-used on e.g. browser refresh. """ self.clients.remove(self) return super(Connection, self).on_close()
By now, you have probably realized that we are storing clients on the class. This means we can't properly load balance this to multiple nodes without session stickyness. This sucks. I'm trying to figure out a way to store clients inside redis, but for now, session stickyness is required (or if you just run it on one server, you should be fine).
If you watched my talk, you already know that I love redis. My original Socket.IO solution was basically just a frontend to redis. Redis has served me very well for a year, so now I'll show you how to automatically forward messages from redis to the clients. First, add this method to the above class:
@classmethod def pubsub_message(cls, msg): for client in cls.clients: if client.authenticated and client.channel == msg.channel: client.send(msg.body)
Now we just need to run the Tornado server and subscribe to redis:
if __name__ == '__main__': url = urlparse.urlparse(os.environ.get('REDISTOGO_URL', os.environ.get('OPENREDIS_URL', 'redis://localhost:6379'))) pool = tornadoredis.ConnectionPool(host=url.hostname, port=url.port) c = tornadoredis.Client(connection_pool=pool, password=url.password) c.connect() c.psubscribe("*", lambda msg: c.listen(Connection.pubsub_message)) Router = SockJSRouter(Connection, '/namespace-here', dict(disabled_transports=['websocket'])) # Disable websockets for heroku app = web.Application(Router.urls) app.listen(os.environ.get("PORT", 8080)) ioloop.IOLoop.instance().start()
You definitely want to remove the part where I disable websockets, if you are lucky enough to be hosted somewhere where they support websockets. (Please mail me, if you know a host that offers websockets in eu-west). Furthermore you should make a prefix, so you are still able to use pubsub in redis, without it being sent to SockJS. 'namespace-here' is the url your Connection class will be served at. This will enable you to namespace your different services later on.
Now, put it all in a file and run it!
Create a simple TemplateView or equivalent in your web application and insert the following JavaScript in script tags:
function connect(url, auth_json) { var sock = new SockJS(url); sock.onopen = function() { sock.send(auth_json); }; sock.onmessage = function (event) { data = jQuery.parseJSON(event.data); if (data.data_type == 'data') { // parse your data here } else if (data.data_type == 'auth_error') { throw data.data.message; } }; } connect("http://localhost:8080/namespace-here", "");
In our web application (in this case, django) we generate `` like this:
from django.core import signing import json auth_json = json.dumps({ 'data_type': 'auth', 'data': { 'salt': SOME_SALT, 'token': signing.dumps('channel',SECRET_KEY, salt=SOME_SALT) } })
And make sure to include SockJS somewhere before the above:
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"> </script>
Now you should be ready to start sending events to the browser, from python through redis. Let's try.
>>> import redis >>> r = redis.Redis() >>> r.publish("channel", "hello") 0L
Depending whether "channel" matches your pattern and whether you have an open connection, this will return either 0L or 1L, afaik the number of "listeners" to your message. You will get front-end error though, as you are not passing in properly formatted JSON. My script assumes you send messages formatted like this:
{ "data_type": "data", "data": { "message": "here", "or any other": "key/value pairs" } }
That was all for now. If you enjoyed this blog post, check out my twitter for updates.
SockJs App on Cowboy with Rebar
## Acquire a rebar binary I chose to clone rebar's git and build it instead of downloading the binary from https://github.com/basho/rebar/wiki/rebar In order to extend my project to have multiple Erlang applications, I put the applications in `apps` folder. To support that structure, I created a simple `rebar.config`: %%-*- mode: erlang -*- {sub_dirs, ["apps/example", "rel"]}. {deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}}, {sockjs, ".*", {git, "git://github.com/sockjs/sockjs-erlang.git", "master"}} ]}. The `sub_dirs` option tells `rebar` where to look for the app. The `deps` option specifies which dependencies to download. In this case, `cowboy`, and `sockjs-erlang`. $ mkdir --parent apps/example $ cd apps/example $ ../../rebar create-app appid=example $ cd ../.. This will create a `src` folder inside `apps\example` with three files: * `example.app.src`: This can be seen as the definition of the application, in which `rebar` will copy over to the `ebin` after compiling. * `example_app.erl`: The main application module * `example_sup.erl`: The supervisor module I also created a `start.sh` script to start the server. ## Structure ├── apps │ ├── example │ │ ├── src │ │ │ ├── example.app.src │ │ │ ├── example_app.erl │ │ │ ├── example_sup.erl ├── echo.html ├── rebar ├── rebar.config ├── start.sh The content of those files are available at [this gist](https://gist.github.com/655869fe29a2709948b9)
SockJS is a browser JavaScript library that provides a WebSocket-like object. SockJS gives you a coherent, cross-browser, Javascript API which creates a low latency, full duplex, cross-domain communication channel between the browser and the web server. Under the hood SockJS tries to use native WebSockets first. If that fails it can use a variety of browser-specific transport protocols and presents them through WebSocket-like abstractions. SockJS is intended to work for all modern browsers and in environments which don't support WebSocket protcol, for example behind restrictive corporate proxies. SockJS family: * [SockJS-client](https://github.com/sockjs/sockjs-client) JavaScript client library * [SockJS-node](https://github.com/sockjs/sockjs-node) Node.js server * [SockJS-erlang](https://github.com/sockjs/sockjs-erlang) Erlang server * [SockJS-lua](https://github.com/luvit/sockjs-luvit) Lua/Luvit server * [SockJS-tornado](https://github.com/MrJoes/sockjs-tornado) Python/Tornado server * [vert.x](https://github.com/purplefox/vert.x) Java/vert.x server Work in progress: * [SockJS-ruby](https://github.com/sockjs/sockjs-ruby) * [SockJS-netty](https://github.com/cgbystrom/sockjs-netty) * [SockJS-gevent](https://github.com/sdiehl/sockjs-gevent) * [pyramid-SockJS](https://github.com/fafhrd91/pyramid_sockjs) * [wildcloud-websockets](https://github.com/wildcloud/wildcloud-websockets) * [SockJS-cyclone](https://github.com/flaviogrossi/sockjs-cyclone) * [SockJS-twisted](https://github.com/Fugiman/sockjs-twisted/) * [wai-SockJS](https://github.com/Palmik/wai-sockjs)