class APIService: def __init__(self, flask_app, logger): self.app = flask_app self.log = logger self.api = Api(self.app) self.is_terminating = False self.func_properties = {} self.func_request_counts = {} self.api_prefix = getenv("API_PREFIX") self.tracer = None if not isinstance(self.log, AI4EAppInsights): self.tracer = self.log.tracer self.api_task_manager = TaskManager() signal.signal(signal.SIGINT, self.initialize_term) # Add health check endpoint self.app.add_url_rule( self.api_prefix + "/", view_func=self.health_check, methods=["GET"] ) print("Adding url rule: " + self.api_prefix + "/") # Add task endpoint self.api.add_resource( Task, self.api_prefix + "/task/<id>", resource_class_kwargs={"task_manager": self.api_task_manager}, ) print("Adding url rule: " + self.api_prefix + "/task/<int:taskId>") self.app.before_request(self.before_request) def health_check(self): print("Health check call successful.") return "Health check OK" def api_func( self, is_async, api_path, methods, request_processing_function, maximum_concurrent_requests, content_types=None, content_max_length=None, trace_name=None, *args, **kwargs ): def decorator_api_func(func): if not self.api_prefix + api_path in self.func_properties: self.func_properties[self.api_prefix + api_path] = { MAX_REQUESTS_KEY_NAME: maximum_concurrent_requests, CONTENT_TYPE_KEY_NAME: content_types, CONTENT_MAX_KEY_NAME: content_max_length, } self.func_request_counts[self.api_prefix + api_path] = 0 @wraps(func) def api(*args, **kwargs): internal_args = {"func": func, "api_path": api_path} if request_processing_function: return_values = request_processing_function(request) combined_kwargs = {**internal_args, **kwargs, **return_values} else: combined_kwargs = {**internal_args, **kwargs} if is_async: task_info = self.api_task_manager.AddTask(request) taskId = str(task_info["TaskId"]) combined_kwargs["taskId"] = taskId self.wrap_async_endpoint(trace_name, *args, **combined_kwargs) return "TaskId: " + taskId else: return self.wrap_sync_endpoint(trace_name, *args, **combined_kwargs) api.__name__ = "api_" + api_path.replace("/", "") print( "Adding url rule: " + self.api_prefix + api_path + ", " + api.__name__ ) self.app.add_url_rule( self.api_prefix + api_path, view_func=api, methods=methods, provide_automatic_options=True, ) return decorator_api_func def api_async_func( self, api_path, methods, request_processing_function=None, maximum_concurrent_requests=None, content_types=None, content_max_length=None, trace_name=None, *args, **kwargs ): is_async = True return self.api_func( is_async, api_path, methods, request_processing_function, maximum_concurrent_requests, content_types, content_max_length, trace_name, *args, **kwargs ) def api_sync_func( self, api_path, methods, request_processing_function=None, maximum_concurrent_requests=None, content_types=None, content_max_length=None, trace_name=None, *args, **kwargs ): is_async = False return self.api_func( is_async, api_path, methods, request_processing_function, maximum_concurrent_requests, content_types, content_max_length, trace_name, *args, **kwargs ) def initialize_term(self, signum, frame): print("Signal handler called with signal: " + signum) print( "SIGINT received, service is terminating and will no longer accept requests." ) self.is_terminating = True def before_request(self): # Don't accept a request if SIGTERM has been called on this instance. if self.is_terminating: print("Process is being terminated. Request has been denied.") abort(503, {"message": "Service is busy, please try again later."}) if request.path in self.func_properties: if ( self.func_properties[request.path][CONTENT_TYPE_KEY_NAME] and not request.content_type in self.func_properties[request.path][CONTENT_TYPE_KEY_NAME] ): print("Invalid content type. Request has been denied.") abort( 401, { "message": "Content-type must be " + str(self.func_properties[request.path][CONTENT_TYPE_KEY_NAME]) }, ) if ( self.func_properties[request.path][CONTENT_MAX_KEY_NAME] and request.content_length > self.func_properties[request.path][CONTENT_MAX_KEY_NAME] ): print("Request is too large. Request has been denied.") abort( 413, { "message": "Request content too large (" + str(request.content_length) + "). Must be smaller than: " + str(self.func_properties[request.path][CONTENT_MAX_KEY_NAME]) }, ) denied_request = 0 if ( self.func_request_counts[request.path] + 1 > self.func_properties[request.path][MAX_REQUESTS_KEY_NAME] ): print( "Current requests: " + str(self.func_request_counts[request.path] + 1) ) print( "Max requests: " + str(self.func_properties[request.path][MAX_REQUESTS_KEY_NAME]) ) denied_request = 1 print("Service is busy. Request has been denied.") abort(503, {"message": "Service is busy, please try again later."}) if disable_request_metric == "False": self.log.track_metric( APP_INSIGHTS_REQUESTS_KEY_NAME + request.path, denied_request ) def increment_requests(self, api_path): self.func_request_counts[self.api_prefix + api_path] += 1 def decrement_requests(self, api_path): self.func_request_counts[self.api_prefix + api_path] -= 1 def wrap_sync_endpoint(self, trace_name=None, *args, **kwargs): if self.tracer: if not trace_name: api_path = kwargs["api_path"] trace_name = api_path with self.tracer.span(name=trace_name) as span: return self._execute_func_with_counter(*args, **kwargs) else: return self._execute_func_with_counter(*args, **kwargs) def wrap_async_endpoint(self, trace_name=None, *args, **kwargs): if self.tracer: if not trace_name: api_path = kwargs["api_path"] trace_name = api_path with self.tracer.span(name=trace_name) as span: self._create_and_execute_thread(*args, **kwargs) else: self._create_and_execute_thread(*args, **kwargs) def _create_and_execute_thread(self, *args, **kwargs): kwargs["request"] = request thread = Thread( target=self._execute_func_with_counter, args=args, kwargs=kwargs ) thread.start() def _log_and_fail_exeception(self, **kwargs): exc_type, exc_value, exc_traceback = sys.exc_info() ex_str = traceback.format_exception(exc_type, exc_value, exc_traceback) print(ex_str) if "taskId" in kwargs: taskId = kwargs["taskId"] if taskId: self.log.log_exception(ex_str) self.api_task_manager.FailTask( taskId, "Task failed - please contact support or try again." ) else: self.log.log_exception(ex_str) else: self.log.log_exception(ex_str) def _execute_func_with_counter(self, *args, **kwargs): func = kwargs["func"] api_path = kwargs["api_path"] self.increment_requests(api_path) try: r = func(*args, **kwargs) return r except Exception as e: self._log_and_fail_exeception(e) abort(500) finally: self.decrement_requests(api_path)
class APIService(): def __init__(self, flask_app, logger): self.app = flask_app self.log = logger self.api = Api(self.app) self.appinsights = AppInsights(self.app) self.is_terminating = False self.func_properties = {} self.func_request_counts = {} self.api_prefix = getenv('API_PREFIX') self.api_task_manager = TaskManager() signal.signal(signal.SIGINT, self.initialize_term) # Add health check endpoint self.app.add_url_rule(self.api_prefix + '/', view_func=self.health_check, methods=['GET']) print("Adding url rule: " + self.api_prefix + '/') # Add task endpoint self.api.add_resource( Task, self.api_prefix + '/task/<int:id>', resource_class_kwargs={'task_manager': self.api_task_manager}) print("Adding url rule: " + self.api_prefix + '/task/<int:taskId>') self.app.before_request(self.before_request) def health_check(self): print("Health check call successful.") return 'Health check OK' def api_func(self, is_async, api_path, methods, request_processing_function, maximum_concurrent_requests, content_types=None, content_max_length=None, trace_name=None, *args, **kwargs): def decorator_api_func(func): if not api_path in self.func_properties: self.func_properties[api_path] = { MAX_REQUESTS_KEY_NAME: maximum_concurrent_requests, CONTENT_TYPE_KEY_NAME: content_types, CONTENT_MAX_KEY_NAME: content_max_length } self.func_request_counts[api_path] = 0 @wraps(func) def api(*args, **kwargs): internal_args = {"func": func, "api_path": api_path} if request_processing_function: return_values = request_processing_function(request) combined_kwargs = { **internal_args, **kwargs, **return_values } else: combined_kwargs = {**internal_args, **kwargs} if is_async: task_info = self.api_task_manager.AddTask(request) taskId = str(task_info['TaskId']) combined_kwargs["taskId"] = taskId self.wrap_async_endpoint(trace_name, *args, **combined_kwargs) return 'TaskId: ' + taskId else: return self.wrap_sync_endpoint(trace_name, *args, **combined_kwargs) api.__name__ = 'api_' + api_path.replace('/', '') print("Adding url rule: " + self.api_prefix + api_path + ", " + api.__name__) self.app.add_url_rule(self.api_prefix + api_path, view_func=api, methods=methods, provide_automatic_options=True) return decorator_api_func def api_async_func(self, api_path, methods, request_processing_function=None, maximum_concurrent_requests=None, content_types=None, content_max_length=None, trace_name=None, *args, **kwargs): is_async = True return self.api_func(is_async, api_path, methods, request_processing_function, maximum_concurrent_requests, content_types, content_max_length, trace_name, *args, **kwargs) def api_sync_func(self, api_path, methods, request_processing_function=None, maximum_concurrent_requests=None, content_types=None, content_max_length=None, trace_name=None, *args, **kwargs): is_async = False return self.api_func(is_async, api_path, methods, request_processing_function, maximum_concurrent_requests, content_types, content_max_length, trace_name, *args, **kwargs) def initialize_term(self, signum, frame): print('Signal handler called with signal: ' + signum) print( 'SIGINT received, service is terminating and will no longer accept requests.' ) self.is_terminating = True def before_request(self): # Don't accept a request if SIGTERM has been called on this instance. if (self.is_terminating): print('Process is being terminated. Request has been denied.') abort(503, {'message': 'Service is busy, please try again later.'}) if request.path in self.func_properties: if (self.func_request_counts[request.path] + 1 > self.func_properties[request.path][MAX_REQUESTS_KEY_NAME]): print('Service is busy. Request has been denied.') abort(503, {'message': 'Service is busy, please try again later.'}) if (self.func_properties[request.path][CONTENT_TYPE_KEY_NAME] and not request.content_type in self.func_properties[ request.path][CONTENT_TYPE_KEY_NAME]): print('Invalid content type. Request has been denied.') abort( 401, { 'message': 'Content-type must be ' + self.func_properties[ request.path][CONTENT_TYPE_KEY_NAME] }) if (self.func_properties[request.path][CONTENT_MAX_KEY_NAME] and request.content_length > self.func_properties[request.path][CONTENT_MAX_KEY_NAME]): print('Request is too large. Request has been denied.') abort( 413, { 'message': 'Request content too large (' + str(request.content_length) + "). Must be smaller than: " + str(self.func_properties[ request.path][CONTENT_MAX_KEY_NAME]) }) def increment_requests(self, api_path): self.func_request_counts[api_path] += 1 if (disable_request_metric == False): self.log.track_metric( APP_INSIGHTS_REQUESTS_KEY_NAME + self.api_prefix + api_path, self.func_request_counts[api_path]) def decrement_requests(self, api_path): self.func_request_counts[api_path] -= 1 if (disable_request_metric == False): self.log.track_metric( APP_INSIGHTS_REQUESTS_KEY_NAME + self.api_prefix + api_path, self.func_request_counts[api_path]) def wrap_sync_endpoint(self, trace_name=None, *args, **kwargs): if (trace_name): with tracer.span(name=trace_name) as span: return self._execute_func_with_counter(*args, **kwargs) else: return self._execute_func_with_counter(*args, **kwargs) def wrap_async_endpoint(self, trace_name=None, *args, **kwargs): if (trace_name): with tracer.span(name=trace_name) as span: self._create_and_execute_thread(*args, **kwargs) else: self._create_and_execute_thread(*args, **kwargs) def _create_and_execute_thread(self, *args, **kwargs): kwargs['request'] = request thread = Thread(target=self._execute_func_with_counter, args=args, kwargs=kwargs) thread.start() def _log_and_fail_exeception(self, **kwargs): if ('taskId' in kwargs): taskId = kwargs['taskId'] if taskId: self.log.log_exception(sys.exc_info()[0], taskId) self.api_task_manager.FailTask(taskId, 'Task failed - try again') else: self.log.log_exception(sys.exc_info()[0]) else: self.log.log_exception(sys.exc_info()[0]) def _execute_func_with_counter(self, *args, **kwargs): func = kwargs['func'] api_path = kwargs['api_path'] self.increment_requests(api_path) try: r = func(*args, **kwargs) return r except HTTPException as e: self._log_and_fail_exeception(**kwargs) return e except: print(sys.exc_info()[0]) self._log_and_fail_exeception(**kwargs) abort(500) finally: self.decrement_requests(api_path)
class APIService(): def __init__(self, flask_app, logger): self.app = flask_app self.log = logger self.api = Api(self.app) self.is_terminating = False self.func_properties = {} self.func_request_counts = {} self.api_prefix = getenv('API_PREFIX') self.tracer = None self.api_task_manager = TaskManager() signal.signal(signal.SIGINT, self.initialize_term) # Add health check endpoint self.app.add_url_rule(self.api_prefix + '/', view_func=self.health_check, methods=['GET']) print("Adding url rule: " + self.api_prefix + '/') # Add task endpoint self.api.add_resource( Task, self.api_prefix + '/task/<id>', resource_class_kwargs={'task_manager': self.api_task_manager}) print("Adding url rule: " + self.api_prefix + '/task/<int:taskId>') if getenv('APPINSIGHTS_INSTRUMENTATIONKEY', None): azure_exporter = AzureExporter( connection_string='InstrumentationKey=' + str(getenv('APPINSIGHTS_INSTRUMENTATIONKEY')), timeout=getenv('APPINSIGHTS_TIMEOUT', 30.0)) sampling_rate = getenv('TRACE_SAMPLING_RATE', None) if not sampling_rate: sampling_rate = 1.0 self.middleware = FlaskMiddleware( self.app, exporter=azure_exporter, sampler=ProbabilitySampler(rate=float(sampling_rate)), ) self.tracer = Tracer( exporter=AzureExporter( connection_string='InstrumentationKey=' + str(getenv('APPINSIGHTS_INSTRUMENTATIONKEY')), timeout=getenv('APPINSIGHTS_TIMEOUT', 30.0)), sampler=ProbabilitySampler(rate=float(sampling_rate)), ) self.app.before_request(self.before_request) def health_check(self): print("Health check call successful.") return 'Health check OK' def api_func(self, is_async, api_path, methods, request_processing_function, maximum_concurrent_requests, content_types=None, content_max_length=None, trace_name=None, *args, **kwargs): def decorator_api_func(func): if not self.api_prefix + api_path in self.func_properties: self.func_properties[self.api_prefix + api_path] = { MAX_REQUESTS_KEY_NAME: maximum_concurrent_requests, CONTENT_TYPE_KEY_NAME: content_types, CONTENT_MAX_KEY_NAME: content_max_length } self.func_request_counts[self.api_prefix + api_path] = 0 @wraps(func) def api(*args, **kwargs): internal_args = {"func": func, "api_path": api_path} if request_processing_function: return_values = request_processing_function(request) combined_kwargs = { **internal_args, **kwargs, **return_values } else: combined_kwargs = {**internal_args, **kwargs} if is_async: task_info = self.api_task_manager.AddTask(request) taskId = str(task_info['TaskId']) combined_kwargs["taskId"] = taskId self.wrap_async_endpoint(trace_name, *args, **combined_kwargs) return 'TaskId: ' + taskId else: return self.wrap_sync_endpoint(trace_name, *args, **combined_kwargs) api.__name__ = 'api_' + api_path.replace('/', '') print("Adding url rule: " + self.api_prefix + api_path + ", " + api.__name__) self.app.add_url_rule(self.api_prefix + api_path, view_func=api, methods=methods, provide_automatic_options=True) return decorator_api_func def api_async_func(self, api_path, methods, request_processing_function=None, maximum_concurrent_requests=None, content_types=None, content_max_length=None, trace_name=None, *args, **kwargs): is_async = True return self.api_func(is_async, api_path, methods, request_processing_function, maximum_concurrent_requests, content_types, content_max_length, trace_name, *args, **kwargs) def api_sync_func(self, api_path, methods, request_processing_function=None, maximum_concurrent_requests=None, content_types=None, content_max_length=None, trace_name=None, *args, **kwargs): is_async = False return self.api_func(is_async, api_path, methods, request_processing_function, maximum_concurrent_requests, content_types, content_max_length, trace_name, *args, **kwargs) def initialize_term(self, signum, frame): print('Signal handler called with signal: ' + signum) print( 'SIGINT received, service is terminating and will no longer accept requests.' ) self.is_terminating = True def before_request(self): # Don't accept a request if SIGTERM has been called on this instance. if (self.is_terminating): print('Process is being terminated. Request has been denied.') abort(503, {'message': 'Service is busy, please try again later.'}) if request.path in self.func_properties: if (self.func_properties[request.path][CONTENT_TYPE_KEY_NAME] and not request.content_type in self.func_properties[ request.path][CONTENT_TYPE_KEY_NAME]): print('Invalid content type. Request has been denied.') abort( 401, { 'message': 'Content-type must be ' + str(self.func_properties[ request.path][CONTENT_TYPE_KEY_NAME]) }) if (self.func_properties[request.path][CONTENT_MAX_KEY_NAME] and request.content_length > self.func_properties[request.path][CONTENT_MAX_KEY_NAME]): print('Request is too large. Request has been denied.') abort( 413, { 'message': 'Request content too large (' + str(request.content_length) + "). Must be smaller than: " + str(self.func_properties[ request.path][CONTENT_MAX_KEY_NAME]) }) denied_request = 0 if (self.func_request_counts[request.path] + 1 > self.func_properties[request.path][MAX_REQUESTS_KEY_NAME]): print('Current requests: ' + str(self.func_request_counts[request.path] + 1)) print('Max requests: ' + str(self.func_properties[request.path] [MAX_REQUESTS_KEY_NAME])) denied_request = 1 print('Service is busy. Request has been denied.') abort(503, {'message': 'Service is busy, please try again later.'}) if (disable_request_metric == 'False'): self.log.track_metric( APP_INSIGHTS_REQUESTS_KEY_NAME + request.path, denied_request) def increment_requests(self, api_path): self.func_request_counts[self.api_prefix + api_path] += 1 def decrement_requests(self, api_path): self.func_request_counts[self.api_prefix + api_path] -= 1 def wrap_sync_endpoint(self, trace_name=None, *args, **kwargs): if (self.tracer): if (not trace_name): api_path = kwargs['api_path'] trace_name = api_path with self.tracer.span(name=trace_name) as span: return self._execute_func_with_counter(*args, **kwargs) else: return self._execute_func_with_counter(*args, **kwargs) def wrap_async_endpoint(self, trace_name=None, *args, **kwargs): if (self.tracer): if (not trace_name): api_path = kwargs['api_path'] trace_name = api_path with self.tracer.span(name=trace_name) as span: self._create_and_execute_thread(*args, **kwargs) else: self._create_and_execute_thread(*args, **kwargs) def _create_and_execute_thread(self, *args, **kwargs): kwargs['request'] = request thread = Thread(target=self._execute_func_with_counter, args=args, kwargs=kwargs) thread.start() def _log_and_fail_exeception(self, **kwargs): exc_type, exc_value, exc_traceback = sys.exc_info() ex_str = traceback.format_exception(exc_type, exc_value, exc_traceback) print(ex_str) if ('taskId' in kwargs): taskId = kwargs['taskId'] if taskId: self.log.log_exception(ex_str) self.api_task_manager.FailTask( taskId, 'Task failed - please contact support or try') else: self.log.log_exception(ex_str) else: self.log.log_exception(ex_str) def _execute_func_with_counter(self, *args, **kwargs): func = kwargs['func'] api_path = kwargs['api_path'] self.increment_requests(api_path) try: r = func(*args, **kwargs) return r except e: self._log_and_fail_exeception(e) abort(500) finally: self.decrement_requests(api_path)