diff options
Diffstat (limited to 'metaserver/metaserver.py')
-rw-r--r-- | metaserver/metaserver.py | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/metaserver/metaserver.py b/metaserver/metaserver.py new file mode 100644 index 0000000..cb4ea19 --- /dev/null +++ b/metaserver/metaserver.py @@ -0,0 +1,365 @@ +from socket import * +import os, sys, time, random +from select import select +from cStringIO import StringIO +from weakref import WeakValueDictionary +from metastruct import * +from common import httpserver, stdlog + +if __name__ == '__main__': + os.chdir(os.path.dirname(sys.argv[0]) or os.curdir) + +META_SERVER_HTTP_PORT = 8050 +META_SERVER_PORT = 8055 +META_SERVER_UDP_PORT = 8055 +IMAGE_DIR = "../bubbob/doc/images" +ICONS = [open(os.path.join(IMAGE_DIR, s), 'rb').read() + for s in os.listdir(IMAGE_DIR) if s.endswith('.png')] +assert ICONS, "you need to run ../bubbob/doc/bonus-doc.py" +MAX_SERVERS = 50 +MAX_CONNEXIONS = 60 + + +serversockets = {} + +class MetaServer: + + def __init__(self, port=META_SERVER_PORT, udpport=META_SERVER_UDP_PORT): + s = socket(AF_INET, SOCK_STREAM) + s.bind(('', port)) + s.listen(5) + self.parentsock = s + self.ServersDict = {} + self.ServersList = [] + serversockets[s] = self.clientconnect, sys.exit + self.udpsock = socket(AF_INET, SOCK_DGRAM) + self.udpsock.bind(('', udpport)) + serversockets[self.udpsock] = self.udp_message, None + self.udpdata = [] + + def detach(self): + pid = os.fork() + if pid: + print pid + os._exit(0) + # in the child process + os.setsid() + logfile = stdlog.LogFile(limitsize=131072) + if logfile: + print >> logfile + print "Logging to", logfile.filename + fd = logfile.f.fileno() + try: + # detach from parent + os.dup2(fd, 1) + os.dup2(fd, 2) + os.dup2(fd, 0) + except OSError: + pass + logfile.close() + # record pid + f = open('pid', 'w') + print >> f, os.getpid() + f.close() + + def clientconnect(self): + s, addr = self.parentsock.accept() + Connexion(s, addr) + + def publish(self, server): + key = server.serverkey + if key in self.ServersDict: + current = self.ServersDict[key] + if current is server: + return + self.ServersList.remove(current) + elif len(self.ServersDict) >= MAX_SERVERS: + raise OverflowError + self.ServersList.append(server) + self.ServersDict[key] = server + print '+', key + + def unpublish(self, server): + key = server.serverkey + if key in self.ServersDict: + current = self.ServersDict[key] + if current is server: + del self.ServersDict[key] + self.ServersList.remove(server) + print '-', key + + def makelist(self): + items = {} + for srv in self.ServersList: + items[srv.serverkey] = encodedict(srv.serverinfo) + return encodedict(items) + + def getserver(self, key): + return self.ServersDict[key] + + def udp_message(self): + data, addr = self.udpsock.recvfrom(32) + self.udpdata.append((data, addr)) + if len(self.udpdata) > 50: + del self.udpdata[0] + + +class Connexion(MessageSocket): + + def __init__(self, s, addr): + MessageSocket.__init__(self, s) + self.serverinfo = { + 'time': int(time.time()), + 'icon': random.choice(ICONS), + 'iconformat': 'png', + } + self.addr = addr + self.key = '%s:%d' % addr + self.serverkey = None + print '[', self.key + self.backlinks = WeakValueDictionary() + if len(serversockets) >= MAX_CONNEXIONS: + self.disconnect() + raise OverflowError + serversockets[s] = self.receive, self.disconnect + + def disconnect(self): + metaserver.unpublish(self) + try: + del serversockets[self.s] + except KeyError: + pass + print ']', self.key + + def msg_serverinfo(self, info, *rest): + print '|', self.key + if len(info) > 15000: + raise OverflowError + info = decodedict(info) + self.serverinfo.update(info) + + def msg_startserver(self, port, *rest): + serverkey = '%s:%d' % (self.addr[0], port) + if self.serverkey and self.serverkey != serverkey: + metaserver.unpublish(self) + self.serverkey = serverkey + metaserver.publish(self) + + def msg_stopserver(self, *rest): + metaserver.unpublish(self) + + def msg_list(self, *rest): + self.s.sendall(message(RMSG_LIST, metaserver.makelist())) + + def msg_route(self, targetkey, *rest): + try: + target = metaserver.getserver(targetkey) + except KeyError: + try: + target = self.backlinks[targetkey] + except KeyError: + self.s.sendall(message(RMSG_NO_HOST, targetkey)) + return + target.route(self, *rest) + + def route(self, origin, msgcode, *rest): + self.backlinks[origin.key] = origin + self.s.sendall(message(msgcode, origin.key, *rest)) + + def msg_traceback(self, tb, *rest): + f = stdlog.LogFile('tb-%s.log' % (self.addr[0],)) + if f: + print >> f, tb + f.close() + + def msg_udp_addr(self, pattern, *rest): + for data, addr in metaserver.udpdata: + if data == pattern: + try: + host, port = addr + port = int(port) + except ValueError: + continue + self.s.sendall(message(RMSG_UDP_ADDR, host, port)) + return + else: + self.s.sendall(message(RMSG_UDP_ADDR)) + + MESSAGES = { + MMSG_INFO: msg_serverinfo, + MMSG_START: msg_startserver, + MMSG_STOP: msg_stopserver, + MMSG_LIST: msg_list, + MMSG_ROUTE: msg_route, + MMSG_TRACEBACK: msg_traceback, + MMSG_UDP_ADDR: msg_udp_addr, + } + + +# ____________________________________________________________ + +import htmlentitydefs +text_to_html = {} +for key, value in htmlentitydefs.entitydefs.items(): + text_to_html[value] = '&' + key + ';' +for i in range(32): + text_to_html[chr(i)] = '?' +def htmlquote(s, getter=text_to_html.get): + lst = [getter(c, c) for c in s if ' ' <= c < '\x7F'] + return ''.join(lst) +def txtfilter(s, maxlen=200): + s = str(s)[:maxlen] + l = [c for c in s if c in "!$*,-.0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "^_`abcdefghijklmnopqrstuvwxyz{|}"] + return ''.join(l) + +f = open('index.html', 'r') +HEADER, ROW, FOOTER = f.read().split('\\') +f.close() + +def makehosthtml(srv, bottommsg, join): + info = srv.serverinfo + hostname, port = srv.serverkey.split(':') + try: + fullhostname = gethostbyaddr(hostname)[0] + except: + fullhostname = hostname + url = None + if join: + url = "http://%s/join.html?host=%s&port=%s&httpport=%s&m=%s" % ( + join, hostname, port, info.get('httpport') or 'off', time.time()) + else: + try: + httpport = int(info.get('httpport')) + except: + pass + else: + url = "http://%s:%s/" % (hostname, httpport) + javamsg = """ +<p>Click on a server above to join the game. This only works if: +<ul><li>your browser understands Java; + <li>the server is not behind a firewall; + <li>you don't mind not hearing the nice background music. +</ul></p> +<p>Alternatively, install the +<a href="http://bub-n-bros.sourceforge.net/download.html">Python version</a> +of the client, which can cope with all of the above problems.</p> +<br>""" + if javamsg not in bottommsg: + bottommsg.append(javamsg) + result = '<strong>%s</strong>:%s' % (fullhostname, port) + if url: + result = '<a href="%s">%s</a>' % (url, result) + return result + +def indexloader(headers, join=[], head=[], **options): + if join: + join = join[0] + data = [HEADER % (head and head[0] or '')] + bottommsg = [] + if metaserver.ServersList: + counter = 0 + for srv in metaserver.ServersList: + info = srv.serverinfo + icon = '<img src="ico?key=%s">' % srv.serverkey + bgcolor = ('#C0D0D0', '#E0D0A8')[counter&1] + hosthtml = makehosthtml(srv, bottommsg, join) + desc = htmlquote(info.get('desc')) or '' + extradesc = htmlquote(info.get('extradesc')) or '' + if isinstance(info.get('time'), int): + stime = time.strftime('%a %b %d<br>%H:%M GMT', + time.gmtime(info['time'])) + else: + stime = '' + data.append(ROW % locals()) + counter += 1 + else: + data.append('''<tr><td bgcolor="#FFFFFF"> + Sorry, there is no registered server at the moment. + </td></tr>''') + if join: + extrafooter = '''<p><img src="home.png"> + <a href="http://%s/?time=%s">Back to local games</a></p>''' % ( + join, time.time()) + else: + extrafooter = '' + bottommsg = '\n'.join(bottommsg) + tbfiles = [s for s in os.listdir('.') if s.startswith('tb-')] + if tbfiles: + tbfiles = len(tbfiles) + else: + tbfiles = '' + data.append(FOOTER % locals()) + return StringIO(''.join(data)), 'text/html' + +def icoloader(key, **options): + srv = metaserver.getserver(key[0]) + iconformat = txtfilter(srv.serverinfo['iconformat'], 32) + return StringIO(srv.serverinfo['icon']), 'image/' + iconformat + +httpserver.register('', indexloader) +httpserver.register('index.html', indexloader) +httpserver.register('bub-n-bros.html', indexloader) +httpserver.register('ico', icoloader) +httpserver.register('mbub.png', httpserver.fileloader('mbub.png', 'image/png')) +httpserver.register('home.png', httpserver.fileloader('home.png', 'image/png')) + +def openhttpsocket(port = META_SERVER_HTTP_PORT): + from BaseHTTPServer import HTTPServer + class ServerClass(HTTPServer): + def get_request(self): + sock, addr = self.socket.accept() + sock.settimeout(5.0) + return sock, addr + HandlerClass = httpserver.MiniHandler + server_address = ('', port) + httpd = ServerClass(server_address, HandlerClass) + s = httpd.socket + serversockets[s] = httpd.handle_request, None + + +# ____________________________________________________________ + +def mainloop(): + while 1: + iwtd = serversockets.keys() + iwtd, owtd, ewtd = select(iwtd, [], iwtd) + #print iwtd, owtd, ewtd, serversockets + for s in iwtd: + if s in serversockets: + input, close = serversockets[s] + try: + input() + except: + import traceback + print "-"*60 + traceback.print_exc() + print "-"*60 + for s in ewtd: + if s in serversockets: + input, close = serversockets[s] + try: + close() + except: + import traceback + print "-"*60 + traceback.print_exc() + print "-"*60 + + +if __name__ == '__main__': + metaserver = MetaServer() + if sys.argv[1:2] == ['-f']: + metaserver.detach() + try: + openhttpsocket() + print 'listening to client port tcp %d / http %d / udp %d.' % ( + META_SERVER_PORT, + META_SERVER_HTTP_PORT, + META_SERVER_UDP_PORT) + mainloop() + finally: + if metaserver.ServersList: + print '*** servers still connected, waiting 5 seconds' + time.sleep(5) + print '*** leaving at', time.ctime() |