Last active
November 22, 2016 09:30
-
-
Save AlexYukikaze/ee8cf9fdf337f3b77565 to your computer and use it in GitHub Desktop.
World of Tanks remote REPL console for interactive debugging
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from mods.remote import create_terminal | |
from gui import SystemMessages | |
def add_info(text): | |
SystemMessages.pushMessage(text, type=SystemMessages.SM_TYPE.Information) | |
def add_warning(text): | |
SystemMessages.pushMessage(text, type=SystemMessages.SM_TYPE.Warning) | |
HOST, PORT = "localhost", 9999 | |
BANNER = """### WELCOME TO REMOTE CONSOLE ###""" | |
LOCALS = dict(author='Alex Yukikaze', info=add_info, warn=add_warning) | |
# Create the server, binding to localhost on port 9999 | |
remote_terminal= create_terminal(HOST, PORT, banner=BANNER, local=LOCALS) | |
remote_terminal.update_locals(host=HOST, port=PORT) | |
# remote_terminal.exit() # for stop listening and unbind socket | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
Simple remote REPL | |
$ nc 127.0.0.1 9999 | |
@author: Alex Yukikaze (Putin Alexander) | |
@year: 2014 | |
''' | |
import SocketServer | |
import contextlib | |
import threading | |
import code | |
@contextlib.contextmanager | |
def std_redirector(stdin, stdout, stderr): | |
import sys | |
orig_fds = sys.stdin, sys.stdout, sys.stderr | |
sys.stdin, sys.stdout, sys.stderr = stdin, stdout, stderr | |
yield | |
sys.stdin, sys.stdout, sys.stderr = orig_fds | |
class Event(object): | |
def __init__(self): | |
self.__handlers = [] | |
def __iadd__(self, handler): | |
self.__handlers.append(handler) | |
return self | |
def __isub__(self, handler): | |
self.__handlers.remove(handler) | |
return self | |
def invoke(self, *args, **kwargs): | |
for handler in self.__handlers: | |
handler(*args, **kwargs) | |
def clear(self, inObject): | |
for handler in self.__handlers: | |
if handler.im_self == inObject: | |
self -= handler | |
class Interpreter: | |
def __init__(self, local={}): | |
self.console = code.InteractiveConsole(local) | |
self.console.locals['exit'] = self.exit | |
def interact(self, banner=''): | |
print banner | |
self.__running = True | |
more = False | |
try: | |
while self.__running: | |
line = raw_input('>>> ' if not more else '... ') | |
more = self.console.push(line) | |
except: | |
pass | |
def updateLocals(self, new_locals): | |
self.console.locals = new_locals | |
def exit(self): | |
self.__running = False | |
class __TerminalServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): | |
def __init__(self, addr, handler, banner='', local={}): | |
if 'exit' in local: | |
raise Exception('exit is reserved keyword') | |
import socket | |
try: | |
SocketServer.TCPServer.__init__(self, addr, handler) | |
except socket.error, e: | |
raise Exception('Socket error [%d]' % e.errno) | |
self.onLocalsChanged = Event() | |
self.allow_reuse_address = True | |
self.banner = banner | |
self.locals = local | |
self.__handlers_counter = 0 | |
self.__lock = threading.Lock() | |
def run_async(self): | |
worker = threading.Thread(target=self.serve_forever) | |
worker.daemon = True | |
worker.start() | |
return worker | |
def update_locals(self, **kwargs): | |
for attr in kwargs: | |
self.locals[attr] = kwargs[attr] | |
self.onLocalsChanged.invoke(self.locals) | |
def exit(self): | |
try: | |
self.shutdown() | |
self.socket.close() | |
except Exception, e: | |
print e | |
@property | |
def isBusy(self): | |
return self.__handlers_counter > 1 | |
@contextlib.contextmanager | |
def controller(self, handler): | |
with self.__lock: | |
self.onLocalsChanged += handler.updateLocals | |
self.__handlers_counter += 1 | |
yield | |
with self.__lock: | |
self.__handlers_counter -= 1 | |
self.onLocalsChanged -= handler.updateLocals | |
class __ConnectionHandler(SocketServer.StreamRequestHandler): | |
def handle(self): | |
self.__interpreter = Interpreter(self.server.locals) | |
with self.server.controller(self): | |
if self.server.isBusy: | |
self.send('Remote terminal is busy') | |
return | |
with std_redirector(self.rfile, self.wfile, self.wfile): | |
self.__interpreter.interact(banner=self.server.banner) | |
def send(self, data): | |
return self.wfile.write(data) | |
def updateLocals(self, new_locals): | |
if self.__interpreter: | |
self.__interpreter.updateLocals(new_locals) | |
def create_terminal(host, port, banner='', local={}): | |
server = __TerminalServer((host, port), __ConnectionHandler, | |
banner=banner, local=local) | |
server.daemon_threads = True | |
server.run_async() | |
return server |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment