Skip to content

Commit 8057745

Browse files
committed
adjust http trigger
Signed-off-by: laminar <[email protected]>
1 parent 5dd0c09 commit 8057745

File tree

15 files changed

+435
-169
lines changed

15 files changed

+435
-169
lines changed

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@
5252
"grpcio==1.54.2",
5353
"flask>=1.0,<3.0",
5454
"click>=7.0,<9.0",
55-
# "watchdog>=1.0.0,<2.0.0",
55+
"uvicorn>=0.22.0",
5656
"gunicorn>=19.2.0,<21.0; platform_system!='Windows'",
5757
"cloudevents>=1.2.0,<2.0.0",
5858
"dapr>=1.10.0",
5959
"aiohttp==3.8.4",
60+
"dapr-ext-grpc>=1.10.0",
61+
"dapr-ext-fastapi>=1.10.0"
6062
],
6163
entry_points={
6264
"console_scripts": [

src/functions_framework/__init__.py

Lines changed: 0 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -11,126 +11,3 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
import io
15-
import json
16-
import logging
17-
import sys
18-
19-
import flask
20-
21-
from cloudevents.http import from_http
22-
23-
from functions_framework.exceptions import (
24-
EventConversionException,
25-
FunctionsFrameworkException,
26-
)
27-
from functions_framework.openfunction import dapr_output_middleware
28-
from functions_framework.runner import Runner
29-
30-
MAX_CONTENT_LENGTH = 10 * 1024 * 1024
31-
32-
_FUNCTION_STATUS_HEADER_FIELD = "X-Google-Status"
33-
_CRASH = "crash"
34-
35-
_CLOUDEVENT_MIME_TYPE = "application/cloudevents+json"
36-
37-
38-
class _LoggingHandler(io.TextIOWrapper):
39-
"""Logging replacement for stdout and stderr in GCF Python 3.7."""
40-
41-
def __init__(self, level, stderr=sys.stderr):
42-
io.TextIOWrapper.__init__(self, io.StringIO(), encoding=stderr.encoding)
43-
self.level = level
44-
self.stderr = stderr
45-
46-
def write(self, out):
47-
payload = dict(severity=self.level, message=out.rstrip("\n"))
48-
return self.stderr.write(json.dumps(payload) + "\n")
49-
50-
51-
def setup_logging():
52-
logging.getLogger().setLevel(logging.INFO)
53-
info_handler = logging.StreamHandler(sys.stdout)
54-
info_handler.setLevel(logging.NOTSET)
55-
info_handler.addFilter(lambda record: record.levelno <= logging.INFO)
56-
logging.getLogger().addHandler(info_handler)
57-
58-
warn_handler = logging.StreamHandler(sys.stderr)
59-
warn_handler.setLevel(logging.WARNING)
60-
logging.getLogger().addHandler(warn_handler)
61-
62-
63-
def setup_logging_level(debug):
64-
if debug:
65-
logging.getLogger().setLevel(logging.DEBUG)
66-
67-
68-
def _http_view_func_wrapper(function, request):
69-
def view_func(path):
70-
return function(request._get_current_object())
71-
72-
return view_func
73-
74-
75-
def _run_cloud_event(function, request):
76-
data = request.get_data()
77-
event = from_http(request.headers, data)
78-
function(event)
79-
80-
81-
82-
def read_request(response):
83-
"""
84-
Force the framework to read the entire request before responding, to avoid
85-
connection errors when returning prematurely.
86-
"""
87-
88-
flask.request.get_data()
89-
return response
90-
91-
92-
def crash_handler(e):
93-
"""
94-
Return crash header to allow logging 'crash' message in logs.
95-
"""
96-
return str(e), 500, {_FUNCTION_STATUS_HEADER_FIELD: _CRASH}
97-
98-
99-
class LazyWSGIApp:
100-
"""
101-
Wrap the WSGI app in a lazily initialized wrapper to prevent initialization
102-
at import-time
103-
"""
104-
105-
def __init__(self, target=None, source=None, signature_type=None, func_context=None, debug=False):
106-
# Support HTTP frameworks which support WSGI callables.
107-
# Note: this ability is currently broken in Gunicorn 20.0, and
108-
# environment variables should be used for configuration instead:
109-
# https://github.com/benoitc/gunicorn/issues/2159
110-
self.target = target
111-
self.source = source
112-
self.signature_type = signature_type
113-
self.func_context = func_context
114-
self.debug = debug
115-
116-
# Placeholder for the app which will be initialized on first call
117-
self.app = None
118-
119-
def __call__(self, *args, **kwargs):
120-
if not self.app:
121-
self.app = Runner(self.target, self.source, self.signature_type, self.func_context, self.debug)
122-
return self.app(*args, **kwargs)
123-
124-
125-
app = LazyWSGIApp()
126-
127-
128-
class DummyErrorHandler:
129-
def __init__(self):
130-
pass
131-
132-
def __call__(self, *args, **kwargs):
133-
return self
134-
135-
136-
errorhandler = DummyErrorHandler()

src/functions_framework/_cli.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,14 @@
2020
@click.command()
2121
@click.option("--target", envvar="FUNCTION_TARGET", type=click.STRING, required=True)
2222
@click.option("--source", envvar="FUNCTION_SOURCE", type=click.Path(), default=None)
23-
# @click.option(
24-
# "--signature-type",
25-
# envvar="FUNCTION_SIGNATURE_TYPE",
26-
# type=click.Choice(["http", "event", "cloudevent"]),
27-
# default="http",
28-
# )
2923
@click.option("--host", envvar="HOST", type=click.STRING, default="0.0.0.0")
3024
@click.option("--port", envvar="PORT", type=click.INT, default=8080)
3125
@click.option("--debug", envvar="DEBUG", is_flag=True)
3226
@click.option("--dry-run", envvar="DRY_RUN", is_flag=True)
3327
def _cli(target, source, host, port, debug, dry_run):
34-
context = _function_registry.get_openfunction_context(None)
35-
36-
# # determine if async or knative
37-
# if context and context.is_runtime_async():
38-
# app = create_async_app(target, source, context, debug)
39-
# if dry_run:
40-
# run_dry(target, host, port)
41-
# else:
42-
# app.run(context.port)
43-
# else:
44-
# app = create_app(target, source, signature_type, context, debug)
45-
# if dry_run:
46-
# run_dry(target, host, port)
47-
# else:
48-
# create_server(app, debug).run(host, port)
28+
# fetch the context
29+
context = _function_registry.get_openfunction_context('')
30+
4931
runner = Runner(context, target, source, host, port, debug, dry_run)
5032
runner.run()
5133

src/functions_framework/_function_registry.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def __function_signature_rule__(context: UserContext):
4545
pass
4646

4747

48-
FUNCTION_SIGNATURE_RULE = inspect.Signature(__function_signature_rule__())
48+
FUNCTION_SIGNATURE_RULE = inspect.signature(__function_signature_rule__)
4949

5050

5151
def get_user_function(source, source_module, target):
@@ -67,12 +67,12 @@ def get_user_function(source, source_module, target):
6767
)
6868
)
6969

70-
if FUNCTION_SIGNATURE_RULE != inspect.Signature(function):
70+
if FUNCTION_SIGNATURE_RULE != inspect.signature(function):
7171
raise InvalidFunctionSignatureException(
7272
"The function defined in file {source} as {target} needs to be of "
7373
"function signature {signature}, but got {target_signature}".format(
7474
source=source, target=target, signature=FUNCTION_SIGNATURE_RULE,
75-
target_signature=inspect.Signature(function))
75+
target_signature=inspect.signature(function))
7676
)
7777

7878
return function

src/functions_framework/context/function_context.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ def from_json(json_dct):
6363
for trigger in _dapr_triggers:
6464
dapr_triggers.append(DaprTrigger.from_json(trigger))
6565

66+
http_trigger = HTTPRoute.from_json(http_trigger)
67+
6668
return FunctionContext(name, version, dapr_triggers, http_trigger,
6769
inputs, outputs, states, pre_hooks, post_hooks, tracing)
6870

@@ -105,6 +107,29 @@ def from_json(json_dct):
105107
return Component(component_name, component_type, topic, metadata, operation)
106108

107109

110+
class HTTPRoute(object):
111+
"""HTTP route."""
112+
113+
def __init__(self, port="", hostname="", rules=None):
114+
self.port = port
115+
self.hostname = hostname
116+
self.rules = rules
117+
118+
def __str__(self):
119+
return "{port: %s, hostname: %s, rules: %s}" % (
120+
self.port,
121+
self.hostname,
122+
self.rules
123+
)
124+
125+
@staticmethod
126+
def from_json(json_dct):
127+
port = json_dct.get('port', '')
128+
hostnames = json_dct.get('route', {}).get('hostnames', '')
129+
rules = json_dct.get('route', {}).get('rules', [])
130+
return HTTPRoute(port, hostnames, rules)
131+
132+
108133
class DaprTrigger(object):
109134

110135
def __init__(self, name, component_type, topic):

src/functions_framework/context/runtime_context.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
from functions_framework.context.function_context import FunctionContext, Component
14+
from functions_framework.context.function_context import Component, FunctionContext
1515

1616

17-
class RuntimeContext(object):
17+
class RuntimeContext:
1818
"""Context for runtime."""
1919

20-
def __int__(self, context: FunctionContext = None, logger=None):
20+
def __init__(self, context: FunctionContext = None, logger=None):
2121
self.context = context
2222
self.logger = logger
2323

@@ -36,6 +36,13 @@ def get_dapr_triggers(self):
3636
else:
3737
return []
3838

39+
def get_http_trigger(self):
40+
"""Get http trigger."""
41+
if self.context:
42+
return self.context.http_trigger
43+
else:
44+
return None
45+
3946
def get_outputs(self) -> [Component]:
4047
if self.context and self.context.outputs:
4148
return self.context.outputs

src/functions_framework/context/user_context.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@
2525
class UserContext(object):
2626
"""Context for user."""
2727

28-
def __int__(self, runtime_context: RuntimeContext = None,
29-
binding_request=None, topic_event=None, logger=None):
28+
def __init__(self, runtime_context: RuntimeContext = None,
29+
binding_request=None, topic_event=None, http_request=None, logger=None):
3030
self.runtime_context = runtime_context
3131
self.logger = logger
3232
self.out = FunctionOut(0, None, "", {})
3333
self.dapr_client = None
3434
self.__binding_request = binding_request
3535
self.__topic_event = topic_event
36+
self.__http_request = http_request
3637
self.__init_dapr_client()
3738

3839
def __init_dapr_client(self):
@@ -49,6 +50,9 @@ def get_binding_request(self):
4950
def get_topic_event(self):
5051
return copy.deepcopy(self.__topic_event)
5152

53+
def get_http_request(self):
54+
return self.__http_request
55+
5256
@exception_handler
5357
def send(self, output_name, data):
5458
"""Send data to specify output component.

src/functions_framework/log.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,3 @@ def initialize_logger(name=None, level=logging.DEBUG):
4646

4747
# initialize logger
4848
logger = initialize_logger(__name__, logging.INFO)
49-
50-
# test logger
51-
logger.debug("This is a debug message")
52-
logger.info("This is an info message")
53-
logger.warning("This is a warning message")
54-
logger.error("This is an error message")

src/functions_framework/runner.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from functions_framework.context.runtime_context import RuntimeContext
2222
from functions_framework.exceptions import MissingSourceException
2323
from functions_framework.triggers.dapr_trigger.dapr import DaprTriggerHandler
24+
from functions_framework.triggers.http_trigger.http import HTTPTriggerHandler
2425

2526

2627
class Runner:
@@ -31,13 +32,13 @@ def __init__(self, context: FunctionContext, target=None, source=None,
3132
self.context = context
3233
self.user_function = None
3334
self.request = None
34-
self.app = App()
3535
self.host = host
3636
self.port = port
3737
self.debug = debug
3838
self.dry_run = dry_run
3939
self.logger = None
4040
self.load_user_function()
41+
self.init_logger()
4142

4243
def load_user_function(self):
4344
_target = _function_registry.get_function_target(self.target)
@@ -63,11 +64,14 @@ def init_logger(self):
6364

6465
def run(self):
6566
# convert to runtime context
66-
runtime_context = RuntimeContext().__int__(self.context, self.logger)
67+
runtime_context = RuntimeContext(self.context, self.logger)
68+
69+
_trigger = runtime_context.get_http_trigger()
70+
if _trigger:
71+
http_trigger = HTTPTriggerHandler(self.context.port, _trigger, self.source, self.target, self.user_function)
72+
http_trigger.start(runtime_context, logger=self.logger)
6773

6874
_triggers = runtime_context.get_dapr_triggers()
6975
if _triggers:
70-
dapr_trigger = DaprTriggerHandler(self.context.port, self.app, _triggers, self.user_function)
71-
dapr_trigger.start(runtime_context)
72-
73-
self.app.run(self.port)
76+
dapr_trigger = DaprTriggerHandler(self.context.port, _triggers, self.user_function)
77+
dapr_trigger.start(runtime_context, logger=self.logger)

src/functions_framework/triggers/dapr_trigger/dapr.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@
2525

2626

2727
class DaprTriggerHandler(TriggerHandler):
28-
def __init__(self, port, app: App = None, triggers: [DaprTrigger] = None, user_function=None):
28+
"""Handle dapr trigger."""
29+
def __init__(self, port, triggers: [DaprTrigger] = None, user_function=None):
2930
self.port = port
3031
self.triggers = triggers
31-
self.app = app
32+
self.app = App()
3233
self.user_function = user_function
3334

3435
def start(self, context: RuntimeContext, logger=None):
@@ -40,7 +41,7 @@ def start(self, context: RuntimeContext, logger=None):
4041
@self.app.binding(trigger.name)
4142
def binding_handler(request: BindingRequest):
4243
rt_ctx = deepcopy(context)
43-
user_ctx = UserContext().__int__(runtime_context=rt_ctx, binding_request=request, logger=logger)
44+
user_ctx = UserContext(runtime_context=rt_ctx, binding_request=request, logger=logger)
4445
logging.basicConfig(level=logging.DEBUG)
4546
logging.info('Received Message : ' + request.text())
4647
self.user_function(user_ctx)
@@ -49,7 +50,9 @@ def binding_handler(request: BindingRequest):
4950
@self.app.subscribe(pubsub_name=trigger.name, topic=trigger.topic)
5051
def topic_handler(event: v1.Event):
5152
rt_ctx = deepcopy(context)
52-
user_ctx = UserContext().__int__(runtime_context=rt_ctx, topic_event=event, logger=logger)
53+
user_ctx = UserContext(runtime_context=rt_ctx, topic_event=event, logger=logger)
5354
logging.basicConfig(level=logging.DEBUG)
5455
logging.info('Received Message : ' + event.data.__str__())
5556
self.user_function(user_ctx)
57+
58+
self.app.run(self.port)

0 commit comments

Comments
 (0)