#!/usr/bin/env python import os, os.path, errno, sys import tornado.httpserver import tornado.ioloop import tornado.web import tornado.template import logging, logging.handlers from config import * from util import * from ws_handler import * from game_data_handler import GameDataHandler import process_handler class MainHandler(tornado.web.RequestHandler): def get(self): host = self.request.host if self.request.protocol == "https": protocol = "wss://" else: protocol = "ws://" self.render("client.html", socket_server = protocol + host + "/socket", username = None, config = config) class NoCacheHandler(tornado.web.StaticFileHandler): def set_extra_headers(self, path): self.set_header("Cache-Control", "no-cache, no-store, must-revalidate") self.set_header("Pragma", "no-cache") self.set_header("Expires", "0") def err_exit(errmsg): logging.error(errmsg) sys.exit(errmsg) def daemonize(): try: pid = os.fork() if pid > 0: os._exit(0) except OSError, e: err_exit("Fork #1 failed! (%s)" % (e.strerror)) os.setsid() try: pid = os.fork() if pid > 0: os._exit(0) except OSError, e: err_exit("Fork #2 failed! (%s)" % e.strerror) with open("/dev/null", "rw") as f: os.dup2(f.fileno(), sys.stdin.fileno()) os.dup2(f.fileno(), sys.stdout.fileno()) os.dup2(f.fileno(), sys.stderr.fileno()) def write_pidfile(): if not pidfile: return if os.path.exists(pidfile): try: with open(pidfile) as f: pid = int(f.read()) except ValueError: err_exit("PIDfile %s contains non-numeric value" % pidfile) try: os.kill(pid, 0) except OSError, why: if why[0] == errno.ESRCH: # The pid doesn't exist. logging.warn("Removing stale pidfile %s" % pidfile) os.remove(pidfile) else: err_exit("Can't check status of PID %s from pidfile %s: %s" % (pid, pidfile, why[1])) else: err_exit("Another Webtiles server is running, PID %s\n" % pid) with open(pidfile, "w") as f: f.write(str(os.getpid())) def remove_pidfile(): if not pidfile: return try: os.remove(pidfile) except OSError, e: if e.errno == errno.EACCES or e.errno == errno.EPERM: logging.warn("No permission to delete pidfile!") else: logging.error("Failed to delete pidfile!") except: logging.error("Failed to delete pidfile!") def shed_privileges(): if gid is not None: os.setgid(gid) if uid is not None: os.setuid(uid) def signal_handler(signum, frame): logging.info("Received signal %i, shutting down.", signum) shutdown() if len(sockets) == 0: ioloop.stop() def bind_server(): settings = { "static_path": static_path, "template_loader": DynamicTemplateLoader.get(template_path) } if hasattr(config, "no_cache") and config.no_cache: settings["static_handler_class"] = NoCacheHandler application = tornado.web.Application([ (r"/", MainHandler), (r"/socket", CrawlWebSocket), (r"/gamedata/(.*)/(.*)", GameDataHandler) ], gzip=True, **settings) kwargs = {} if http_connection_timeout is not None: kwargs["connection_timeout"] = http_connection_timeout servers = [] if bind_nonsecure: server = tornado.httpserver.HTTPServer(application, **kwargs) try: listens = bind_pairs except NameError: listens = ( (bind_address, bind_port), ) for (addr, port) in listens: server.listen(port, addr) servers.append(server) if ssl_options: # TODO: allow different ssl_options per bind pair server = tornado.httpserver.HTTPServer(application, ssl_options = ssl_options, **kwargs) try: listens = ssl_bind_pairs except NameError: listens = ( (ssl_address, ssl_port), ) for (addr, port) in listens: server.listen(port, addr) servers.append(server) return servers def init_logging(logging_config): filename = logging_config.get("filename") if filename: max_bytes = logging_config.get("max_bytes", 10*1000*1000) backup_count = logging_config.get("backup_count", 5) hdlr = logging.handlers.RotatingFileHandler( filename, maxBytes=max_bytes, backupCount=backup_count) else: hdlr = logging.StreamHandler(None) fs = logging_config.get("format", "%(levelname)s:%(name)s:%(message)s") dfs = logging_config.get("datefmt", None) fmt = logging.Formatter(fs, dfs) hdlr.setFormatter(fmt) logging.getLogger().addHandler(hdlr) level = logging_config.get("level") if level is not None: logging.getLogger().setLevel(level) logging.getLogger().addFilter(TornadoFilter()) logging.addLevelName(logging.DEBUG, "DEBG") logging.addLevelName(logging.WARNING, "WARN") def check_config(): success = True for (game_id, game_data) in games.iteritems(): if not os.path.exists(game_data["crawl_binary"]): logging.warning("Crawl executable %s doesn't exist!", game_data["crawl_binary"]) success = False if ("client_path" in game_data and not os.path.exists(game_data["client_path"])): logging.warning("Client data path %s doesn't exist!", game_data["client_path"]) success = False return success if __name__ == "__main__": if chroot: os.chroot(chroot) init_logging(logging_config) if not check_config(): err_exit("Errors in config. Exiting.") if daemon: daemonize() signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGHUP, signal_handler) if umask is not None: os.umask(umask) write_pidfile() servers = bind_server() shed_privileges() if dgl_mode: ensure_user_db_exists() ioloop = tornado.ioloop.IOLoop.instance() ioloop.set_blocking_log_threshold(0.5) if dgl_mode: status_file_timeout() purge_login_tokens_timeout() start_reading_milestones() if watch_socket_dirs: process_handler.watch_socket_dirs() logging.info("Webtiles server started! (PID: %s)" % os.getpid()) try: ioloop.start() except KeyboardInterrupt: logging.info("Received keyboard interrupt, shutting down.") for server in servers: server.stop() shutdown() ioloop.add_timeout(time.time() + 2, ioloop.stop) if len(sockets) > 0: ioloop.start() # We'll wait until all crawl processes have ended. remove_pidfile()