functions_framework 0.1.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +6 -2
- data/CHANGELOG.md +44 -0
- data/README.md +56 -136
- data/bin/functions-framework-ruby +19 -0
- data/docs/deploying-functions.md +182 -0
- data/docs/overview.md +142 -0
- data/docs/running-a-functions-server.md +122 -0
- data/docs/testing-functions.md +169 -0
- data/docs/writing-functions.md +275 -0
- data/lib/functions_framework.rb +16 -50
- data/lib/functions_framework/cli.rb +71 -13
- data/lib/functions_framework/cloud_events.rb +12 -110
- data/lib/functions_framework/cloud_events/content_type.rb +107 -30
- data/lib/functions_framework/cloud_events/errors.rb +42 -0
- data/lib/functions_framework/cloud_events/event.rb +56 -249
- data/lib/functions_framework/cloud_events/event/field_interpreter.rb +150 -0
- data/lib/functions_framework/cloud_events/event/v0.rb +236 -0
- data/lib/functions_framework/cloud_events/event/v1.rb +223 -0
- data/lib/functions_framework/cloud_events/http_binding.rb +310 -0
- data/lib/functions_framework/cloud_events/json_format.rb +173 -0
- data/lib/functions_framework/function.rb +80 -26
- data/lib/functions_framework/legacy_event_converter.rb +145 -0
- data/lib/functions_framework/registry.rb +0 -39
- data/lib/functions_framework/server.rb +61 -51
- data/lib/functions_framework/testing.rb +64 -24
- data/lib/functions_framework/version.rb +1 -1
- metadata +16 -4
- data/lib/functions_framework/cloud_events/binary_content.rb +0 -59
- data/lib/functions_framework/cloud_events/json_structure.rb +0 -88
@@ -16,21 +16,59 @@ module FunctionsFramework
|
|
16
16
|
##
|
17
17
|
# Representation of a function.
|
18
18
|
#
|
19
|
-
# A function has a name, a type, and
|
19
|
+
# A function has a name, a type, and an implementation.
|
20
|
+
#
|
21
|
+
# The implementation in general is an object that responds to the `call`
|
22
|
+
# method. For a function of type `:http`, the `call` method takes a single
|
23
|
+
# `Rack::Request` argument and returns one of various HTTP response types.
|
24
|
+
# See {FunctionsFramework::Registry.add_http}. For a function of type
|
25
|
+
# `:cloud_event`, the `call` method takes a single
|
26
|
+
# {FunctionsFramework::CloudEvents::Event CloudEvent} argument, and does not
|
27
|
+
# return a value. See {FunctionsFramework::Registry.add_cloud_event}.
|
28
|
+
#
|
29
|
+
# If a callable object is provided directly, its `call` method is invoked for
|
30
|
+
# every function execution. Note that this means it may be called multiple
|
31
|
+
# times concurrently in separate threads.
|
32
|
+
#
|
33
|
+
# Alternately, the implementation may be provided as a class that should be
|
34
|
+
# instantiated to produce a callable object. If a class is provided, it should
|
35
|
+
# either subclass {FunctionsFramework::Function::CallBase} or respond to the
|
36
|
+
# same constructor interface, i.e. accepting arbitrary keyword arguments. A
|
37
|
+
# separate callable object will be instantiated from this class for every
|
38
|
+
# function invocation, so each instance will be used for only one invocation.
|
39
|
+
#
|
40
|
+
# Finally, an implementation can be provided as a block. If a block is
|
41
|
+
# provided, it will be recast as a `call` method in an anonymous subclass of
|
42
|
+
# {FunctionsFramework::Function::CallBase}. Thus, providing a block is really
|
43
|
+
# just syntactic sugar for providing a class. (This means, for example, that
|
44
|
+
# the `return` keyword will work within the block because it is treated as a
|
45
|
+
# method.)
|
20
46
|
#
|
21
47
|
class Function
|
22
48
|
##
|
23
49
|
# Create a new function definition.
|
24
50
|
#
|
25
51
|
# @param name [String] The function name
|
26
|
-
# @param type [Symbol] The type of function. Valid types are
|
27
|
-
# `:
|
28
|
-
# @param
|
52
|
+
# @param type [Symbol] The type of function. Valid types are `:http` and
|
53
|
+
# `:cloud_event`.
|
54
|
+
# @param callable [Class,#call] A callable object or class.
|
55
|
+
# @param block [Proc] The function code as a block.
|
29
56
|
#
|
30
|
-
def initialize name, type, &block
|
57
|
+
def initialize name, type, callable = nil, &block
|
31
58
|
@name = name
|
32
59
|
@type = type
|
33
|
-
@
|
60
|
+
@callable = @callable_class = nil
|
61
|
+
if callable.respond_to? :call
|
62
|
+
@callable = callable
|
63
|
+
elsif callable.is_a? ::Class
|
64
|
+
@callable_class = callable
|
65
|
+
elsif block_given?
|
66
|
+
@callable_class = ::Class.new CallBase do
|
67
|
+
define_method :call, &block
|
68
|
+
end
|
69
|
+
else
|
70
|
+
raise ::ArgumentError, "No callable given for function"
|
71
|
+
end
|
34
72
|
end
|
35
73
|
|
36
74
|
##
|
@@ -44,32 +82,48 @@ module FunctionsFramework
|
|
44
82
|
attr_reader :type
|
45
83
|
|
46
84
|
##
|
47
|
-
#
|
85
|
+
# Get a callable for performing a function invocation. This will either
|
86
|
+
# return the singleton callable object, or instantiate a new callable from
|
87
|
+
# the configured class.
|
48
88
|
#
|
49
|
-
|
89
|
+
# @param logger [::Logger] The logger for use by function executions. This
|
90
|
+
# may or may not be used by the callable.
|
91
|
+
# @return [#call]
|
92
|
+
#
|
93
|
+
def new_call logger: nil
|
94
|
+
return @callable unless @callable.nil?
|
95
|
+
logger ||= FunctionsFramework.logger
|
96
|
+
@callable_class.new logger: logger, function_name: name, function_type: type
|
97
|
+
end
|
50
98
|
|
51
99
|
##
|
52
|
-
#
|
53
|
-
# of function.
|
54
|
-
#
|
55
|
-
# * A `:http` type function takes a `Rack::Request` argument, and returns
|
56
|
-
# a Rack response type. See {FunctionsFramework::Registry.add_http}.
|
57
|
-
# * A `:event` or `:cloud_event` type function takes a
|
58
|
-
# {FunctionsFramework::CloudEvents::Event} argument, and does not
|
59
|
-
# return a value. See {FunctionsFramework::Registry.add_cloud_event}.
|
60
|
-
# Note that for an `:event` type function, the passed event argument is
|
61
|
-
# split into two arguments when passed to the underlying block.
|
100
|
+
# A base class for a callable object that provides calling context.
|
62
101
|
#
|
63
|
-
#
|
64
|
-
# @return [Object]
|
102
|
+
# An object of this class is `self` while a function block is running.
|
65
103
|
#
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
104
|
+
class CallBase
|
105
|
+
##
|
106
|
+
# Create a callable object with the given context.
|
107
|
+
#
|
108
|
+
# @param context [keywords] A set of context arguments. See {#context} for
|
109
|
+
# a list of keys that will generally be passed in. However,
|
110
|
+
# implementations should be prepared to accept any abritrary keys.
|
111
|
+
#
|
112
|
+
def initialize **context
|
113
|
+
@context = context
|
72
114
|
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# A keyed hash of context information. Common context keys include:
|
118
|
+
#
|
119
|
+
# * **:logger** (`Logger`) A logger for use by this function call.
|
120
|
+
# * **:function_name** (`String`) The name of the running function.
|
121
|
+
# * **:function_type** (`Symbol`) The type of the running function,
|
122
|
+
# either `:http` or `:cloud_event`.
|
123
|
+
#
|
124
|
+
# @return [Hash]
|
125
|
+
#
|
126
|
+
attr_reader :context
|
73
127
|
end
|
74
128
|
end
|
75
129
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# Copyright 2020 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "json"
|
16
|
+
|
17
|
+
module FunctionsFramework
|
18
|
+
##
|
19
|
+
# Converter from legacy GCF event formats to CloudEvents.
|
20
|
+
#
|
21
|
+
class LegacyEventConverter
|
22
|
+
##
|
23
|
+
# Decode an event from the given Rack environment hash.
|
24
|
+
#
|
25
|
+
# @param env [Hash] The Rack environment
|
26
|
+
# @return [FunctionsFramework::CloudEvents::Event] if the request could
|
27
|
+
# be converted
|
28
|
+
# @return [nil] if the event format was not recognized.
|
29
|
+
#
|
30
|
+
def decode_rack_env env
|
31
|
+
content_type = CloudEvents::ContentType.new env["CONTENT_TYPE"]
|
32
|
+
return nil unless content_type.media_type == "application" && content_type.subtype_base == "json"
|
33
|
+
input = read_input_json env["rack.input"], content_type.charset
|
34
|
+
return nil unless input
|
35
|
+
raw_context = input["context"] || input
|
36
|
+
context = normalized_context raw_context
|
37
|
+
return nil unless context
|
38
|
+
construct_cloud_event context, input["data"]
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def read_input_json input, charset
|
44
|
+
input = input.read if input.respond_to? :read
|
45
|
+
input = input.encode charset if charset
|
46
|
+
content = ::JSON.parse input
|
47
|
+
content = nil unless content.is_a? ::Hash
|
48
|
+
content
|
49
|
+
rescue ::JSON::ParserError
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def normalized_context raw_context
|
54
|
+
id = raw_context["eventId"]
|
55
|
+
return nil unless id
|
56
|
+
timestamp = raw_context["timestamp"]
|
57
|
+
return nil unless timestamp
|
58
|
+
type = raw_context["eventType"]
|
59
|
+
return nil unless type
|
60
|
+
service, resource = analyze_resource raw_context["resource"], type
|
61
|
+
return nil unless service && resource
|
62
|
+
{ id: id, timestamp: timestamp, type: type, service: service, resource: resource }
|
63
|
+
end
|
64
|
+
|
65
|
+
def analyze_resource raw_resource, type
|
66
|
+
case raw_resource
|
67
|
+
when ::Hash
|
68
|
+
[raw_resource["service"], raw_resource["name"]]
|
69
|
+
when ::String
|
70
|
+
[service_from_type(type), raw_resource]
|
71
|
+
else
|
72
|
+
[nil, nil]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def service_from_type type
|
77
|
+
LEGACY_TYPE_TO_SERVICE.each do |pattern, service|
|
78
|
+
return service if pattern =~ type
|
79
|
+
end
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def construct_cloud_event context, data
|
84
|
+
source, subject = convert_source context[:service], context[:resource]
|
85
|
+
type = LEGACY_TYPE_TO_CE_TYPE[context[:type]]
|
86
|
+
return nil unless type && source
|
87
|
+
ce_data = convert_data context[:service], data
|
88
|
+
CloudEvents::Event.new id: context[:id],
|
89
|
+
source: source,
|
90
|
+
type: type,
|
91
|
+
spec_version: "1.0",
|
92
|
+
data_content_type: "application/json",
|
93
|
+
data: ce_data,
|
94
|
+
subject: subject,
|
95
|
+
time: context[:timestamp]
|
96
|
+
end
|
97
|
+
|
98
|
+
def convert_source service, resource
|
99
|
+
if service == "storage.googleapis.com"
|
100
|
+
match = %r{^(projects/[^/]+/buckets/[^/]+)/([^#]+)(?:#.*)?$}.match resource
|
101
|
+
return [nil, nil] unless match
|
102
|
+
["//#{service}/#{match[1]}", match[2]]
|
103
|
+
else
|
104
|
+
["//#{service}/#{resource}", nil]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def convert_data service, data
|
109
|
+
if service == "pubsub.googleapis.com"
|
110
|
+
{ "message" => data, "subscription" => nil }
|
111
|
+
else
|
112
|
+
data
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
LEGACY_TYPE_TO_SERVICE = {
|
117
|
+
%r{^providers/cloud\.firestore/} => "firestore.googleapis.com",
|
118
|
+
%r{^providers/cloud\.pubsub/} => "pubsub.googleapis.com",
|
119
|
+
%r{^providers/cloud\.storage/} => "storage.googleapis.com",
|
120
|
+
%r{^providers/firebase\.auth/} => "firebase.googleapis.com",
|
121
|
+
%r{^providers/google\.firebase} => "firebase.googleapis.com"
|
122
|
+
}.freeze
|
123
|
+
|
124
|
+
LEGACY_TYPE_TO_CE_TYPE = {
|
125
|
+
"google.pubsub.topic.publish" => "google.cloud.pubsub.topic.v1.messagePublished",
|
126
|
+
"providers/cloud.pubsub/eventTypes/topic.publish" => "google.cloud.pubsub.topic.v1.messagePublished",
|
127
|
+
"google.storage.object.finalize" => "google.cloud.storage.object.v1.finalized",
|
128
|
+
"google.storage.object.delete" => "google.cloud.storage.object.v1.deleted",
|
129
|
+
"google.storage.object.archive" => "google.cloud.storage.object.v1.archived",
|
130
|
+
"google.storage.object.metadataUpdate" => "google.cloud.storage.object.v1.metadataUpdated",
|
131
|
+
"providers/cloud.firestore/eventTypes/document.write" => "google.cloud.firestore.document.v1.written",
|
132
|
+
"providers/cloud.firestore/eventTypes/document.create" => "google.cloud.firestore.document.v1.created",
|
133
|
+
"providers/cloud.firestore/eventTypes/document.update" => "google.cloud.firestore.document.v1.updated",
|
134
|
+
"providers/cloud.firestore/eventTypes/document.delete" => "google.cloud.firestore.document.v1.deleted",
|
135
|
+
"providers/firebase.auth/eventTypes/user.create" => "google.firebase.auth.user.v1.created",
|
136
|
+
"providers/firebase.auth/eventTypes/user.delete" => "google.firebase.auth.user.v1.deleted",
|
137
|
+
"providers/google.firebase.analytics/eventTypes/event.log" => "google.firebase.analytics.log.v1.written",
|
138
|
+
"providers/google.firebase.database/eventTypes/ref.create" => "google.firebase.database.document.v1.created",
|
139
|
+
"providers/google.firebase.database/eventTypes/ref.write" => "google.firebase.database.document.v1.written",
|
140
|
+
"providers/google.firebase.database/eventTypes/ref.update" => "google.firebase.database.document.v1.updated",
|
141
|
+
"providers/google.firebase.database/eventTypes/ref.delete" => "google.firebase.database.document.v1.deleted",
|
142
|
+
"providers/cloud.storage/eventTypes/object.change" => "google.cloud.storage.object.v1.finalized"
|
143
|
+
}.freeze
|
144
|
+
end
|
145
|
+
end
|
@@ -75,42 +75,6 @@ module FunctionsFramework
|
|
75
75
|
self
|
76
76
|
end
|
77
77
|
|
78
|
-
##
|
79
|
-
# Add a CloudEvent function to the registry.
|
80
|
-
#
|
81
|
-
# You must provide a name for the function, and a block that implemets the
|
82
|
-
# function. The block should take two arguments: the event _data_ and the
|
83
|
-
# event _context_. Any return value is ignored.
|
84
|
-
#
|
85
|
-
# The event data argument will be one of the following types:
|
86
|
-
# * A `String` (with encoding `ASCII-8BIT`) if the data is in the form of
|
87
|
-
# binary data. You may choose to perform additional interpretation of
|
88
|
-
# the binary data using information in the content type provided by the
|
89
|
-
# context argument.
|
90
|
-
# * Any data type that can be represented in JSON (i.e. `String`,
|
91
|
-
# `Integer`, `Array`, `Hash`, `true`, `false`, or `nil`) if the event
|
92
|
-
# came with a JSON payload. The content type may also be set in the
|
93
|
-
# context if the data is a String.
|
94
|
-
#
|
95
|
-
# The context argument will be of type {FunctionsFramework::CloudEvents::Event},
|
96
|
-
# and will contain CloudEvents context attributes such as `id` and `type`.
|
97
|
-
#
|
98
|
-
# See also {#add_cloud_event} which creates a function that takes a single
|
99
|
-
# argument of type {FunctionsFramework::CloudEvents::Event}.
|
100
|
-
#
|
101
|
-
# @param name [String] The function name
|
102
|
-
# @param block [Proc] The function code as a proc
|
103
|
-
# @return [self]
|
104
|
-
#
|
105
|
-
def add_event name, &block
|
106
|
-
name = name.to_s
|
107
|
-
synchronize do
|
108
|
-
raise ::ArgumentError, "Function already defined: #{name}" if @functions.key? name
|
109
|
-
@functions[name] = Function.new name, :event, &block
|
110
|
-
end
|
111
|
-
self
|
112
|
-
end
|
113
|
-
|
114
78
|
##
|
115
79
|
# Add a CloudEvent function to the registry.
|
116
80
|
#
|
@@ -118,9 +82,6 @@ module FunctionsFramework
|
|
118
82
|
# function. The block should take _one_ argument: the event object of type
|
119
83
|
# {FunctionsFramework::CloudEvents::Event}. Any return value is ignored.
|
120
84
|
#
|
121
|
-
# See also {#add_event} which creates a function that takes data and
|
122
|
-
# context as separate arguments.
|
123
|
-
#
|
124
85
|
# @param name [String] The function name
|
125
86
|
# @param block [Proc] The function code as a proc
|
126
87
|
# @return [self]
|
@@ -47,7 +47,7 @@ module FunctionsFramework
|
|
47
47
|
case function.type
|
48
48
|
when :http
|
49
49
|
HttpApp.new function, @config
|
50
|
-
when :
|
50
|
+
when :cloud_event
|
51
51
|
EventApp.new function, @config
|
52
52
|
else
|
53
53
|
raise "Unrecognized function type: #{function.type}"
|
@@ -212,7 +212,7 @@ module FunctionsFramework
|
|
212
212
|
# @param bind_addr [String,nil]
|
213
213
|
#
|
214
214
|
def bind_addr= bind_addr
|
215
|
-
@bind_addr = bind_addr || ::ENV["
|
215
|
+
@bind_addr = bind_addr || ::ENV["FUNCTION_BIND_ADDR"] || "0.0.0.0"
|
216
216
|
end
|
217
217
|
|
218
218
|
##
|
@@ -228,7 +228,7 @@ module FunctionsFramework
|
|
228
228
|
# @param min_threads [Integer,nil]
|
229
229
|
#
|
230
230
|
def min_threads= min_threads
|
231
|
-
@min_threads = (min_threads || ::ENV["
|
231
|
+
@min_threads = (min_threads || ::ENV["FUNCTION_MIN_THREADS"])&.to_i
|
232
232
|
end
|
233
233
|
|
234
234
|
##
|
@@ -236,7 +236,7 @@ module FunctionsFramework
|
|
236
236
|
# @param max_threads [Integer,nil]
|
237
237
|
#
|
238
238
|
def max_threads= max_threads
|
239
|
-
@max_threads = (max_threads || ::ENV["
|
239
|
+
@max_threads = (max_threads || ::ENV["FUNCTION_MAX_THREADS"])&.to_i
|
240
240
|
end
|
241
241
|
|
242
242
|
##
|
@@ -244,8 +244,12 @@ module FunctionsFramework
|
|
244
244
|
# @param show_error_details [Boolean,nil]
|
245
245
|
#
|
246
246
|
def show_error_details= show_error_details
|
247
|
-
|
248
|
-
|
247
|
+
@show_error_details =
|
248
|
+
if show_error_details.nil?
|
249
|
+
!::ENV["FUNCTION_DETAILED_ERRORS"].to_s.empty?
|
250
|
+
else
|
251
|
+
show_error_details ? true : false
|
252
|
+
end
|
249
253
|
end
|
250
254
|
|
251
255
|
##
|
@@ -315,15 +319,15 @@ module FunctionsFramework
|
|
315
319
|
|
316
320
|
## @private
|
317
321
|
class AppBase
|
318
|
-
|
322
|
+
EXCLUDED_PATHS = ["/favicon.ico", "/robots.txt"].freeze
|
319
323
|
|
320
324
|
def initialize config
|
321
325
|
@config = config
|
322
326
|
end
|
323
327
|
|
324
|
-
def
|
328
|
+
def excluded_path? env
|
325
329
|
path = env[::Rack::SCRIPT_NAME].to_s + env[::Rack::PATH_INFO].to_s
|
326
|
-
|
330
|
+
EXCLUDED_PATHS.include? path
|
327
331
|
end
|
328
332
|
|
329
333
|
def interpret_response response
|
@@ -335,15 +339,13 @@ module FunctionsFramework
|
|
335
339
|
when ::String
|
336
340
|
string_response response, "text/plain", 200
|
337
341
|
when ::Hash
|
338
|
-
|
339
|
-
|
342
|
+
string_response ::JSON.dump(response), "application/json", 200
|
343
|
+
when CloudEvents::CloudEventsError
|
344
|
+
cloud_events_error_response response
|
340
345
|
when ::StandardError
|
341
|
-
|
342
|
-
string_response error, "text/plain", 500
|
346
|
+
error_response "#{response.class}: #{response.message}\n#{response.backtrace}\n"
|
343
347
|
else
|
344
|
-
|
345
|
-
error = error_message e
|
346
|
-
string_response error, "text/plain", 500
|
348
|
+
error_response "Unexpected response type: #{response.class}"
|
347
349
|
end
|
348
350
|
end
|
349
351
|
|
@@ -359,20 +361,15 @@ module FunctionsFramework
|
|
359
361
|
[status, headers, [string]]
|
360
362
|
end
|
361
363
|
|
362
|
-
def
|
363
|
-
|
364
|
-
|
365
|
-
else
|
366
|
-
"Unexpected internal error"
|
367
|
-
end
|
364
|
+
def cloud_events_error_response error
|
365
|
+
@config.logger.warn error
|
366
|
+
string_response "#{error.class}: #{error.message}", "text/plain", 400
|
368
367
|
end
|
369
368
|
|
370
|
-
def
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
"Failed to decode CloudEvent"
|
375
|
-
end
|
369
|
+
def error_response message
|
370
|
+
@config.logger.error message
|
371
|
+
message = "Unexpected internal error" unless @config.show_error_details?
|
372
|
+
string_response message, "text/plain", 500
|
376
373
|
end
|
377
374
|
end
|
378
375
|
|
@@ -384,15 +381,15 @@ module FunctionsFramework
|
|
384
381
|
end
|
385
382
|
|
386
383
|
def call env
|
387
|
-
return notfound_response if
|
384
|
+
return notfound_response if excluded_path? env
|
388
385
|
response =
|
389
386
|
begin
|
390
|
-
logger = env["rack.logger"]
|
387
|
+
logger = env["rack.logger"] ||= @config.logger
|
391
388
|
request = ::Rack::Request.new env
|
392
389
|
logger.info "FunctionsFramework: Handling HTTP #{request.request_method} request"
|
393
|
-
@function.
|
390
|
+
calling_context = @function.new_call logger: logger
|
391
|
+
calling_context.call request
|
394
392
|
rescue ::StandardError => e
|
395
|
-
logger.warn e
|
396
393
|
e
|
397
394
|
end
|
398
395
|
interpret_response response
|
@@ -404,33 +401,46 @@ module FunctionsFramework
|
|
404
401
|
def initialize function, config
|
405
402
|
super config
|
406
403
|
@function = function
|
404
|
+
@cloud_events = CloudEvents::HttpBinding.default
|
405
|
+
@legacy_events = LegacyEventConverter.new
|
407
406
|
end
|
408
407
|
|
409
408
|
def call env
|
410
|
-
return notfound_response if
|
411
|
-
logger = env["rack.logger"]
|
412
|
-
event =
|
413
|
-
begin
|
414
|
-
CloudEvents.decode_rack_env env
|
415
|
-
rescue ::StandardError => e
|
416
|
-
e
|
417
|
-
end
|
409
|
+
return notfound_response if excluded_path? env
|
410
|
+
logger = env["rack.logger"] ||= @config.logger
|
411
|
+
event = decode_event env
|
418
412
|
response =
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
e
|
427
|
-
end
|
413
|
+
case event
|
414
|
+
when CloudEvents::Event
|
415
|
+
handle_cloud_event event, logger
|
416
|
+
when ::Array
|
417
|
+
CloudEvents::HttpContentError.new "Batched CloudEvents are not supported"
|
418
|
+
when CloudEvents::CloudEventsError
|
419
|
+
event
|
428
420
|
else
|
429
|
-
|
430
|
-
string_response usage_message(e), "text/plain", 400
|
421
|
+
raise "Unexpected event type: #{event.class}"
|
431
422
|
end
|
432
423
|
interpret_response response
|
433
424
|
end
|
425
|
+
|
426
|
+
private
|
427
|
+
|
428
|
+
def decode_event env
|
429
|
+
@cloud_events.decode_rack_env(env) ||
|
430
|
+
@legacy_events.decode_rack_env(env) ||
|
431
|
+
raise(CloudEvents::HttpContentError, "Unrecognized event format")
|
432
|
+
rescue CloudEvents::CloudEventsError => e
|
433
|
+
e
|
434
|
+
end
|
435
|
+
|
436
|
+
def handle_cloud_event event, logger
|
437
|
+
logger.info "FunctionsFramework: Handling CloudEvent"
|
438
|
+
calling_context = @function.new_call logger: logger
|
439
|
+
calling_context.call event
|
440
|
+
"ok"
|
441
|
+
rescue ::StandardError => e
|
442
|
+
e
|
443
|
+
end
|
434
444
|
end
|
435
445
|
end
|
436
446
|
end
|