nbsdgames/http2/httppages.py
2022-02-03 13:46:22 +03:30

707 lines
25 KiB
Python

import os, sys, random
from io import StringIO
import socket, time
PLAYERNAMES = ['Bub', 'Bob', 'Boob', 'Beb',
'Biob', 'Bab', 'Bib',
'Baub', 'Beab', 'Biab']
try:
FILE = __file__
except NameError:
FILE = sys.argv[0]
LOCALDIR = os.path.abspath(os.path.dirname(FILE))
sys.path.insert(0, os.path.abspath(os.path.join(LOCALDIR, os.pardir)))
sys.path.insert(0, os.path.abspath(os.path.join(LOCALDIR, os.pardir,'common')))
import gamesrv, httpserver, hostchooser
from metaserver import metaclient
from httpserver import HTTPRequestError
class Options:
def __init__(self, dict={}):
self.update(dict)
def dict(self):
return self.__dict__.copy()
def update(self, dict):
self.__dict__.update(dict)
def copy(self):
return Options(self.__dict__)
def clear(self):
self.__dict__.clear()
def __getattr__(self, attr):
if not attr.startswith('_'):
return None
else:
raise AttributeError(attr)
class PageServer:
CONFIGFILE = 'config.txt'
localservers = None
def __init__(self, Game):
self.Game = Game
self.seed = hex(random.randrange(0x1000, 0x10000))
#self.unique_actions = {}
self.localhost = gamesrv.HOSTNAME
self.filename = os.path.join(LOCALDIR, self.CONFIGFILE)
data = self.loadoptionfile()
self.globaloptions = Options(data.get('*', {}))
self.localoptions = Options(data.get(self.localhost, {}))
self.reloadports()
#self.inetserverlist = None
#self.inetservers = {}
#self.has_been_published = 0
def registerpages(self):
prefix = '%s/' % self.seed
#httpserver.register('controlcenter.html', self.controlcenterloader)
httpserver.register(prefix, self.indexloader)
httpserver.register(prefix+'index.html', self.indexloader)
#httpserver.register(prefix+'list.html', self.listloader)
httpserver.register(prefix+'new.html', self.newloader)
httpserver.register(prefix+'run.html', self.runloader)
httpserver.register(prefix+'stop.html', self.stoploader)
httpserver.register(prefix+'join.html', self.joinloader)
#httpserver.register(prefix+'register.html',self.registerloader)
httpserver.register(prefix+'options.html', self.optionsloader)
httpserver.register(prefix+'name.html', self.nameloader)
for fn in os.listdir(os.path.join(LOCALDIR, 'data')):
path = prefix + fn
if not httpserver.is_registered(path):
httpserver.register(path, httpserver.fileloader(
os.path.join(LOCALDIR, 'data', fn)))
def opensocket(self):
hs = gamesrv.openhttpsocket()
if hs is None:
return 0
self.httpport = port = gamesrv.displaysockport(hs)
self.indexurl = 'http://127.0.0.1:%d/%s/' % (port, self.seed)
if self.Game:
print(self.Game.FnDesc, end=' ')
print('server is ready at', self.indexurl)
return 1
def getlocalservers(self):
if self.localservers is None:
self.searchlocalservers()
return self.localservers
def searchlocalservers(self):
servers = list(hostchooser.find_servers().items())
servers = list(filter(self.filterserver, servers))
servers.sort()
self.localservers = servers
## def parse_inetserv(self, s):
## try:
## host, port, udpport, httpport = s.split(':')
## return host, int(port), int(udpport)
## except (ValueError, IndexError):
## return None, None, None
## def getinetservers(self):
## if self.inetserverlist is None:
## return None
## result = []
## for s in self.inetserverlist:
## host, port, udpport = self.parse_inetserv(s)
## addr = host, port
## if addr in self.inetservers:
## result.append((addr, self.inetservers[addr]))
## return result
## def setinetserverlist(self, lst):
## self.inetserverlist = lst
## def checkinetserverlist(self):
## ulist = []
## for s in self.inetserverlist:
## host, port, udpport = self.parse_inetserv(s)
## if host is not None:
## ulist.append((host, udpport))
## srvs = hostchooser.find_servers(ulist, delay=0.8)
## self.inetservers = {}
## for srv in srvs.items():
## if not self.filterserver(srv):
## continue
## (host, port), info = srv
## try:
## host = socket.gethostbyaddr(host)[0]
## except socket.error:
## pass
## self.inetservers[host, port] = info
## #print 'hostchooser:', self.inetserverlist, '->', self.inetservers
def filterserver(self, xxx_todo_changeme):
((host, port), info) = xxx_todo_changeme
for c in host+str(port):
if c not in "-.0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_abcdefghijklmnopqrstuvwxyz":
return 0
return 1
## def statusservers(self):
## result = [], []
## for s in self.inetserverlist:
## host, port, udpport = self.parse_inetserv(s)
## addr = host, port
## found = addr in self.inetservers
## result[found].append(s)
## return result
def loadoptionfile(self):
try:
f = open(self.filename, 'r')
data = f.read().strip()
f.close()
except IOError:
data = None
return eval(data or '{}', {}, {})
def saveoptions(self):
data = self.loadoptionfile()
data['*'] = self.globaloptions.dict()
data[self.localhost] = self.localoptions.dict()
try:
f = open(self.filename, 'w')
print(repr(data), file=f)
f.close()
except IOError as e:
print("! Cannot save config file: " + str(e), file=sys.stderr)
def reloadports(self):
import msgstruct
msgstruct.PORTS.clear()
for key, value in list(self.localoptions.dict().items()):
if key.startswith('port_'):
key = key[5:]
if key == 'CLIENT' and type(value) == str and ':' in value:
udphostname, value = value.split(':')
msgstruct.PORTS['sendudpto'] = udphostname
try:
value = int(value)
except:
continue
msgstruct.PORTS[key] = value
def startgame(self):
self.reloadports()
options = self.globaloptions
kwds = {}
if options.beginboard is not None:
kwds['beginboard'] = int(options.beginboard)
if options.lvlend is not None and options.lvlend.startswith('n'):
kwds['finalboard'] = int(options.finalboard)
if options.stepboard is not None:
kwds['stepboard'] = int(options.stepboard)
if options.limit is not None and options.limit.startswith('y'):
kwds['limitlives'] = int(options.lives)
if options.limitlifegain is not None:
kwds['lifegainlimit'] = int(options.lifegainlimit)
if options.extralife is not None:
kwds['extralife'] = int(options.extralife)
if options.autoreset is not None:
kwds['autoreset'] = options.autoreset.startswith('y')
if options.metapublish is not None:
kwds['metaserver'] = options.metapublish.startswith('y')
self.Game(options.file, **kwds)
### loaders ###
def metaserverpage(self, headers):
metaserver_url = metaclient.METASERVER_URL
myhost = my_host(headers)
joinurl = quote_plus('%s/%s' % (myhost, self.seed))
return metaserver_url + '?join=%s&time=%s' % (joinurl, time.time())
def mainpage(self, headers, juststarted=0, justconnected=0):
running = my_server()
count = len(gamesrv.clients)
tim = time.time()
#if running:
# metapublish = my_server_meta_address()
# fndesc = quote_plus(gamesrv.game.FnDesc)
#else:
# metapublish = None
return httpserver.load(os.path.join(LOCALDIR, 'data', 'index.html'),
'text/html', locals=locals())
def indexloader(self, headers, cheat=[], **options):
if cheat:
import builtins
for c in cheat:
getattr(__builtin__, '__cheat')(c)
else:
self.localservers = None
return self.mainpage(headers, juststarted=('juststarted' in options))
def controlcenterloader(self, headers, **options):
host = headers['remote host']
host = socket.gethostbyname(host)
if host != '127.0.0.1':
raise HTTPRequestError("Access denied")
return None, self.indexurl
## def listloader(self, headers, s=[], **options):
## self.setinetserverlist(s)
## self.checkinetserverlist()
## query = []
## missing, found = self.statusservers()
## for s in missing:
## query.append('d=' + s)
## for s in found:
## query.append('a=' + s)
## return self.mainpage(headers, query)
def newloader(self, headers, **options):
if not self.Game:
raise HTTPRequestError("Complete bub-n-bros installation needed")
locals = {
'Game': self.Game,
'options': self.globaloptions,
'running': gamesrv.game is not None,
}
return httpserver.load(os.path.join(LOCALDIR, 'data', 'new.html'),
'text/html', locals=locals)
def runloader(self, headers, **options):
self.globaloptions.metapublish = 'n'
self.globaloptions.autoreset = 'n'
for key, value in list(options.items()):
if len(value) == 1:
setattr(self.globaloptions, key, value[0])
self.saveoptions()
self.startgame()
return None, 'index.html?juststarted=%s' % time.time()
def stoploader(self, headers, really=[], **options):
count = len(gamesrv.clients)
if count == 0 or really:
locals = {
'self': self,
#'metaserver': METASERVER,
#'metapublish': gamesrv.game and my_server_meta_address(),
#'localdir': LOCALDIR,
}
gamesrv.closeeverything()
return httpserver.load(os.path.join(LOCALDIR, 'data', 'stop.html'),
'text/html', locals=locals)
else:
locals = {
'count': count,
}
return httpserver.load(os.path.join(LOCALDIR, 'data', 'confirm.html'),
'text/html', locals=locals)
## def registerloader(self, headers, a=[], d=[], **options):
## if a: # the lists 'a' and 'd' contain dummies !!
## self.globaloptions.metapublish = 'y'
## self.has_been_published = 1
## kwd = 'a'
## else:
## self.globaloptions.metapublish = 'n'
## kwd = 'd'
## url = "%s?cmd=register&%s=%s" % (METASERVER,
## kwd, my_server_meta_address())
## if a and gamesrv.game:
## url += '&desc=' + quote_plus(gamesrv.game.FnDesc)
## return None, url
def joinloader(self, headers, host=[], port=[], httpport=[],
m=[], **options):
args = self.buildclientoptions()
assert len(host) == 1
host = host[0]
if len(port) == 1:
port = port[0]
else:
try:
host, port = host.split(':')
except:
port = None
if args is None:
# redirect to the Java applet
try:
httpport = int(httpport[0])
except (ValueError, IndexError):
if port:
raise HTTPRequestError("This server is not running HTTP.")
else:
raise HTTPRequestError("Sorry, I cannot connect the Java applet to a server using this field.")
return None, 'http://%s:%s/' % (host, httpport)
# now is a good time to generate the color files if we can
file = os.path.join(LOCALDIR, os.pardir, 'bubbob', 'images',
'buildcolors.py')
if os.path.exists(file):
g = {'__name__': '__auto__', '__file__': file}
exec(compile(open(file, "rb").read(), file, 'exec'), g)
if port:
address = '%s:%s' % (host, port)
else:
address = host
nbclients = len(gamesrv.clients)
script = os.path.join(LOCALDIR, os.pardir, 'display', 'Client.py')
script = no_quote_worries(script)
if m:
args.insert(0, '-m')
args = [script] + args + [address]
schedule_launch(args)
if m:
time.sleep(0.5)
s = 'Connecting to %s.' % address
return None, self.metaserverpage(headers) + '&head=' + quote_plus(s)
#elif my_server_address() == address:
# endtime = time.time() + 3.0
# while gamesrv.recursiveloop(endtime, []):
# if len(gamesrv.clients) > nbclients:
# break
return self.mainpage(headers, justconnected=1)
def optionsloader(self, headers, reset=[], savetime=[], **options):
if reset:
self.localoptions.clear()
self.globaloptions.clear()
self.saveoptions()
elif savetime:
self.localoptions.port_CLIENT = None
self.localoptions.port_LISTEN = None
self.localoptions.port_HTTP = None
for key, value in list(options.items()):
setattr(self.localoptions, key, value[0])
self.saveoptions()
locals = {
'self' : self,
'options': self.localoptions,
}
return httpserver.load(os.path.join(LOCALDIR, 'data', 'options.html'),
'text/html', locals=locals)
def nameloader(self, headers, **options):
MAX = len(PLAYERNAMES)
if options:
anyname = None
for id in range(MAX):
keyid = 'player%d' % id
if keyid in options:
value = options[keyid][0]
anyname = anyname or value
teamid = 'team%d' % id
if teamid in options:
team = options[teamid][0]
if len(team) == 1:
value = '%s (%s)' % (value, team)
setattr(self.localoptions, keyid, value)
if 'c' in options:
for id in range(MAX):
keyid = 'player%d' % id
try:
delattr(self.localoptions, keyid)
except AttributeError:
pass
if 'f' in options:
for id in range(MAX):
keyid = 'player%d' % id
if not getattr(self.localoptions, keyid):
setattr(self.localoptions, keyid,
anyname or PLAYERNAMES[id])
else:
anyname = getattr(self.localoptions, keyid)
self.saveoptions()
if 's' in options:
return self.mainpage(headers)
locals = {
'options': self.localoptions.dict(),
}
return httpserver.load(os.path.join(LOCALDIR, 'data', 'name.html'),
'text/html', locals=locals)
def graphicmodeslist(self):
try:
return self.GraphicModesList
except AttributeError:
import display.modes
self.GraphicModesList = display.modes.graphicmodeslist()
javamode = display.modes.GraphicMode(
'java', 'Java Applet (for Java browsers)', [])
javamode.low_priority = 1
javamode.getmodule = lambda : None
self.GraphicModesList.insert(0, javamode)
return self.GraphicModesList
def soundmodeslist(self):
try:
return self.SoundModesList
except AttributeError:
import display.modes
self.SoundModesList = display.modes.soundmodeslist()
return self.SoundModesList
def localmodes(self):
import display.modes
currentmodes = []
options = self.localoptions
for name, lst in [(options.dpy_, self.graphicmodeslist()),
(options.snd_, self.soundmodeslist())]:
try:
mode = display.modes.findmode(name, lst)
except KeyError:
try:
mode = display.modes.findmode(None, lst)
except KeyError as e:
print(str(e), file=sys.stderr) # no mode!
mode = None
currentmodes.append(mode)
return currentmodes
def buildclientoptions(self):
dpy, snd = self.localmodes()
if dpy.getmodule() is None:
return None # redirect to the Java applet
if dpy is None or snd is None:
raise HTTPRequestError("No installed graphics or sounds drivers. See the settings page.")
options = self.localoptions
result = ['--cfg='+no_quote_worries(self.filename)]
for key, value in list(options.dict().items()):
if key.startswith('port_') and value:
result.append('--port')
result.append('%s=%s' % (key[5:], value))
if options.datachannel == 'tcp': result.append('--tcp')
if options.datachannel == 'udp': result.append('--udp')
if options.music == 'no': result.append('--music=no')
for optname, mode in [('--display', dpy),
('--sound', snd)]:
result.append(optname + '=' + mode.name)
uid = mode.unique_id() + '_'
for key, value in list(options.dict().items()):
if key.startswith(uid):
result.append('--%s=%s' % (key[len(uid):], value))
return result
def my_host(headers):
return headers.get('host') or httpserver.my_host()
def my_server():
if gamesrv.game:
s = gamesrv.opentcpsocket()
return ((gamesrv.HOSTNAME, gamesrv.displaysockport(s)),
gamesrv.game.FnDesc)
else:
return None
def my_server_address():
running = my_server()
if running:
(host, port), info = running
return '%s:%d' % (host, port)
else:
return None
##def my_server_meta_address():
## s = gamesrv.opentcpsocket()
## ps = gamesrv.openpingsocket()
## hs = gamesrv.openhttpsocket()
## fullname = gamesrv.HOSTNAME
## try:
## fullname = socket.gethostbyaddr(fullname)[0]
## except socket.error:
## pass
## return '%s:%s:%s:%s' % (fullname,
## gamesrv.displaysockport(s),
## gamesrv.displaysockport(ps),
## gamesrv.displaysockport(hs))
##def meta_register():
## # Note: this tries to open a direct HTTP connection to the meta-server
## # which may not work if the proxy is not configured in $http_proxy
## try:
## import urllib
## except ImportError:
## print >> sys.stderr, "cannot register with the meta-server: Python's urllib missing"
## return
## print "registering with the meta-server...",
## sys.stdout.flush()
## addr = my_server_meta_address()
## try:
## f = urllib.urlopen('%s?a=%s&desc=%s' % (
## METASERVER, addr, quote_plus(gamesrv.game.FnDesc)))
## f.close()
## except Exception, e:
## print
## print >> sys.stderr, "cannot contact the meta-server (check $http_proxy):"
## print >> sys.stderr, "%s: %s" % (e.__class__.__name__, e)
## else:
## print "ok"
## unregister_at_exit(addr)
##def meta_unregister(addr):
## import urllib
## print "unregistering from the meta-server...",
## sys.stdout.flush()
## try:
## f = urllib.urlopen(METASERVER + '?d=' + addr)
## f.close()
## except Exception, e:
## print "failed"
## else:
## print "ok"
##def unregister_at_exit(addr, firsttime=[1]):
## if firsttime:
## import atexit
## atexit.register(meta_unregister, addr)
## del firsttime[:]
QuoteTranslation = {}
for c in ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'abcdefghijklmnopqrstuvwxyz'
'0123456789' '_.-'):
QuoteTranslation[c] = c
del c
QuoteTranslation[' '] = '+'
def quote_plus(s):
"""Quote the query fragment of a URL; replacing ' ' with '+'"""
getter = QuoteTranslation.get
return ''.join([getter(c, '%%%02X' % ord(c)) for c in s])
def main(Game, save_url_to=None, quiet=0):
#gamesrv.openpingsocket(0) # try to reserve the standard UDP port
srv = PageServer(Game)
srv.registerpages()
if not srv.opensocket():
print("server aborted.", file=sys.stderr)
sys.exit(1)
if quiet:
if Game:
Game.Quiet = 1
import stdlog
f = stdlog.LogFile()
if f:
print("Logging to", f.filename)
sys.stdout = sys.stderr = f
if save_url_to:
data = srv.indexurl + '\n'
def try_to_unlink(fn):
try:
os.unlink(fn)
except:
pass
import atexit
atexit.register(try_to_unlink, save_url_to)
try:
fno = os.open(save_url_to, os.O_CREAT | os.O_TRUNC | os.O_WRONLY,
0o600)
if os.write(fno, data) != len(data):
raise OSError
os.close(fno)
except:
f = open(save_url_to, 'w')
f.write(data)
f.close()
#if webbrowser:
# srv.launchbrowser()
# ____________________________________________________________
# Hack hack hack - workaround for the fact that on Windows
# the socket is inherited by the subprocess, which is quite
# bad because it keeps the browser-server connexion alive
# and the browser gets confused
def schedule_launch(args):
httpserver.actions_when_finished.append(lambda args=args: launch(args))
def launch(args):
# platform-specific hacks
print('Running client -> ', ' '.join(args))
if 0: # OLD CODE sys.platform == 'darwin': # must start as a UI process
import tempfile
cmdname = tempfile.mktemp('_BubBob.py')
f = open(cmdname, 'w')
print('import sys, os', file=f)
print('try: os.unlink(%r)' % cmdname, file=f)
print('except OSError: pass', file=f)
print('sys.argv[:] = %r' % (args,), file=f)
print('__file__ = %r' % cmdname, file=f)
print('execfile(%r)' % args[0], file=f)
f.close()
os.system('/usr/bin/open -a PythonLauncher "%s"' % cmdname)
else:
args.insert(0, sys.executable)
# try to close the open fds first
if hasattr(os, 'fork'):
try:
from resource import getrlimit, RLIMIT_NOFILE, error
except ImportError:
pass
else:
try:
soft, hard = getrlimit(RLIMIT_NOFILE)
except error:
pass
else:
if os.fork():
return # in parent -- done, continue
# in child
for fd in range(3, min(16384, hard)):
try:
os.close(fd)
except OSError:
pass
os.execv(args[0], args)
# this point should never be reached
# fall-back
# (quoting sucks on Windows) ** 42
if sys.platform == 'win32':
args[0] = '"%s"' % (args[0],)
if hasattr(os, 'P_DETACH'):
mode = os.P_DETACH
elif hasattr(os, 'P_NOWAIT0'):
mode = os.P_NOWAIT0
else:
mode = os.P_NOWAIT
os.spawnv(mode, sys.executable, args)
if sys.platform != "win32":
def no_quote_worries(s):
return s
else:
def no_quote_worries(s): # quoting !&?+*:-(
s = os.path.normpath(os.path.abspath(s))
absroot = os.path.join(LOCALDIR, os.pardir)
absroot = os.path.normpath(os.path.abspath(absroot))
ROOTDIR = os.curdir
while os.path.normpath(os.path.abspath(ROOTDIR)) != absroot:
if ROOTDIR == os.curdir:
ROOTDIR = os.pardir
else:
ROOTDIR = os.path.join(ROOTDIR, os.pardir)
if len(ROOTDIR) > 200:
# cannot find relative path! try with absolute one anyway
ROOTDIR = absroot
break
assert s.startswith(absroot)
if absroot.endswith(os.sep): # 'C:\'
absroot = absroot[:-1]
assert s[len(absroot)] == os.sep
relpath = s[len(absroot)+1:]
result = os.path.join(ROOTDIR, relpath)
print("no_quote_worries %r => %r" % (s, result))
return result
if __name__ == '__main__':
if (len(sys.argv) != 3 or sys.argv[1] != '--quiet' or
not sys.argv[2].startswith('--saveurlto=')):
print("This script should only be launched by BubBob.py.", file=sys.stderr)
sys.exit(2)
main(None, sys.argv[2][len('--saveurlto='):], quiet=1)
gamesrv.mainloop()