def handle(handle_code: customer_code.Function, port: int = 5000): """ FDK entry point :param handle_code: customer's code :type handle_code: fdk.customer_code.Function :param port: TCP port to start an FDK at :type port: int :return: None """ host = "localhost" log.log("entering handle") log.log("Starting HTTP server on " "TCP socket: {0}:{1}".format(host, port)) loop = asyncio.get_event_loop() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(("localhost", port)) rtr = router.Router() rtr.add("/call", frozenset({"POST"}), event_handler.event_handle(handle_code)) srv = app.AsyncHTTPServer(name="fdk-tcp-debug", router=rtr) start_serving, server_forever = srv.run(sock=sock, loop=loop) start_serving() server_forever()
def SetResponseHeaders(self, headers, status_code): log.log("setting headers. gateway: {0}".format(self.__is_gateway())) if self.__is_gateway(): headers = hs.encap_headers(headers, status=status_code) for k, v in headers.items(): self.__response_headers[k.lower()] = v
def setup_unix_server(handle_func, loop=None): log.log("in setup_unix_server") app = web.Application(loop=loop) app.router.add_post('/{tail:.*}', handle(handle_func)) return app
async def handle_request(handler_code, format_def, **kwargs): """ Handles a function's request :param handler_code: customer's code :type handler_code: fdk.customer_code.Function :param format_def: function's format :type format_def: str :param kwargs: request-specific parameters :type kwargs: dict :return: function's response :rtype: fdk.response.Response """ log.log("in handle_request") ctx, body = context.context_from_format(format_def, **kwargs) log.log("context provisioned") try: response_data = await with_deadline(ctx, handler_code, body) log.log("function result obtained") if isinstance(response_data, response.Response): return response_data headers = ctx.GetResponseHeaders() log.log("response headers obtained") return response.Response(ctx, response_data=response_data, headers=headers, status_code=200) except (Exception, TimeoutError) as ex: log.log("exception appeared: {0}".format(ex)) traceback.print_exc(file=sys.stderr) return errors.DispatchException(ctx, 502, str(ex)).response()
def __init__(self, app_id, app_name, fn_id, fn_name, call_id, content_type="application/octet-stream", deadline=None, config=None, headers=None, request_url=None, method="POST", fn_format=None, tracing_context=None): """ Request context here to be a placeholder for request-specific attributes :param app_id: Fn App ID :type app_id: str :param app_name: Fn App name :type app_name: str :param fn_id: Fn App Fn ID :type fn_id: str :param fn_name: Fn name :type fn_name: str :param call_id: Fn call ID :type call_id: str :param content_type: request content type :type content_type: str :param deadline: request deadline :type deadline: str :param config: an app/fn config :type config: dict :param headers: request headers :type headers: dict :param request_url: request URL :type request_url: str :param method: request method :type method: str :param fn_format: function format :type fn_format: str :param tracing_context: tracing context :type tracing_context: TracingContext """ self.__app_id = app_id self.__fn_id = fn_id self.__call_id = call_id self.__config = config if config else {} self.__headers = headers if headers else {} self.__http_headers = {} self.__deadline = deadline self.__content_type = content_type self._request_url = request_url self._method = method self.__response_headers = {} self.__fn_format = fn_format self.__app_name = app_name self.__fn_name = fn_name self.__tracing_context = tracing_context if tracing_context else None log.log("request headers. gateway: {0} {1}" .format(self.__is_gateway(), headers)) if self.__is_gateway(): self.__headers = hs.decap_headers(headers, True) self.__http_headers = hs.decap_headers(headers, False)
def serialize_response_data(data, content_type): log.log("in serialize_response_data") if data: if "application/json" in content_type: return bytes(ujson.dumps(data), "utf8") if "text/plain" in content_type: return bytes(str(data), "utf8") if "application/xml" in content_type: # returns a bytearray if isinstance(data, str): return bytes(data, "utf8") return ElementTree.tostring(data, encoding='utf8', method='xml') if "application/octet-stream" in content_type: return data return
def handle(handle_func): log.log("entering handle") asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) loop = asyncio.get_event_loop() format_def = os.environ.get(constants.FN_FORMAT) lsnr = os.environ.get(constants.FN_LISTENER) log.log("format: {0}".format(format_def)) if format_def == constants.HTTPSTREAM: if lsnr is None: log.log("{0} is not set".format(constants.FN_LISTENER)) sys.exit(1) log.log("{0} is set, value: {1}".format(constants.FN_LISTENER, lsnr)) http_stream.start(handle_func, lsnr, loop=loop) else: log.log("incompatible function format!") print("incompatible function format!", file=sys.stderr, flush=True) sys.exit("incompatible function format!")
async def handle_request(handle_func, format_def, **kwargs): ctx, body = context.context_from_format(format_def, **kwargs) try: response_data = await with_deadline(ctx, handle_func, body) if isinstance(response_data, response.RawResponse): return response_data resp_class = response.response_class_from_context(ctx) return resp_class( ctx, response_data=response_data, headers={}, status_code=200) except (Exception, TimeoutError) as ex: log.log("exception appeared") traceback.print_exc(file=sys.stderr) status = 502 if isinstance(ex, TimeoutError) else 500 err_class = errors.error_class_from_format(format_def) return err_class(ctx, status, str(ex), ).response()
async def pure_handler(request): log.log("in pure_handler") data = None if request.has_body: log.log("has body: {}".format(request.has_body)) log.log("request comes with data") data = await request.content.read() response = await runner.handle_request( handle_func, constants.HTTPSTREAM, request=request, data=data) log.log("request execution completed") headers = response.context().GetResponseHeaders() response_content_type = headers.get( constants.CONTENT_TYPE, "application/json" ) headers.set(constants.CONTENT_TYPE, response_content_type) kwargs = { "headers": headers.http_raw() } sdata = serialize_response_data( response.body(), response_content_type) if response.status() >= 500: kwargs.update(reason=sdata, status=500) else: kwargs.update(body=sdata, status=200) log.log("sending response back") try: resp = web.Response(**kwargs) except (Exception, BaseException) as ex: resp = web.Response( text=str(ex), reason=str(ex), status=500, content_type="text/plain", headers={ "Fn-Http-Status": str(500) }) return resp
def handle(handle_code: customer_code.Function): """ FDK entry point :param handle_code: customer's code :type handle_code: fdk.customer_code.Function :return: None """ log.log("entering handle") if not isinstance(handle_code, customer_code.Function): sys.exit( '\n\n\nWARNING!\n\n' 'Your code is not compatible the the latest FDK!\n\n' 'Update Dockerfile entry point to:\n' 'ENTRYPOINT["/python/bin/fdk", "<path-to-your-func.py>", {0}]\n\n' 'if __name__ == "__main__":\n\tfdk.handle(handler)\n\n' 'syntax no longer supported!\n' 'Update your code as soon as possible!' '\n\n\n'.format(handle_code.__name__)) loop = asyncio.get_event_loop() format_def = os.environ.get(constants.FN_FORMAT) lsnr = os.environ.get(constants.FN_LISTENER) log.log("{0} is set, value: {1}".format(constants.FN_FORMAT, format_def)) if lsnr is None: sys.exit("{0} is not set".format(constants.FN_LISTENER)) log.log("{0} is set, value: {1}".format(constants.FN_LISTENER, lsnr)) if format_def == constants.HTTPSTREAM: start(handle_code, lsnr, loop=loop) else: sys.exit("incompatible function format!")
async def pure_handler(request): from fdk import runner log.log("in pure_handler") headers = dict(request.headers) log_frame_header(headers) func_response = await runner.handle_request(handle_code, constants.HTTPSTREAM, headers=headers, data=io.BytesIO( request.body)) log.log("request execution completed") headers = func_response.context().GetResponseHeaders() status = func_response.status() if status not in constants.FN_ENFORCED_RESPONSE_CODES: status = constants.FN_DEFAULT_RESPONSE_CODE return response.HTTPResponse( headers=headers, status=status, content_type=headers.get(constants.CONTENT_TYPE), body_bytes=func_response.body_bytes(), )
async def handle_request(handler_code, format_def, **kwargs): """ Handles a function's request :param handler_code: customer's code :type handler_code: fdk.customer_code.Function :param format_def: function's format :type format_def: str :param kwargs: request-specific parameters :type kwargs: dict :return: function's response :rtype: fdk.response.Response """ log.log("in handle_request") ctx, body = context.context_from_format(format_def, **kwargs) log.set_request_id(ctx.CallID()) log.log("context provisioned") try: response_data = await with_deadline(ctx, handler_code, body) log.log("function result obtained") if isinstance(response_data, response.Response): return response_data headers = ctx.GetResponseHeaders() log.log("response headers obtained") return response.Response(ctx, response_data=response_data, headers=headers, status_code=200) except (Exception, TimeoutError) as ex: log.log("exception appeared: {0}".format(ex)) (exctype, value, tb) = sys.exc_info() tb_flat = ''.join( s.replace('\n', '\\n') for s in traceback.format_tb(tb)) log.get_request_log().error('{}:{}'.format(value, tb_flat)) return errors.DispatchException(ctx, 502, str(ex)).response()
def start(handle_code: customer_code.Function, uds: str, loop: asyncio.AbstractEventLoop = None): """ Unix domain socket HTTP server entry point :param handle_code: customer's code :type handle_code: fdk.customer_code.Function :param uds: path to a Unix domain socket :type uds: str :param loop: event loop :type loop: asyncio.AbstractEventLoop :return: None """ log.log("in http_stream.start") socket_path = os.path.normpath(str(uds).lstrip("unix:")) socket_dir, socket_file = os.path.split(socket_path) if socket_file == "": sys.exit("malformed FN_LISTENER env var " "value: {0}".format(socket_path)) phony_socket_path = os.path.join(socket_dir, "phony" + socket_file) log.log("deleting socket files if they exist") try: os.remove(socket_path) os.remove(phony_socket_path) except OSError: pass sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.bind(phony_socket_path) rtr = router.Router() rtr.add("/call", frozenset({"POST"}), event_handler.event_handle(handle_code)) srv = app.AsyncHTTPServer(name="fdk", router=rtr) start_serving, server_forever = srv.run(sock=sock, loop=loop) try: log.log("CHMOD 666 {0}".format(phony_socket_path)) os.chmod(phony_socket_path, 0o666) log.log("phony socket permissions: {0}".format( oct(os.stat(phony_socket_path).st_mode))) log.log("calling '.start_serving()'") start_serving() log.log("sym-linking {0} to {1}".format(socket_path, phony_socket_path)) os.symlink(os.path.basename(phony_socket_path), socket_path) log.log("socket permissions: {0}".format( oct(os.stat(socket_path).st_mode))) log.log("starting infinite loop") except (Exception, BaseException) as ex: log.log(str(ex)) raise ex server_forever()
def start(handle_func, uds, loop=None): log.log("in http_stream.start") app = setup_unix_server(handle_func, loop=loop) socket_path = str(uds).lstrip("unix:") if asyncio.iscoroutine(app): app = loop.run_until_complete(app) log.log("socket file exist? - {0}" .format(os.path.exists(socket_path))) app_runner = web.AppRunner( app, handle_signals=True, access_log=log.get_logger()) # setting up app runner log.log("setting app_runner") loop.run_until_complete(app_runner.setup()) # try to remove pre-existing UDS: ignore errors here socket_dir, socket_file = os.path.split(socket_path) phony_socket_path = os.path.join( socket_dir, "phony" + socket_file) log.log("deleting socket files if they exist") try: os.remove(socket_path) os.remove(phony_socket_path) except (FileNotFoundError, Exception, BaseException): pass log.log("starting unix socket site") uds_sock = web.UnixSite( app_runner, phony_socket_path, shutdown_timeout=0.1) loop.run_until_complete(uds_sock.start()) try: try: log.log("CHMOD 666 {0}".format(phony_socket_path)) os.chmod(phony_socket_path, 0o666) log.log("phony socket permissions: {0}" .format(oct(os.stat(phony_socket_path).st_mode))) log.log("sym-linking {0} to {1}".format( socket_path, phony_socket_path)) os.symlink(os.path.basename(phony_socket_path), socket_path) log.log("socket permissions: {0}" .format(oct(os.stat(socket_path).st_mode))) except (Exception, BaseException) as ex: log.log(str(ex)) raise ex try: log.log("starting infinite loop") loop.run_forever() except web.GracefulExit: pass finally: loop.run_until_complete(app_runner.cleanup()) if hasattr(loop, 'shutdown_asyncgens'): loop.run_until_complete(loop.shutdown_asyncgens()) loop.close()