blob: d3a4a10673ab1a027a178bb4f532a379f06140c9 [file] [log] [blame]
#// Copyright (c) 2000-2019 Ericsson Telecom AB //
#// All rights reserved. This program and the accompanying materials are made available under the terms //
#// of the Eclipse Public License v2.0 which accompanies this distribution, and is available at //
#// https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html //
#/////////////////////////////////////////////////////////////////////////////////////////////////////////
#!/usr/bin/python
'''
The main module of the framework.
AppAgent is an http server, which redirects http messages based on the message's uri extension to
microservices loaded dynamically from the src/Microservices directory. The microservices are handling the
application specific http requests by implementing the message handler interface.
An application based on this framework should contain the framework files, along with application specific
microservices located in the src/Microservices directory.
The DataSource handler is always present, and can be used by all microservices that handle DsRestAPI requests.
For more details on the microservice- and message handler interface, see the comments for the microservices
(src/Microservices/__init__.py).
'''
import threading, errno, os, sys, shutil, json, traceback, urllib2, logging, logging.config, ssl, importlib
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
from SocketServer import ThreadingMixIn
class FileHandler(SimpleHTTPRequestHandler):
'''A file handler that supports the methods needed by the framework'''
def _handleProxyRequests(self, method, userCredentials):
if userCredentials['username'] is not None:
parts = self._cleanPath(self.path).split('/', 2)
url = parts[1].decode('hex')
if len(parts) > 2:
url = url + '/' + parts[2]
body = None
if method == 'POST' or method == 'PUT':
body = self.rfile.read(int(self.headers.getheader('content-length')))
try:
request = urllib2.Request(url, body, self.headers)
request.get_method = lambda: method
response = urllib2.urlopen(request)
self.send_response(200)
headers = response.info()
for header in headers:
self.send_header(header, headers[header])
self.end_headers()
self.wfile.write(response.read())
except urllib2.HTTPError as e:
self.send_response(e.code)
self.end_headers()
except Exception as e:
self.send_response(404)
self.end_headers()
else:
self.send_response(401)
self.end_headers()
def _lsdir(self, path):
if path == '':
path = '.'
result = []
try:
files = os.listdir(path)
for file in files:
fileName = path + '/' + file
contentType = ''
if os.path.islink(fileName):
contentType += 'l'
else:
contentType += '-'
if os.path.isdir(fileName):
contentType += 'd'
else:
contentType += 'f'
stat = os.stat(fileName)
result.append({
'fileName': fileName,
'contentType': contentType,
'size': stat.st_size,
'timestamp': stat.st_mtime
})
except:
pass
return {'fileList': result}
def _mkdir(self, path):
try:
os.makedirs(path)
return True
except OSError as e:
if e.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
return False
def _rmdir(self, path):
try:
if not os.path.exists(path):
return
if os.path.isfile(path) or os.path.islink(path):
os.unlink(path)
else:
shutil.rmtree(path)
return True
except:
return False
def _cleanPath(self, path):
return path.strip('/')
def do_LSDIR(self):
'''Listing the contents of a directory'''
path = self._cleanPath(self.path)
if path.startswith('proxy'):
self._handleProxyRequests('LSDIR', self.server.getUserCredentials(self.headers))
else:
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(self._lsdir(path), indent = 4))
def do_RMDIR(self):
'''Deleting directory'''
userCredentials = self.server.getUserCredentials(self.headers)
if userCredentials['username'] is not None:
path = self._cleanPath(self.path)
if path.startswith('proxy'):
self._handleProxyRequests('RMDIR', userCredentials)
else:
if self._rmdir(path):
self.send_response(200)
else:
self.send_response(401)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
else:
self.send_response(401)
self.end_headers()
def do_MKDIR(self):
'''Creating a directory'''
userCredentials = self.server.getUserCredentials(self.headers)
if userCredentials['username'] is not None:
path = self._cleanPath(self.path)
if path.startswith('proxy'):
self._handleProxyRequests('MKDIR', userCredentials)
else:
if self._mkdir(path):
self.send_response(200)
else:
self.send_response(401)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
else:
self.send_response(401)
self.end_headers()
def do_PUT(self):
'''Saving a file'''
userCredentials = self.server.getUserCredentials(self.headers)
if userCredentials['username'] is not None:
path = self._cleanPath(self.path)
if path.startswith('proxy'):
self._handleProxyRequests('PUT', userCredentials)
else:
try:
with open(path, 'w') as f:
f.write(self.rfile.read(int(self.headers.getheader('content-length'))))
self.send_response(200)
except:
self.send_response(401)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
else:
self.send_response(401)
self.end_headers()
class MainHandler(FileHandler):
'''The main handler of the HTTP server'''
def _useHandlers(self, method, path, headers, body, userCredentials):
'''Try to find a handler based on the extension and handle the request'''
try:
pathForExtension = path.split('?')[0]
if '/' in pathForExtension:
extension = pathForExtension[pathForExtension.rindex('/') + 1:]
else:
extension = pathForExtension
handler = self.server.requestHandlers[extension]
response = {
'returnCode': 200,
'mimeType': 'text/plain',
'body': '',
'headers': {}
}
handler.handleMessage(method, path, headers, body, userCredentials, response)
self.send_response(response['returnCode'])
if 'Content-Type' not in response['headers']:
self.send_header('Content-Type', 'text/plain')
for header in response['headers']:
self.send_header(header, response['headers'][header])
self.end_headers()
self.wfile.write(response['body'])
return True
except:
return False
def do_GET(self):
'''Handler GET requests'''
path = self._cleanPath(self.path)
if path.startswith('proxy'):
self._handleProxyRequests('GET', self.server.getUserCredentials(self.headers))
else:
if not self._useHandlers('GET', path, self.headers, '', self.server.getUserCredentials(self.headers)):
SimpleHTTPRequestHandler.do_GET(self)
def do_POST(self):
'''Handler POST requests'''
userCredentials = self.server.getUserCredentials(self.headers)
path = self._cleanPath(self.path)
if path.startswith('proxy'):
self._handleProxyRequests('POST', userCredentials)
else:
if not self._useHandlers('POST', path, self.headers, self.rfile.read(int(self.headers.getheader('content-length'))), userCredentials):
self.send_response(404)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(self.path)
def log_message(self, format, *args):
'''Disables logging'''
return
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
'''HTTP server that handles requests in a separate thread.'''
def runAppAgent(server, directory, htdocs, ssl, appList = None):
logger = logging.getLogger('AppAgent')
sys.path.insert(0, directory)
server.requestHandlers = {}
dsRestApiHandlers = {}
guiDirectory = os.path.normpath(os.path.join(directory, htdocs))
webAppDirectory = os.path.join(guiDirectory, 'WebApplications')
customizationDirectory = os.path.join(guiDirectory, 'CustomizableContent')
createdLinks = set()
originalDirectory = os.getcwd()
os.chdir(guiDirectory)
microservicesDirectory = os.path.join(directory, 'Microservices')
if appList is None:
appList = os.listdir(microservicesDirectory)
for app in appList:
appPath = os.path.join(microservicesDirectory, app)
if os.path.isdir(appPath):
appName = 'Microservices.' + app
try:
appWebAppDir = os.path.join(appPath, 'WebApplication')
appGuiDir = os.path.join(appPath, 'GUI')
# link webapp
if os.path.isdir(appWebAppDir):
linkFile = os.path.join(webAppDirectory, app)
createdLinks.add(linkFile)
if not os.path.exists(linkFile):
os.symlink(os.path.relpath(appWebAppDir, webAppDirectory), linkFile)
logger.info('Linked WebApp: ' + os.path.relpath(appWebAppDir, webAppDirectory) + ' -> ' + linkFile)
else:
logger.warning('Linked WebApp: ' + app + ' already exists')
# link customization
if os.path.isdir(appGuiDir):
linkFile = os.path.join(customizationDirectory, app)
createdLinks.add(linkFile)
if not os.path.exists(linkFile):
os.symlink(os.path.relpath(appGuiDir, customizationDirectory), linkFile)
logger.info('Linked GUI: ' + os.path.relpath(appGuiDir, customizationDirectory) + ' -> ' + linkFile)
else:
logger.warning('Linked GUI: ' + app + ' already exists')
# load service module
module = importlib.import_module(appName)
# create the handler
handler = module.createHandler(appPath)
server.requestHandlers[module.EXTENSION] = handler
# add DataSource handlers if they exist
if hasattr(handler, 'getDataSourceHandlers'):
dsRestApiHandlers.update(handler.getDataSourceHandlers())
logger.info('Loaded ' + appName + ' serving ' + module.EXTENSION)
except Exception as e:
logger.exception('Loading ' + appName + ' failed')
# special microservice logic
if 'api.authenticate' in server.requestHandlers:
server.getUserCredentials = server.requestHandlers['api.authenticate'].getUserCredentials
else:
server.getUserCredentials = lambda headers: {'username': None, 'password': None, 'groups': set()}
if 'api.appagent' in server.requestHandlers:
server.requestHandlers['api.appagent'].setHandlers(dsRestApiHandlers)
try:
if ssl:
server.socket = ssl.wrap_socket(server.socket, keyfile=os.path.join(directory, 'localhost.key'), certfile=os.path.join(directory, 'localhost.crt'), server_side=True)
logger.info('Server started on ' + server.server_name + ':' + str(server.server_port) + ' serving directory: ' + os.getcwd())
server.serve_forever()
except:
pass
finally:
logger.info('Stopping server')
for handler in server.requestHandlers:
server.requestHandlers[handler].close()
logger.info("Handler closed: " + handler)
os.chdir(directory)
for link in createdLinks:
if os.path.exists(link):
os.unlink(link)
logger.info('GUI unlinked: ' + link)
os.chdir(originalDirectory)
logger.info("Server stopped")
if __name__ == '__main__':
directory = os.path.abspath(os.path.dirname(sys.argv[0]))
# Initialize logging facility
logConfigPath = os.path.join(directory, 'LogConfig.json')
if os.path.exists(logConfigPath):
with open(logConfigPath, 'r') as f:
logConfig = json.load(f)
logging.config.dictConfig(logConfig)
else:
logging.basicConfig()
# Logging facility initialization done
config = {}
with open(os.path.join(directory, 'config.json'), 'r') as f:
config = json.load(f)
server = ThreadedHTTPServer((config['address'], config['port']), MainHandler)
runAppAgent(server, directory, config['htdocs'], 'ssl' in config and config['ssl'])