functions_framework 0.1.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|