| #// Copyright (c) 2000-2017 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: |
| module = importlib.import_module(appName) |
| # create the handler |
| handler = module.createHandler(appPath) |
| server.requestHandlers[module.EXTENSION] = handler |
| 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') |
| # 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']) |