diff --git a/example/link.py b/example/link.py new file mode 100644 index 0000000..38337c5 --- /dev/null +++ b/example/link.py @@ -0,0 +1,38 @@ +import json +import urllib2 + +PEERLINK='http://[::1]:8842/' +NAME = 'chat' + +def urlopen(url, data=None, headers=None): + if data and not isinstance(data, str): + data = json.dumps(data) + if not headers: + headers = { + 'Content-Type': 'application/json', + 'User-Agent': 'ChatServer/0.0' + } + opener = urllib2.build_opener() + print 'urlopen', url, data + req = urllib2.Request(url, data=data, headers=headers) + response = opener.open(req) + return response.read() + +def post(action, data=None): + url = PEERLINK + action + return json.loads(urlopen(url, data)) + +def add(name, url): + global NAME + NAME = name + return post('add', {'name': name, 'url': url}) + +def remote(peer, action, data): + url = PEERLINK + '%s/%s/%s' % (peer, NAME, action) + if not data: + data = {'test': True} + print 'REMOTE', url + return urlopen(url, data) + +def remote_json(peer, action, data): + return json.loads(remote(peer, action, data)) diff --git a/example/server.py b/example/server.py old mode 100644 new mode 100755 index 794411e..ac3ec43 --- a/example/server.py +++ b/example/server.py @@ -1,104 +1,140 @@ -#!/usr/bin/python +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division, print_function import json import os -import shutil -import SimpleHTTPServer -import socket -import SocketServer +import signal import sys -import urllib2 +import mimetypes -peers = {} +from tornado.httpserver import HTTPServer +from tornado.ioloop import IOLoop, PeriodicCallback +from tornado.web import Application +import tornado -NETBASE='http://[::1]:8842/' -NAME = 'chat' +import websocket +from websocket import trigger_event +import state +from tasks import Tasks +from utils import json_dumps +import link -def remote(peer, action, data): - url = NETBASE + '%s/%s/%s' % (peer, NAME, action) - if data and not isinstance(data, str): - data = json.dumps(data) - opener = urllib2.build_opener() - req = urllib2.Request(url, data=data, headers={ - 'Content-Type': 'application/json', - 'User-Agent': 'ChatServer/0.0' - }) - response = opener.open(req) - return response.read() +import logging +logger = logging.getLogger('server') +root_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) +STATIC_PATH = os.path.join(root_dir, 'static') -def add_service(name, url): - add = NETBASE + 'add' - urllib2.urlopen(add, json.dumps({'name': name, 'url': url})) +class BaseHandler(tornado.web.RequestHandler): -class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler): - - def do_GET(self): - print 'GET', self.path - path = os.path.join('static', self.path[1:] if self.path != '/' else 'index.html') + def serve_static(self, path, mimetype=None, include_body=True): + if not mimetype: + mimetype = mimetypes.guess_type(path)[0] + logging.debug('serve %s', path) if os.path.exists(path): - with open(path) as fd: - shutil.copyfileobj(fd, self.wfile) - - def _remote_request(self, action, data): - response = {} - if action == 'message': - pass - elif action == 'ping': - print self.headers - response['userid'] = self.headers.getheader('From') - response['remote ping'] = True - response['data'] = data - return response - - def _request(self, action, data): - response = {} - if action == 'test': - response['test'] = 'ok' - response['data'] = data - elif action in ('ping', 'pong'): - id = data['id'] - del data['id'] - response = remote(id, action, data) - return response - - def do_POST(self): - print 'POST', self.path - length = int(self.headers.getheader('content-length')) - body = self.rfile.read(length) - data = json.loads(body) - if self.path.startswith('/remote'): - action = self.path.split('/')[2] - response = self._remote_request(action, data) + self.set_header('Content-Type', mimetype) + self.set_header('Content-Length', str(os.stat(path).st_size)) + if include_body: + with open(path) as fd: + self.write(fd.read()) else: - action = self.path.split('/')[1] - response = self._request(action, data) + self.set_status(404) + return - response = json.dumps(response, indent=2) - self.send_response(200) - self.send_header('Content-Type', 'application/json') - self.send_header('Content-Length', str(len(response))) - self.end_headers() - self.wfile.write(response) + def render_json(self, response): + response = json_dumps(response) + self.set_header('Content-Type', 'application/json') + self.set_header('Content-Length', str(len(response))) + self.write(response) + self.finish() -class Server(SocketServer.ThreadingMixIn, SocketServer.TCPServer): - ''' - IPv4/IPv6 Dual Stack - ''' - address_family = socket.AF_INET6 - allow_reuse_address = True +class RemoteHandler(BaseHandler): - def server_bind(self): - self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False) - SocketServer.TCPServer.server_bind(self) + def post(self, action): + data = json.loads(self.request.body) + response = {} + print ('GOT remote request', action, data) + if action == 'info': + response = state.info + elif action == 'message': + data['from'] = self.request.headers['From'] + trigger_event(action, data) + else: + response = {'error': 'unknown action'} + return self.render_json(response) + +class MainHandler(BaseHandler): + + def get(self, path): + path = path[1:] + if not path: + path = 'index.html' + path = os.path.join(STATIC_PATH, path) + self.serve_static(path) + + def post(self, path): + action = path.split('/')[1] + data = json.loads(self.request.body) + response = {} + if action in ('info', 'ping'): + if 'id' in data: + id = data['id'] + del data['id'] + response = link.remote_json(id, action, data) + return self.render_json(response) if __name__ == '__main__': - if len(sys.argv) == 2: + address = '' + port = 8000 + + if len(sys.argv) > 1: port = int(sys.argv[1]) + link.PEERLINK=sys.argv[2] + + logging.basicConfig(level=logging.DEBUG) + + options = { + 'debug': True, + } + handlers = [ + (r'/ws', websocket.Handler), + (r"/remote/(.*)", RemoteHandler), + (r"(.*)", MainHandler), + ] + + http_server = HTTPServer(Application(handlers, **options)) + + http_server.listen(port, address) + + state.tasks = Tasks() + state.main = IOLoop.instance() + + state._status = PeriodicCallback(lambda: state.info.update(link.post('info')), 60000) + state._status.start() + + if ':' in address: + host = '[%s]' % address + elif not address: + host = '[::1]' else: - port = 8000 - print "listening on port", port - url = 'http://127.0.0.1:%s/remote/' % port - add_service(NAME, url) - httpd = Server(("", port), Handler) - httpd.serve_forever() + host = address + url = 'http://%s:%s/' % (host, port) + + link.add('chat', url + 'remote/') + state.info = link.post('info') + + print('listening at %s' % url) + + def shutdown(): + state.tasks.join() + http_server.stop() + + signal.signal(signal.SIGTERM, shutdown) + + try: + state.main.start() + except: + print('shutting down...') + shutdown() diff --git a/example/state.py b/example/state.py new file mode 100644 index 0000000..66e672c --- /dev/null +++ b/example/state.py @@ -0,0 +1,4 @@ +websockets = [] +tasks = None +info = {} +peers = [] diff --git a/example/static/chat.js b/example/static/chat.js new file mode 100644 index 0000000..411af0a --- /dev/null +++ b/example/static/chat.js @@ -0,0 +1,90 @@ +var names = {}; +Ox.load(function() { + var app = window.app = {}; + app.triggerEvent = function(action, data) { + if (action == 'info') { + Ox.$('#id').html(data.id); + Ox.$('#nick').val(data.nick || ''); + var peers = Ox.$('#peers'); + peers.empty() + data.local.forEach(function(peer) { + console.log('add', peer); + if (names[peer]) { + Ox.$('