/examples/web/terminal/terminal.py

https://bitbucket.org/prologic/circuits/ · Python · 139 lines · 101 code · 35 blank · 3 comment · 12 complexity · f6e1c3bd50d1e0340b10174e05a7d6bb MD5 · raw file

  1. #!/usr/bin/env python
  2. import os
  3. import signal
  4. from StringIO import StringIO
  5. from subprocess import Popen, PIPE
  6. from circuits.io import File
  7. from circuits.tools import inspect
  8. from circuits.net.events import write
  9. from circuits.web.events import stream
  10. from circuits import handler, Event, Component
  11. from circuits.web import Server, Controller, Logger, Static, Sessions
  12. BUFFERING = 1
  13. STREAMING = 2
  14. class kill(Event):
  15. """kill Event"""
  16. class input(Event):
  17. """input Event"""
  18. class Command(Component):
  19. channel = "cmd"
  20. def __init__(self, request, response, command, channel=channel):
  21. super(Command, self).__init__(channel=channel)
  22. self._request = request
  23. self._response = response
  24. self._command = command
  25. self._state = BUFFERING
  26. self._buffer = None
  27. self._p = Popen(
  28. command, shell=True, stdout=PIPE, stderr=PIPE,
  29. close_fds=True, preexec_fn=os.setsid
  30. )
  31. self._stdin = None
  32. if self._p.stdin is not None:
  33. self._stdin = File(self._p.stdin, channel="%s.stdin" % channel)
  34. self._stdin.register(self)
  35. self._stdout = File(self._p.stdout, channel="%s.stdout" % channel)
  36. self.addHandler(
  37. handler("eof", channel="%s.stdout" % channel)(self._on_stdout_eof)
  38. )
  39. self.addHandler(
  40. handler("read", channel="%s.stdout" % channel)(
  41. self._on_stdout_read
  42. )
  43. )
  44. self._stdout.register(self)
  45. @handler("disconnect", channel="web")
  46. def disconnect(self, sock):
  47. if sock == self._request.sock:
  48. self.fire(kill(), self)
  49. @handler("response", channel="web", priority=-1)
  50. def response(self, response):
  51. if response == self._response:
  52. self._state = STREAMING
  53. def kill(self):
  54. os.killpg(self._p.pid, signal.SIGINT)
  55. self.unregister()
  56. def input(self, data):
  57. if self._stdin is not None:
  58. self.fire(write(data), self._stdin)
  59. @staticmethod
  60. def _on_stdout_eof(self):
  61. if self._buffer is not None:
  62. self._buffer.flush()
  63. data = self._buffer.getvalue()
  64. self.fire(stream(self._response, data), "web")
  65. self.fire(stream(self._response, None), "web")
  66. self.fire(kill())
  67. @staticmethod
  68. def _on_stdout_read(self, data):
  69. if self._state == BUFFERING:
  70. if self._buffer is None:
  71. self._buffer = StringIO()
  72. self._buffer.write(data)
  73. elif self._state == STREAMING:
  74. if self._buffer is not None:
  75. self._buffer.write(data)
  76. self._buffer.flush()
  77. data = self._buffer.getvalue()
  78. self._buffer = None
  79. self.fire(stream(self._response, data), "web")
  80. else:
  81. self.fire(stream(self._response, data), "web")
  82. class Root(Controller):
  83. def GET(self, *args, **kwargs):
  84. self.expires(60 * 60 * 24 * 30)
  85. return self.serve_file(os.path.abspath("static/index.xhtml"))
  86. def POST(self, input=None):
  87. if not input:
  88. return ""
  89. self.response.headers["Content-Type"] = "text/plain"
  90. if input.strip() == "inspect":
  91. return inspect(self)
  92. self.response.stream = True
  93. sid = self.request.sid
  94. self += Command(self.request, self.response, input, channel=sid)
  95. return self.response
  96. from circuits import Debugger
  97. app = Server(("0.0.0.0", 8000))
  98. Debugger().register(app)
  99. Static("/js", docroot="static/js").register(app)
  100. Static("/css", docroot="static/css").register(app)
  101. Sessions().register(app)
  102. Logger().register(app)
  103. Root().register(app)
  104. app.run()