source: OpenRLabs-Git/deploy/rlabs-docker/web2py-rlabs/gluon/contrib/websocket_messaging.py

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100755
File size: 8.9 KB
Line 
1# -*- coding: utf-8 -*-
2#!/usr/bin/env python
3
4"""
5This file is part of the web2py Web Framework
6Copyrighted by Massimo Di Pierro <mdip...@cs.depaul.edu>
7License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8
9Attention: Requires Chrome or Safari. For IE of Firefox you need https://github.com/gimite/web-socket-js
10
111) install tornado (requires Tornado 3.0 or later)
12
13   easy_install tornado
14
152) start this app:
16
17   python gluon/contrib/websocket_messaging.py -k mykey -p 8888
18
193) from any web2py app you can post messages with
20
21   from gluon.contrib.websocket_messaging import websocket_send
22   websocket_send('http://127.0.0.1:8888', 'Hello World', 'mykey', 'mygroup')
23
244) from any template you can receive them with
25
26   <script>
27   $(document).ready(function(){
28      if(!$.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup', function(e){alert(e.data)}))
29
30         alert("html5 websocket not supported by your browser, try Google Chrome");
31   });
32   </script>
33
34When the server posts a message, all clients connected to the page will popup an alert message
35Or if you want to send json messages and store evaluated json in a var called data:
36
37   <script>
38   $(document).ready(function(){
39      var data;
40      $.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup', function(e){data=eval('('+e.data+')')});
41   });
42   </script>
43
44- All communications between web2py and websocket_messaging will be digitally signed with hmac.
45- All validation is handled on the web2py side and there is no need to modify websocket_messaging.py
46- Multiple web2py instances can talk with one or more websocket_messaging servers.
47- "ws://127.0.0.1:8888/realtime/" must be contain the IP of the websocket_messaging server.
48- Via group='mygroup' name you can support multiple groups of clients (think of many chat-rooms)
49
50
51Here is a complete sample web2py action:
52
53    def index():
54        form=LOAD('default', 'ajax_form', ajax=True)
55        script=SCRIPT('''
56            jQuery(document).ready(function(){
57              var callback=function(e){alert(e.data)};
58              if(!$.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup', callback))
59
60                alert("html5 websocket not supported by your browser, try Google Chrome");
61            });
62        ''')
63        return dict(form=form, script=script)
64
65    def ajax_form():
66        form=SQLFORM.factory(Field('message'))
67        if form.accepts(request,session):
68            from gluon.contrib.websocket_messaging import websocket_send
69            websocket_send(
70                'http://127.0.0.1:8888', form.vars.message, 'mykey', 'mygroup')
71        return form
72
73https is possible too using 'https://127.0.0.1:8888' instead of 'http://127.0.0.1:8888', but need to
74be started with
75
76   python gluon/contrib/websocket_messaging.py -k mykey -p 8888 -s keyfile.pem -c certfile.pem
77
78for secure websocket do:
79
80   web2py_websocket('wss://127.0.0.1:8888/realtime/mygroup',callback)
81
82Acknowledgements:
83Tornado code inspired by http://thomas.pelletier.im/2010/08/websocket-tornado-redis/
84
85"""
86from __future__ import print_function
87import tornado.httpserver
88import tornado.websocket
89import tornado.ioloop
90import tornado.web
91import hmac
92import sys
93import optparse
94import time
95import sys
96import gluon.utils
97from gluon._compat import to_native, to_bytes, urlencode, urlopen
98
99listeners, names, tokens = {}, {}, {}
100
101def websocket_send(url, message, hmac_key=None, group='default'):
102    sig = hmac_key and hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest() or ''
103    params = urlencode(
104        {'message': message, 'signature': sig, 'group': group})
105    f = urlopen(url, to_bytes(params))
106    data = f.read()
107    f.close()
108    return data
109
110
111class PostHandler(tornado.web.RequestHandler):
112    """
113    only authorized parties can post messages
114    """
115    def post(self):
116        if hmac_key and not 'signature' in self.request.arguments:
117            self.send_error(401)
118        if 'message' in self.request.arguments:
119            message = self.request.arguments['message'][0].decode(encoding='UTF-8')
120            group = self.request.arguments.get('group', ['default'])[0].decode(encoding='UTF-8')
121            print('%s:MESSAGE to %s:%s' % (time.time(), group, message))
122            if hmac_key:
123                signature = self.request.arguments['signature'][0]
124                actual_signature = hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest()
125                if not gluon.utils.compare(to_native(signature), actual_signature):
126                    self.send_error(401)
127            for client in listeners.get(group, []):
128                client.write_message(message)
129
130
131class TokenHandler(tornado.web.RequestHandler):
132    """
133    if running with -t post a token to allow a client to join using the token
134    the message here is the token (any uuid)
135    allows only authorized parties to joins, for example, a chat
136    """
137    def post(self):
138        if hmac_key and not 'message' in self.request.arguments:
139            self.send_error(401)
140        if 'message' in self.request.arguments:
141            message = self.request.arguments['message'][0]
142            if hmac_key:
143                signature = self.request.arguments['signature'][0]
144                actual_signature = hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest()
145                if not gluon.utils.compare(to_native(signature), actual_signature):
146                    self.send_error(401)
147            tokens[message] = None
148
149
150class DistributeHandler(tornado.websocket.WebSocketHandler):
151
152    def check_origin(self, origin):
153        return True
154
155    def open(self, params):
156        group, token, name = params.split('/') + [None, None]
157        self.group = group or 'default'
158        self.token = token or 'none'
159        self.name = name or 'anonymous'
160        # only authorized parties can join
161        if DistributeHandler.tokens:
162            if not self.token in tokens or not token[self.token] is None:
163                self.close()
164            else:
165                tokens[self.token] = self
166        if not self.group in listeners:
167            listeners[self.group] = []
168        # notify clients that a member has joined the groups
169        for client in listeners.get(self.group, []):
170            client.write_message('+' + self.name)
171        listeners[self.group].append(self)
172        names[self] = self.name
173        print('%s:CONNECT to %s' % (time.time(), self.group))
174
175    def on_message(self, message):
176        pass
177
178    def on_close(self):
179        if self.group in listeners:
180            listeners[self.group].remove(self)
181        del names[self]
182        # notify clients that a member has left the groups
183        for client in listeners.get(self.group, []):
184            client.write_message('-' + self.name)
185        print('%s:DISCONNECT from %s' % (time.time(), self.group))
186
187# if your webserver is different from tornado server uncomment this
188# or override using something more restrictive:
189# http://tornado.readthedocs.org/en/latest/websocket.html#tornado.websocket.WebSocketHandler.check_origin
190# def check_origin(self, origin):
191#    return True
192
193if __name__ == "__main__":
194    usage = __doc__
195    version = ""
196    parser = optparse.OptionParser(usage, None, optparse.Option, version)
197    parser.add_option('-p',
198                      '--port',
199                      default='8888',
200                      dest='port',
201                      help='socket')
202    parser.add_option('-l',
203                      '--listen',
204                      default='0.0.0.0',
205                      dest='address',
206                      help='listener address')
207    parser.add_option('-k',
208                      '--hmac_key',
209                      default='',
210                      dest='hmac_key',
211                      help='hmac_key')
212    parser.add_option('-t',
213                      '--tokens',
214                      action='store_true',
215                      default=False,
216                      dest='tokens',
217                      help='require tockens to join')
218    parser.add_option('-s',
219                      '--sslkey',
220                      default=False,
221                      dest='keyfile',
222                      help='require ssl keyfile full path')
223    parser.add_option('-c',
224                      '--sslcert',
225                      default=False,
226                      dest='certfile',
227                      help='require ssl certfile full path')
228    (options, args) = parser.parse_args()
229    hmac_key = options.hmac_key
230    DistributeHandler.tokens = options.tokens
231    urls = [
232        (r'/', PostHandler),
233        (r'/token', TokenHandler),
234        (r'/realtime/(.*)', DistributeHandler)]
235    application = tornado.web.Application(urls, auto_reload=True)
236    if options.keyfile and options.certfile:
237        ssl_options = dict(certfile=options.certfile, keyfile=options.keyfile)
238    else:
239        ssl_options = None
240    http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_options)
241    http_server.listen(int(options.port), address=options.address)
242    tornado.ioloop.IOLoop.instance().start()
Note: See TracBrowser for help on using the repository browser.