'''
The HTTP server to serve local portal and provide RESTful APIs
'''
import os
import pkg_resources
from twisted.web.server import Site
from twisted.web.static import File
from twisted.internet import reactor
from twisted.internet.endpoints import serverFromString
from klein import Klein
from txsockjs.factory import SockJSResource
from autobahn.twisted.resource import WebSocketResource
from paradrop.core.chute.chute_storage import ChuteStorage
from paradrop.core.system.system_status import SystemStatus
from .airshark_api import AirsharkApi
from .airshark_ws import AirsharkSpectrumFactory, AirsharkAnalyzerFactory
from .audio_api import AudioApi
from .auth import requires_auth, AuthApi
from .change_api import ChangeApi
from .change_ws import ChangeStreamFactory
from .chute_api import ChuteApi
from .chute_log_ws import ChuteLogWsFactory
from .config_api import ConfigApi
from .information_api import InformationApi
from .log_sockjs import LogSockJSFactory
from .network_api import NetworkApi
from .paradrop_log_ws import ParadropLogWsFactory
from .password_api import PasswordApi
from .password_manager import PasswordManager
from .snapd_resource import SnapdResource
from .status_sockjs import StatusSockJSFactory
from .token_manager import TokenManager
[docs]class HttpServer(object):
app = Klein()
def __init__(self, update_manager, update_fetcher, airshark_manager, portal_dir=None):
self.update_manager = update_manager
self.update_fetcher = update_fetcher
self.system_status = SystemStatus()
self.password_manager = PasswordManager()
self.token_manager = TokenManager()
self.airshark_manager = airshark_manager
if portal_dir:
self.portal_dir = portal_dir
elif 'SNAP' in os.environ:
self.portal_dir = os.environ['SNAP'] + '/www'
else:
self.portal_dir = pkg_resources.resource_filename('paradrop', 'static')
[docs] @app.route('/api/v1/audio', branch=True)
def api_audio(self, request):
return AudioApi().routes.resource()
[docs] @app.route('/api/v1/auth', branch=True)
def api_auth(self, request):
return AuthApi(self.password_manager, self.token_manager).routes.resource()
[docs] @app.route('/api/v1/changes/', branch=True)
@requires_auth
def api_changes(self, request):
return ChangeApi(self.update_manager).routes.resource()
[docs] @app.route('/api/v1/config', branch=True)
@requires_auth
def api_configuration(self, request):
return ConfigApi(self.update_manager, self.update_fetcher).routes.resource()
[docs] @app.route('/api/v1/chutes/', branch=True)
@requires_auth
def api_chute(self, request):
return ChuteApi(self.update_manager).routes.resource()
[docs] @app.route('/api/v1/password', branch=True)
@requires_auth
def api_password(self, request):
return PasswordApi(self.password_manager).routes.resource()
[docs] @app.route('/api/v1/airshark', branch=True)
@requires_auth
def api_airshark(self, request):
return AirsharkApi(self.airshark_manager).routes.resource()
[docs] @app.route('/api/v1/network', branch=True)
@requires_auth
def api_network(self, request):
return NetworkApi().routes.resource()
[docs] @app.route('/snapd/', branch=True)
@requires_auth
def snapd(self, request):
return SnapdResource()
'''
# Not being used for now because we are using HAProxy to setup the reverse proxy
@app.route('/chutes/<string:name>', branch=True)
def chutes(self, request, name):
try:
container = ChuteContainer(name)
ip = container.getIP()
return ReverseProxyResource(ip, 80, '/')
except ParadropException as error:
return str(error)
'''
[docs] @app.route('/sockjs/logs/<string:name>', branch=True)
@requires_auth
def logs(self, request, name):
#cors.config_cors(request)
options = {
'websocket': True,
'heartbeat': 5,
'timeout': 2,
}
chute = ChuteStorage.get_chute(name)
return SockJSResource(LogSockJSFactory(chute), options)
[docs] @app.route('/sockjs/status', branch=True)
@requires_auth
def status(self, request):
#cors.config_cors(request)
options = {
'websocket': True,
'heartbeat': 5,
'timeout': 2,
}
return SockJSResource(StatusSockJSFactory(self.system_status), options)
[docs] @app.route('/ws/chute_logs/<string:name>', branch=True)
@requires_auth
def chute_logs(self, request, name):
#cors.config_cors(request)
chute = ChuteStorage.get_chute(name)
factory = ChuteLogWsFactory(chute)
factory.setProtocolOptions(autoPingInterval=10, autoPingTimeout=5)
return WebSocketResource(factory)
[docs] @app.route('/ws/airshark/analyzer', branch=True)
@requires_auth
def airshark_analyzer(self, request):
#cors.config_cors(request)
factory = AirsharkAnalyzerFactory(self.airshark_manager)
factory.setProtocolOptions(autoPingInterval=10, autoPingTimeout=5)
return WebSocketResource(factory)
[docs] @app.route('/ws/airshark/spectrum', branch=True)
@requires_auth
def airshark_spectrum(self, request):
#cors.config_cors(request)
factory = AirsharkSpectrumFactory(self.airshark_manager)
factory.setProtocolOptions(autoPingInterval=10, autoPingTimeout=5)
return WebSocketResource(factory)
[docs] @app.route('/ws/paradrop_logs', branch=True)
@requires_auth
def paradrop_logs(self, request):
#cors.config_cors(request)
factory = ParadropLogWsFactory()
factory.setProtocolOptions(autoPingInterval=10, autoPingTimeout=5)
return WebSocketResource(factory)
[docs] @app.route('/ws/changes/<int:change_id>/stream', branch=True)
@requires_auth
def change_stream(self, request, change_id):
change = self.update_manager.find_change(change_id)
if change is not None:
factory = ChangeStreamFactory(change)
factory.setProtocolOptions(autoPingInterval=10, autoPingTimeout=5)
return WebSocketResource(factory)
else:
request.setResponseCode(404)
return "{}"
[docs] @app.route('/', branch=True)
@requires_auth
def home(self, request):
return File(self.portal_dir)
[docs]def setup_http_server(http_server, host, port):
endpoint = serverFromString(
reactor,
"tcp:port={0}:interface={1}".format(port, host)
)
endpoint.listen(Site(http_server.app.resource()))
[docs]def annotate_routes(router, prefix):
"""
Annotate klein routes for compatibility with autoflask generator.
"""
router.static_path = ""
router.view_functions = router.endpoints
for rule in router.url_map.iter_rules():
rule.doc_prefix = prefix
annotate_routes(AudioApi.routes, "/api/v1/audio")
annotate_routes(ChuteApi.routes, "/api/v1/chutes")
annotate_routes(ConfigApi.routes, "/api/v1/config")
annotate_routes(InformationApi.routes, "/api/v1/info")