BVB Source Codes

voltron Show core.py Source code

Return Download voltron: download core.py Source code - Download voltron Source code - Type:.py
  1. import errno
  2. import json
  3. import logging
  4. import logging.config
  5. import os
  6. import os.path
  7. import pkgutil
  8. import select
  9. import signal
  10. import socket
  11. import sys
  12. import threading
  13. import time
  14. import six
  15. import voltron
  16. from flask import Flask, Response, make_response, redirect, render_template, request
  17. from werkzeug.serving import BaseWSGIServer, ThreadedWSGIServer, WSGIRequestHandler
  18. from werkzeug.wsgi import DispatcherMiddleware, SharedDataMiddleware
  19. from requests import ConnectionError
  20.  
  21.  
  22. # import pysigset
  23.  
  24. from .api import *
  25. from .plugin import *
  26.  
  27. try:
  28.     import requests_unixsocket as requests
  29. except:
  30.     import requests
  31.  
  32. if six.PY2:
  33.     if sys.platform == 'win32':
  34.         from SocketServer import ThreadingMixIn
  35.     else:
  36.         from SocketServer import UnixStreamServer, ThreadingMixIn
  37.     from BaseHTTPServer import HTTPServer
  38. else:
  39.     if sys.platform == 'win32':
  40.         from six.moves.socketserver import ThreadingMixIn
  41.     else:
  42.         from six.moves.socketserver import UnixStreamServer, ThreadingMixIn
  43.     from six.moves.BaseHTTPServer import HTTPServer
  44.  
  45. ThreadingMixIn.daemon_threads = True
  46.  
  47. try:
  48.     from voltron_web import app as ui_app
  49. except:
  50.     ui_app = None
  51.  
  52. logging.getLogger("requests").setLevel(logging.WARNING)
  53.  
  54.  
  55. def get_loader(name):
  56.     try:
  57.         return orig_get_loader(name)
  58.     except AttributeError:
  59.         pass
  60. orig_get_loader = pkgutil.get_loader
  61. pkgutil.get_loader = get_loader
  62.  
  63.  
  64. # make sure we use HTTP 1.1 for keep-alive
  65. WSGIRequestHandler.protocol_version = "HTTP/1.1"
  66.  
  67. log = logging.getLogger("core")
  68.  
  69. if sys.version_info.major == 2:
  70.     STRTYPES = (str, unicode)
  71. elif sys.version_info.major == 3:
  72.     STRTYPES = (str, bytes)
  73. else:
  74.     raise RuntimeError("Not sure what strings look like on python %d" %
  75.                        sys.version_info.major)
  76.  
  77.  
  78. class APIFlaskApp(Flask):
  79.     """
  80.    A Flask app for the API.
  81.    """
  82.     def __init__(self, *args, **kwargs):
  83.         if 'server' in kwargs:
  84.             self.server = kwargs['server']
  85.             del kwargs['server']
  86.         super(APIFlaskApp, self).__init__('voltron_api', *args, **kwargs)
  87.  
  88.         def api_post():
  89.             res = self.server.handle_request(request.data.decode('UTF-8'))
  90.             return Response(str(res), status=200, mimetype='application/json')
  91.  
  92.         def api_get():
  93.             res = self.server.handle_request(str(api_request(request.path.split('/')[-1], **request.args.to_dict())))
  94.             return Response(str(res), status=200, mimetype='application/json')
  95.  
  96.         # Handle API POST requests at /api/request
  97.         api_post.methods = ["POST"]
  98.         self.add_url_rule('/request', 'request', api_post)
  99.  
  100.         # Handle API GET requests at /api/<request_name> e.g. /api/version
  101.         for plugin in voltron.plugin.pm.api_plugins:
  102.             self.add_url_rule('/{}'.format(plugin), plugin, api_get)
  103.  
  104.  
  105. class RootFlaskApp(Flask):
  106.     """
  107.    A Flask app for /.
  108.    """
  109.     def __init__(self, *args, **kwargs):
  110.         super(RootFlaskApp, self).__init__('voltron', *args, **kwargs)
  111.  
  112.         def index():
  113.             if ui_app:
  114.                 return redirect('/ui')
  115.             else:
  116.                 return "The Voltron web interface is not installed. Install the <tt>voltron-web</tt> package with <tt>pip</tt>."
  117.  
  118.         self.add_url_rule('/', 'index', index)
  119.  
  120.  
  121. class Server(object):
  122.     """
  123.    Main server class instantiated by the debugger host. Responsible for
  124.    controlling the background thread that communicates with clients, and
  125.    handling requests forwarded from that thread.
  126.    """
  127.     def __init__(self):
  128.         self.threads = []
  129.         self.listeners = []
  130.         self.is_running = False
  131.         self.queue = []
  132.         self.queue_lock = threading.Lock()
  133.  
  134.     def start(self):
  135.         """
  136.        Start the server.
  137.        """
  138.         plugins = voltron.plugin.pm.web_plugins
  139.         self.app = DispatcherMiddleware(
  140.             RootFlaskApp(),
  141.             {
  142.                 "/api": APIFlaskApp(server=self),
  143.                 "/view": SharedDataMiddleware(
  144.                     None,
  145.                     {'/{}'.format(n): os.path.join(p._dir, 'static') for (n, p) in six.iteritems(plugins)}
  146.                 ),
  147.                 "/ui": ui_app
  148.             }
  149.         )
  150.  
  151.         def run_listener(name, cls, arg):
  152.             # with pysigset.suspended_signals(signal.SIGCHLD):
  153.             log.debug("Starting listener for {} socket on {}".format(name, str(arg)))
  154.             s = cls(*arg)
  155.             t = threading.Thread(target=s.serve_forever)
  156.             t.daemon = True
  157.             t.start()
  158.             self.threads.append(t)
  159.             self.listeners.append(s)
  160.  
  161.         if voltron.config.server.listen.tcp:
  162.             run_listener('tcp', ThreadedVoltronWSGIServer, list(voltron.config.server.listen.tcp) + [self.app])
  163.  
  164.         if voltron.config.server.listen.domain and sys.platform != 'win32':
  165.             path = os.path.expanduser(str(voltron.config.server.listen.domain))
  166.             try:
  167.                 os.unlink(path)
  168.             except:
  169.                 pass
  170.             run_listener('domain', ThreadedUnixWSGIServer, [path, self.app])
  171.  
  172.         self.is_running = True
  173.  
  174.     def stop(self):
  175.         """
  176.        Stop the server.
  177.        """
  178.         log.debug("Stopping listeners")
  179.         self.queue_lock.acquire()
  180.         for s in self.listeners:
  181.             log.debug("Stopping {}".format(s))
  182.             s.shutdown()
  183.             s.socket.close()
  184.         self.cancel_queue()
  185.         for t in self.threads:
  186.             t.join()
  187.         self.listeners = []
  188.         self.threads = []
  189.         self.is_running = False
  190.         self.queue_lock.release()
  191.         log.debug("Listeners stopped and threads joined")
  192.  
  193.     def handle_request(self, data):
  194.         req = None
  195.         res = None
  196.  
  197.         if self.is_running:
  198.             # make sure we have a debugger, or we're gonna have a bad time
  199.             if voltron.debugger:
  200.                 # parse incoming request with the top level APIRequest class so we can determine the request type
  201.                 try:
  202.                     req = APIRequest(data=data)
  203.                 except Exception as e:
  204.                     req = None
  205.                     log.exception("Exception raised while parsing API request: {} {}".format(type(e), e))
  206.  
  207.                 if req:
  208.                     # instantiate the request class
  209.                     try:
  210.                         log.debug("data = {}".format(data))
  211.                         req = api_request(req.request, data=data)
  212.                     except Exception as e:
  213.                         log.exception("Exception raised while creating API request: {} {}".format(type(e), e))
  214.                         req = None
  215.                     if not req:
  216.                         res = APIPluginNotFoundErrorResponse()
  217.                 else:
  218.                     res = APIInvalidRequestErrorResponse()
  219.             else:
  220.                 res = APIDebuggerNotPresentErrorResponse()
  221.  
  222.             if not res:
  223.                 # no errors so far, queue the request and wait
  224.                 if req and req.block:
  225.                     self.queue_lock.acquire()
  226.                     self.queue.append(req)
  227.                     self.queue_lock.release()
  228.  
  229.                     # When this returns the request will have been processed by the dispatch_queue method on the main
  230.                     # thread (or timed out). We have to do it this way because GDB sucks.
  231.                     req.wait()
  232.  
  233.                     if req.timed_out:
  234.                         res = APITimedOutErrorResponse()
  235.                     else:
  236.                         res = req.response
  237.  
  238.                     # Remove the request from the queue
  239.                     self.queue_lock.acquire()
  240.                     if req in self.queue:
  241.                         self.queue.remove(req)
  242.                     self.queue_lock.release()
  243.                 else:
  244.                     # non-blocking, dispatch request straight away
  245.                     res = self.dispatch_request(req)
  246.         else:
  247.             res = APIServerNotRunningErrorResponse()
  248.  
  249.         return res
  250.  
  251.     def cancel_queue(self):
  252.         """
  253.        Cancel all requests in the queue so we can exit.
  254.        """
  255.         q = list(self.queue)
  256.         self.queue = []
  257.         log.debug("Canceling requests: {}".format(q))
  258.         for req in q:
  259.             req.response = APIServerNotRunningErrorResponse()
  260.         for req in q:
  261.             req.signal()
  262.  
  263.     def dispatch_queue(self):
  264.         """
  265.        Dispatch any queued requests.
  266.  
  267.        Called by the debugger when it stops.
  268.        """
  269.         self.queue_lock.acquire()
  270.         q = list(self.queue)
  271.         self.queue = []
  272.         self.queue_lock.release()
  273.         log.debug("Dispatching requests: {}".format(q))
  274.         for req in q:
  275.             req.response = self.dispatch_request(req)
  276.         for req in q:
  277.             req.signal()
  278.  
  279.     def dispatch_request(self, req):
  280.         """
  281.        Dispatch a request object.
  282.        """
  283.         log.debug("Dispatching request: {}".format(str(req)))
  284.  
  285.         # make sure it's valid
  286.         res = None
  287.         try:
  288.             req.validate()
  289.         except MissingFieldError as e:
  290.             res = APIMissingFieldErrorResponse(str(e))
  291.  
  292.         # dispatch the request
  293.         if not res:
  294.             try:
  295.                 res = req.dispatch()
  296.             except Exception as e:
  297.                 msg = "Exception raised while dispatching request: {}".format(repr(e))
  298.                 log.exception(msg)
  299.                 res = APIGenericErrorResponse(msg)
  300.  
  301.         log.debug("Response: {}".format(str(res)))
  302.  
  303.         return res
  304.  
  305.  
  306. class VoltronWSGIServer(BaseWSGIServer):
  307.     """
  308.    Custom version of the werkzeug WSGI server.
  309.  
  310.    This just needs to exist so we can swallow errors when clients disconnect.
  311.    """
  312.     clients = []
  313.  
  314.     def finish_request(self, *args):
  315.         self.clients.append(args[0])
  316.         log.debug("finish_request({})".format(args))
  317.         try:
  318.             super(VoltronWSGIServer, self).finish_request(*args)
  319.         except socket.error as e:
  320.             log.error("Error in finish_request: {}".format(e))
  321.  
  322.     def shutdown(self):
  323.         super(VoltronWSGIServer, self).shutdown()
  324.         for c in self.clients:
  325.             try:
  326.                 c.shutdown(socket.SHUT_RD)
  327.                 c.close()
  328.             except:
  329.                 pass
  330.  
  331.  
  332. class ThreadedVoltronWSGIServer(ThreadingMixIn, VoltronWSGIServer):
  333.     """
  334.    Threaded WSGI server to replace werkzeug's
  335.    """
  336.     pass
  337.  
  338.  
  339. if sys.platform != 'win32':
  340.     class UnixWSGIServer(UnixStreamServer, VoltronWSGIServer):
  341.         """
  342.        A subclass of BaseWSGIServer that does sane things with Unix domain sockets.
  343.        """
  344.         def __init__(self, sockfile=None, app=None):
  345.             self.address_family = socket.AF_UNIX
  346.             UnixStreamServer.__init__(self, sockfile, UnixWSGIRequestHandler)
  347.             self.app = app
  348.             self.passthrough_errors = None
  349.             self.shutdown_signal = False
  350.             self.ssl_context = None
  351.             self.host = 'localhost'
  352.             self.port = 0
  353.  
  354.     class UnixWSGIRequestHandler(WSGIRequestHandler):
  355.         """
  356.        A WSGIRequestHandler that does sane things with Unix domain sockets.
  357.        """
  358.         def make_environ(self, *args, **kwargs):
  359.             self.client_address = ('127.0.0.1', 0)
  360.             return super(UnixWSGIRequestHandler, self).make_environ(*args, **kwargs)
  361.  
  362.     class ThreadedUnixWSGIServer(ThreadingMixIn, UnixWSGIServer):
  363.         """
  364.        Threaded HTTP server that works over Unix domain sockets.
  365.  
  366.        Note: this intentionally does not inherit from HTTPServer. Go look at the
  367.        source and you'll see why.
  368.        """
  369.         multithread = True
  370.  
  371.  
  372. class ClientThread(threading.Thread):
  373.     """
  374.    A thread that performs an API request with a client.
  375.    """
  376.     def __init__(self, client, request, *args, **kwargs):
  377.         self.request = request
  378.         self.response = None
  379.         self.exception = None
  380.         self.client = client
  381.         super(ClientThread, self).__init__(*args, **kwargs)
  382.  
  383.     def run(self):
  384.         try:
  385.             self.response = self.client.send_request(self.request)
  386.         except Exception as e:
  387.             self.exception = e
  388.  
  389.  
  390. class Client(object):
  391.     """
  392.    Used by a client (ie. a view) to communicate with the server.
  393.    """
  394.     def __init__(self, host='127.0.0.1', port=5555, sockfile=None, url=None,
  395.                  build_requests=None, callback=None, supports_blocking=True):
  396.         """
  397.        Initialise a new client
  398.        """
  399.         self.session = requests.Session()
  400.         if url:
  401.             self.url = url
  402.         elif sockfile:
  403.             self.url = 'http+unix://{}/api/request'.format(sockfile.replace('/', '%2F'))
  404.         elif voltron.config.view.api_url:
  405.             self.url = voltron.config.view.api_url
  406.         else:
  407.             self.url = 'http://{}:{}/api/request'.format(host, port)
  408.         self.url = self.url.replace('~', os.path.expanduser('~').replace('/', '%2f'))
  409.         self.callback = callback
  410.         self.build_requests = build_requests
  411.         self.done = False
  412.         self.server_version = None
  413.         self.block = False
  414.         self.supports_blocking = supports_blocking
  415.  
  416.     def send_request(self, request):
  417.         """
  418.        Send a request to the server.
  419.  
  420.        `request` is an APIRequest subclass.
  421.  
  422.        Returns an APIResponse or subclass instance. If an error occurred, it
  423.        will be an APIErrorResponse, if the request was successful it will be
  424.        the plugin's specified response class if one exists, otherwise it will
  425.        be an APIResponse.
  426.        """
  427.         # default to an empty response error
  428.         res = APIEmptyResponseErrorResponse()
  429.  
  430.         # perform the request
  431.         log.debug("Client sending request: " + str(request))
  432.         response = self.session.post(self.url, data=str(request))
  433.         data = response.text
  434.         if response.status_code != 200:
  435.             res = APIGenericErrorResponse(response.text)
  436.         elif data and len(data) > 0:
  437.             log.debug('Client received message: ' + data)
  438.  
  439.             try:
  440.                 # parse the response data
  441.                 generic_response = APIResponse(data=data)
  442.  
  443.                 # if there's an error, return an error response
  444.                 if generic_response.is_error:
  445.                     res = APIErrorResponse(data=data)
  446.                 else:
  447.                     # success; generate a proper response
  448.                     plugin = voltron.plugin.pm.api_plugin_for_request(request.request)
  449.                     if plugin and plugin.response_class:
  450.                         # found a plugin for the request we sent, use its response type
  451.                         res = plugin.response_class(data=data)
  452.                     else:
  453.                         # didn't find a plugin, just return the generic APIResponse we already generated
  454.                         res = generic_response
  455.             except Exception as e:
  456.                 log.exception('Exception parsing message: ' + str(e))
  457.                 log.error('Invalid message: ' + data)
  458.         else:
  459.             res = APIEmptyResponseErrorResponse()
  460.  
  461.         return res
  462.  
  463.     def send_requests(self, *args):
  464.         """
  465.        Send a set of requests.
  466.  
  467.        Each request is sent over its own connection and the function will
  468.        return when all the requests have been fulfilled.
  469.        """
  470.         threads = [ClientThread(self, req) for req in args]
  471.         for t in threads:
  472.             t.start()
  473.         for t in threads:
  474.             t.join()
  475.         exceptions = [t.exception for t in threads if t.exception]
  476.         if len(exceptions):
  477.             raise exceptions[0]
  478.         return [t.response for t in threads]
  479.  
  480.     def create_request(self, request_type, *args, **kwargs):
  481.         """
  482.        Create a request.
  483.  
  484.        `request_type` is the request type (string). This is used to look up a
  485.        plugin, whose request class is instantiated and passed the remaining
  486.        arguments passed to this function.
  487.        """
  488.         return api_request(request_type, *args, **kwargs)
  489.  
  490.     def perform_request(self, request_type, *args, **kwargs):
  491.         """
  492.        Create and send a request.
  493.  
  494.        `request_type` is the request type (string). This is used to look up a
  495.        plugin, whose request class is instantiated and passed the remaining
  496.        arguments passed to this function.
  497.        """
  498.         # create a request
  499.         req = api_request(request_type, *args, **kwargs)
  500.  
  501.         # send it
  502.         res = self.send_request(req)
  503.  
  504.         return res
  505.  
  506.     def update(self):
  507.         """
  508.        Update the display
  509.        """
  510.         # build requests for this iteration
  511.         reqs = self.build_requests()
  512.         for r in reqs:
  513.             r.block = self.block
  514.         results = self.send_requests(*reqs)
  515.  
  516.         # call callback with the results
  517.         self.callback(results)
  518.  
  519.     def run(self, build_requests=None, callback=None):
  520.         """
  521.        Run the client in a loop, calling the callback each time the debugger
  522.        stops.
  523.        """
  524.         if callback:
  525.             self.callback = callback
  526.         if build_requests:
  527.             self.build_requests = build_requests
  528.  
  529.         def normalise_requests_err(e):
  530.             try:
  531.                 msg = e.message.args[1].strerror
  532.             except:
  533.                 try:
  534.                     msg = e.message.args[0]
  535.                 except:
  536.                     msg = str(e)
  537.             return msg
  538.  
  539.         while not self.done:
  540.             try:
  541.  
  542.                 # get the server version info
  543.                 if not self.server_version:
  544.                     self.server_version = self.perform_request('version')
  545.  
  546.                     # if the server supports async mode, use it, as some views may only work in async mode
  547.                     if self.server_version.capabilities and 'async' in self.server_version.capabilities:
  548.                         self.update()
  549.                         self.block = False
  550.                     elif self.supports_blocking:
  551.                         self.block = True
  552.                     else:
  553.                         raise BlockingNotSupportedError("Debugger requires blocking mode")
  554.  
  555.                 if self.block:
  556.                     # synchronous requests
  557.                     self.update()
  558.                 else:
  559.                     # async requests, block using a null request until the debugger stops again
  560.                     res = self.perform_request('null', block=True)
  561.                     if res.is_success:
  562.                         self.server_version = res
  563.                         self.update()
  564.             except ConnectionError as e:
  565.                 self.callback(error='Error: {}'.format(normalise_requests_err(e)))
  566.                 self.server_version = None
  567.                 time.sleep(1)
  568.  
  569.     def start(self, build_requests=None, callback=None):
  570.         """
  571.        Run the client using a background thread.
  572.        """
  573.         if callback:
  574.             self.callback = callback
  575.         if build_requests:
  576.             self.build_requests = build_requests
  577.  
  578.         # spin off requester thread
  579.         self.sw = threading.Thread(target=self.run)
  580.         self.sw.start()
  581.  
  582.     def stop(self):
  583.         """
  584.        Stop the background thread.
  585.        """
  586.         self.done = True
  587.  
downloadcore.py Source code - Download voltron Source code
Related Source Codes/Software:
IGListKit - A data-driven UICollectionView framework for build... 2017-01-11
androidmvp - MVP Android Example 2017-01-11
svelte - The magical disappearing UI framework ... 2017-01-11
kcptun - A Simple UDP Tunnel Based On KCP 2017-01-11
poisontap - Exploits locked/password protected computers over ... 2017-01-10
ecma262 - Status, process, and documents for ECMA262 ... 2017-01-11
react-native-elements - React Native Elements UI Toolkit 2017-01-11
tensorflow-zh - Google new open source ai system TensorFlow Chines... 2017-01-11
forum - Blue light (the Lantern) the official BBS 2017-01-11
socketcluster - Highly scalable realtime framework ... 2017-01-11
discover-flask - Full Stack Web Development with Flask. ... 2017-01-12
spring-mvc-showcase - Demonstrates the features of the Spring MVC web fr... 2017-01-12
tushare - TuShare is a utility for crawling historical data ... 2017-01-12
raml-spec - RAML Specification http://raml.... 2017-01-12
android-stackblur - Android StackBlur is a library that can perform a ... 2017-01-12
sound-redux - A Soundcloud client built with React / Redux ... 2017-01-12
httpstat - curl statistics made simple 2017-01-12
vim - Vim configuration file and plug-ins 2017-01-12
appframework - The definitive HTML5 mobile javascript framework ... 2017-01-12
BaiduExporter - Assistant for Baidu to export download links to ar... 2017-01-11

 Back to top