text: # -*- coding: utf-8 -*-
  #!/usr/bin/env python
  
  """
  This file is part of the web2py Web Framework
  Copyrighted by Massimo Di Pierro <mdip...@cs.depaul.edu>
  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
  
  Attention: Requires Chrome or Safari. For IE of Firefox you need https://github.com/gimite/web-socket-js
  
  1) install tornado (requires Tornado 3.0 or later)
  
     easy_install tornado
  
  2) start this app:
  
     python gluon/contrib/websocket_messaging.py -k mykey -p 8888
  
  3) from any web2py app you can post messages with
  
     from gluon.contrib.websocket_messaging import websocket_send
     websocket_send('http://127.0.0.1:8888', 'Hello World', 'mykey', 'mygroup')
  
  4) from any template you can receive them with
  
     <script>
     $(document).ready(function(){
        if(!$.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup', function(e){alert(e.data)}))
  
           alert("html5 websocket not supported by your browser, try Google Chrome");
     });
     </script>
  
  When the server posts a message, all clients connected to the page will popup an alert message
  Or if you want to send json messages and store evaluated json in a var called data:
  
     <script>
     $(document).ready(function(){
        var data;
        $.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup', function(e){data=eval('('+e.data+')')});
     });
     </script>
  
  - All communications between web2py and websocket_messaging will be digitally signed with hmac.
  - All validation is handled on the web2py side and there is no need to modify websocket_messaging.py
  - Multiple web2py instances can talk with one or more websocket_messaging servers.
  - "ws://127.0.0.1:8888/realtime/" must be contain the IP of the websocket_messaging server.
  - Via group='mygroup' name you can support multiple groups of clients (think of many chat-rooms)
  
  
  Here is a complete sample web2py action:
  
      def index():
          form=LOAD('default', 'ajax_form', ajax=True)
          script=SCRIPT('''
              jQuery(document).ready(function(){
                var callback=function(e){alert(e.data)};
                if(!$.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup', callback))
  
                  alert("html5 websocket not supported by your browser, try Google Chrome");
              });
          ''')
          return dict(form=form, script=script)
  
      def ajax_form():
          form=SQLFORM.factory(Field('message'))
          if form.accepts(request,session):
              from gluon.contrib.websocket_messaging import websocket_send
              websocket_send(
                  'http://127.0.0.1:8888', form.vars.message, 'mykey', 'mygroup')
          return form
  
  https is possible too using 'https://127.0.0.1:8888' instead of 'http://127.0.0.1:8888', but need to
  be started with
  
     python gluon/contrib/websocket_messaging.py -k mykey -p 8888 -s keyfile.pem -c certfile.pem
  
  for secure websocket do:
  
     web2py_websocket('wss://127.0.0.1:8888/realtime/mygroup',callback)
  
  Acknowledgements:
  Tornado code inspired by http://thomas.pelletier.im/2010/08/websocket-tornado-redis/
  
  """
  from __future__ import print_function
  import tornado.httpserver
  import tornado.websocket
  import tornado.ioloop
  import tornado.web
  import hmac
  import sys
  import optparse
  import time
  import sys
  import gluon.utils
  from gluon._compat import to_native, to_bytes, urlencode, urlopen
  
  listeners, names, tokens = {}, {}, {}
  
  def websocket_send(url, message, hmac_key=None, group='default'):
      sig = hmac_key and hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest() or ''
      params = urlencode(
          {'message': message, 'signature': sig, 'group': group})
      f = urlopen(url, to_bytes(params))
      data = f.read()
      f.close()
      return data
  
  
  class PostHandler(tornado.web.RequestHandler):
      """
      only authorized parties can post messages
      """
      def post(self):
          if hmac_key and not 'signature' in self.request.arguments:
              self.send_error(401)
          if 'message' in self.request.arguments:
              message = self.request.arguments['message'][0].decode(encoding='UTF-8')
              group = self.request.arguments.get('group', ['default'])[0].decode(encoding='UTF-8')
              print('%s:MESSAGE to %s:%s' % (time.time(), group, message))
              if hmac_key:
                  signature = self.request.arguments['signature'][0]
                  actual_signature = hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest()
                  if not gluon.utils.compare(to_native(signature), actual_signature):
                      self.send_error(401)
              for client in listeners.get(group, []):
                  client.write_message(message)
  
  
  class TokenHandler(tornado.web.RequestHandler):
      """
      if running with -t post a token to allow a client to join using the token
      the message here is the token (any uuid)
      allows only authorized parties to joins, for example, a chat
      """
      def post(self):
          if hmac_key and not 'message' in self.request.arguments:
              self.send_error(401)
          if 'message' in self.request.arguments:
              message = self.request.arguments['message'][0]
              if hmac_key:
                  signature = self.request.arguments['signature'][0]
                  actual_signature = hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest()
                  if not gluon.utils.compare(to_native(signature), actual_signature):
                      self.send_error(401)
              tokens[message] = None
  
  
  class DistributeHandler(tornado.websocket.WebSocketHandler):
  
      def check_origin(self, origin):
          return True
  
      def open(self, params):
          group, token, name = params.split('/') + [None, None]
          self.group = group or 'default'
          self.token = token or 'none'
          self.name = name or 'anonymous'
          # only authorized parties can join
          if DistributeHandler.tokens:
              if not self.token in tokens or not token[self.token] is None:
                  self.close()
              else:
                  tokens[self.token] = self
          if not self.group in listeners:
              listeners[self.group] = []
          # notify clients that a member has joined the groups
          for client in listeners.get(self.group, []):
              client.write_message('+' + self.name)
          listeners[self.group].append(self)
          names[self] = self.name
          print('%s:CONNECT to %s' % (time.time(), self.group))
  
      def on_message(self, message):
          pass
  
      def on_close(self):
          if self.group in listeners:
              listeners[self.group].remove(self)
          del names[self]
          # notify clients that a member has left the groups
          for client in listeners.get(self.group, []):
              client.write_message('-' + self.name)
          print('%s:DISCONNECT from %s' % (time.time(), self.group))
  
  # if your webserver is different from tornado server uncomment this
  # or override using something more restrictive:
  # http://tornado.readthedocs.org/en/latest/websocket.html#tornado.websocket.WebSocketHandler.check_origin
  # def check_origin(self, origin):
  #    return True
  
  if __name__ == "__main__":
      usage = __doc__
      version = ""
      parser = optparse.OptionParser(usage, None, optparse.Option, version)
      parser.add_option('-p',
                        '--port',
                        default='8888',
                        dest='port',
                        help='socket')
      parser.add_option('-l',
                        '--listen',
                        default='0.0.0.0',
                        dest='address',
                        help='listener address')
      parser.add_option('-k',
                        '--hmac_key',
                        default='',
                        dest='hmac_key',
                        help='hmac_key')
      parser.add_option('-t',
                        '--tokens',
                        action='store_true',
                        default=False,
                        dest='tokens',
                        help='require tockens to join')
      parser.add_option('-s',
                        '--sslkey',
                        default=False,
                        dest='keyfile',
                        help='require ssl keyfile full path')
      parser.add_option('-c',
                        '--sslcert',
                        default=False,
                        dest='certfile',
                        help='require ssl certfile full path')
      (options, args) = parser.parse_args()
      hmac_key = options.hmac_key
      DistributeHandler.tokens = options.tokens
      urls = [
          (r'/', PostHandler),
          (r'/token', TokenHandler),
          (r'/realtime/(.*)', DistributeHandler)]
      application = tornado.web.Application(urls, auto_reload=True)
      if options.keyfile and options.certfile:
          ssl_options = dict(certfile=options.certfile, keyfile=options.keyfile)
      else:
          ssl_options = None
      http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_options)
      http_server.listen(int(options.port), address=options.address)
      tornado.ioloop.IOLoop.instance().start()
  