functions_framework 0.1.0 → 0.3.1
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 +38 -0
- data/README.md +57 -137
- 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 +261 -0
- data/lib/functions_framework.rb +19 -42
- data/lib/functions_framework/cli.rb +71 -13
- data/lib/functions_framework/cloud_events.rb +9 -109
- data/lib/functions_framework/cloud_events/errors.rb +42 -0
- data/lib/functions_framework/cloud_events/event.rb +51 -249
- data/lib/functions_framework/cloud_events/event/v1.rb +363 -0
- data/lib/functions_framework/cloud_events/http_binding.rb +270 -0
- data/lib/functions_framework/cloud_events/json_format.rb +122 -0
- data/lib/functions_framework/function.rb +7 -11
- data/lib/functions_framework/legacy_event_converter.rb +145 -0
- data/lib/functions_framework/registry.rb +3 -27
- data/lib/functions_framework/server.rb +63 -42
- data/lib/functions_framework/testing.rb +60 -20
- data/lib/functions_framework/version.rb +1 -1
- metadata +16 -6
- data/lib/functions_framework/cloud_events/binary_content.rb +0 -59
- data/lib/functions_framework/cloud_events/json_structure.rb +0 -88
@@ -0,0 +1,122 @@
|
|
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 "base64"
|
16
|
+
require "json"
|
17
|
+
|
18
|
+
module FunctionsFramework
|
19
|
+
module CloudEvents
|
20
|
+
##
|
21
|
+
# An implementation of JSON format and JSON batch format.
|
22
|
+
#
|
23
|
+
# See https://github.com/cloudevents/spec/blob/master/json-format.md
|
24
|
+
#
|
25
|
+
class JsonFormat
|
26
|
+
##
|
27
|
+
# Decode an event from the given input JSON string.
|
28
|
+
#
|
29
|
+
# @param json [String] A JSON-formatted string
|
30
|
+
# @return [FunctionsFramework::CloudEvents::Event]
|
31
|
+
#
|
32
|
+
def decode json, **_other_kwargs
|
33
|
+
structure = ::JSON.parse json
|
34
|
+
decode_hash_structure structure
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Encode an event to a JSON string.
|
39
|
+
#
|
40
|
+
# @param event [FunctionsFramework::CloudEvents::Event] An input event.
|
41
|
+
# @param sort [boolean] Whether to sort keys of the JSON output.
|
42
|
+
# @return [String] The JSON representation.
|
43
|
+
#
|
44
|
+
def encode event, sort: false, **_other_kwargs
|
45
|
+
structure = encode_hash_structure event
|
46
|
+
structure = sort_keys structure if sort
|
47
|
+
::JSON.dump structure
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Decode a batch of events from the given input string.
|
52
|
+
#
|
53
|
+
# @param json [String] A JSON-formatted string
|
54
|
+
# @return [Array<FunctionsFramework::CloudEvents::Event>]
|
55
|
+
#
|
56
|
+
def decode_batch json, **_other_kwargs
|
57
|
+
structure_array = Array(::JSON.parse(json))
|
58
|
+
structure_array.map do |structure|
|
59
|
+
decode_hash_structure structure
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Encode a batch of event to a JSON string.
|
65
|
+
#
|
66
|
+
# @param events [Array<FunctionsFramework::CloudEvents::Event>] An array
|
67
|
+
# of input events.
|
68
|
+
# @param sort [boolean] Whether to sort keys of the JSON output.
|
69
|
+
# @return [String] The JSON representation.
|
70
|
+
#
|
71
|
+
def encode_batch events, sort: false, **_other_kwargs
|
72
|
+
structure_array = Array(events).map do |event|
|
73
|
+
structure = encode_hash_structure event
|
74
|
+
sort ? sort_keys(structure) : structure
|
75
|
+
end
|
76
|
+
::JSON.dump structure_array
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Decode a single event from a hash data structure with keys and types
|
81
|
+
# conforming to the JSON event format.
|
82
|
+
#
|
83
|
+
# @param structure [Hash] An input hash.
|
84
|
+
# @return [FunctionsFramework::CloudEvents::Event]
|
85
|
+
#
|
86
|
+
def decode_hash_structure structure
|
87
|
+
if structure.key? "data_base64"
|
88
|
+
structure = structure.dup
|
89
|
+
structure["data"] = ::Base64.decode64 structure.delete "data_base64"
|
90
|
+
end
|
91
|
+
Event.create spec_version: structure["specversion"], attributes: structure
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Encode a single event to a hash data structure with keys and types
|
96
|
+
# conforming to the JSON event format.
|
97
|
+
#
|
98
|
+
# @param event [FunctionsFramework::CloudEvents::Event] An input event.
|
99
|
+
# @return [String] The hash structure.
|
100
|
+
#
|
101
|
+
def encode_hash_structure event
|
102
|
+
structure = event.to_h
|
103
|
+
data = structure["data"]
|
104
|
+
if data.is_a?(::String) && data.encoding == ::Encoding::ASCII_8BIT
|
105
|
+
structure.delete "data"
|
106
|
+
structure["data_base64"] = ::Base64.encode64 data
|
107
|
+
end
|
108
|
+
structure
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def sort_keys hash
|
114
|
+
result = {}
|
115
|
+
hash.keys.sort.each do |key|
|
116
|
+
result[key] = hash[key]
|
117
|
+
end
|
118
|
+
result
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -30,7 +30,9 @@ module FunctionsFramework
|
|
30
30
|
def initialize name, type, &block
|
31
31
|
@name = name
|
32
32
|
@type = type
|
33
|
-
@
|
33
|
+
@execution_context_class = Class.new do
|
34
|
+
define_method :call, &block
|
35
|
+
end
|
34
36
|
end
|
35
37
|
|
36
38
|
##
|
@@ -43,32 +45,26 @@ module FunctionsFramework
|
|
43
45
|
#
|
44
46
|
attr_reader :type
|
45
47
|
|
46
|
-
##
|
47
|
-
# @return [Proc] The function code as a proc
|
48
|
-
#
|
49
|
-
attr_reader :block
|
50
|
-
|
51
48
|
##
|
52
49
|
# Call the function. You must pass an argument appropriate to the type
|
53
50
|
# of function.
|
54
51
|
#
|
55
52
|
# * A `:http` type function takes a `Rack::Request` argument, and returns
|
56
53
|
# a Rack response type. See {FunctionsFramework::Registry.add_http}.
|
57
|
-
# * A `:
|
54
|
+
# * A `:cloud_event` type function takes a
|
58
55
|
# {FunctionsFramework::CloudEvents::Event} argument, and does not
|
59
56
|
# 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.
|
62
57
|
#
|
63
58
|
# @param argument [Rack::Request,FunctionsFramework::CloudEvents::Event]
|
64
59
|
# @return [Object]
|
65
60
|
#
|
66
61
|
def call argument
|
62
|
+
execution_context = @execution_context_class.new
|
67
63
|
case type
|
68
64
|
when :event
|
69
|
-
|
65
|
+
execution_context.call argument.data, argument
|
70
66
|
else
|
71
|
-
|
67
|
+
execution_context.call argument
|
72
68
|
end
|
73
69
|
end
|
74
70
|
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_prefix == "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
|
@@ -76,31 +76,10 @@ module FunctionsFramework
|
|
76
76
|
end
|
77
77
|
|
78
78
|
##
|
79
|
-
#
|
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`.
|
79
|
+
# This is an obsolete interface that defines an event function taking two
|
80
|
+
# arguments (data and context) rather than one.
|
97
81
|
#
|
98
|
-
#
|
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]
|
82
|
+
# @deprecated Use {Registry#add_cloud_event} instead.
|
104
83
|
#
|
105
84
|
def add_event name, &block
|
106
85
|
name = name.to_s
|
@@ -118,9 +97,6 @@ module FunctionsFramework
|
|
118
97
|
# function. The block should take _one_ argument: the event object of type
|
119
98
|
# {FunctionsFramework::CloudEvents::Event}. Any return value is ignored.
|
120
99
|
#
|
121
|
-
# See also {#add_event} which creates a function that takes data and
|
122
|
-
# context as separate arguments.
|
123
|
-
#
|
124
100
|
# @param name [String] The function name
|
125
101
|
# @param block [Proc] The function code as a proc
|
126
102
|
# @return [self]
|
@@ -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,10 +319,17 @@ module FunctionsFramework
|
|
315
319
|
|
316
320
|
## @private
|
317
321
|
class AppBase
|
322
|
+
EXCLUDED_PATHS = ["/favicon.ico", "/robots.txt"].freeze
|
323
|
+
|
318
324
|
def initialize config
|
319
325
|
@config = config
|
320
326
|
end
|
321
327
|
|
328
|
+
def excluded_path? env
|
329
|
+
path = env[::Rack::SCRIPT_NAME].to_s + env[::Rack::PATH_INFO].to_s
|
330
|
+
EXCLUDED_PATHS.include? path
|
331
|
+
end
|
332
|
+
|
322
333
|
def interpret_response response
|
323
334
|
case response
|
324
335
|
when ::Array
|
@@ -328,18 +339,20 @@ module FunctionsFramework
|
|
328
339
|
when ::String
|
329
340
|
string_response response, "text/plain", 200
|
330
341
|
when ::Hash
|
331
|
-
|
332
|
-
|
342
|
+
string_response ::JSON.dump(response), "application/json", 200
|
343
|
+
when CloudEvents::CloudEventsError
|
344
|
+
cloud_events_error_response response
|
333
345
|
when ::StandardError
|
334
|
-
|
335
|
-
string_response error, "text/plain", 500
|
346
|
+
error_response "#{response.class}: #{response.message}\n#{response.backtrace}\n"
|
336
347
|
else
|
337
|
-
|
338
|
-
error = error_message e
|
339
|
-
string_response error, "text/plain", 500
|
348
|
+
error_response "Unexpected response type: #{response.class}"
|
340
349
|
end
|
341
350
|
end
|
342
351
|
|
352
|
+
def notfound_response
|
353
|
+
string_response "Not found", "text/plain", 404
|
354
|
+
end
|
355
|
+
|
343
356
|
def string_response string, content_type, status
|
344
357
|
headers = {
|
345
358
|
"Content-Type" => content_type,
|
@@ -348,20 +361,15 @@ module FunctionsFramework
|
|
348
361
|
[status, headers, [string]]
|
349
362
|
end
|
350
363
|
|
351
|
-
def
|
352
|
-
|
353
|
-
|
354
|
-
else
|
355
|
-
"Unexpected internal error"
|
356
|
-
end
|
364
|
+
def cloud_events_error_response error
|
365
|
+
@config.logger.warn error
|
366
|
+
string_response "#{error.class}: #{error.message}", "text/plain", 400
|
357
367
|
end
|
358
368
|
|
359
|
-
def
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
"Failed to decode CloudEvent"
|
364
|
-
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
|
365
373
|
end
|
366
374
|
end
|
367
375
|
|
@@ -373,6 +381,7 @@ module FunctionsFramework
|
|
373
381
|
end
|
374
382
|
|
375
383
|
def call env
|
384
|
+
return notfound_response if excluded_path? env
|
376
385
|
response =
|
377
386
|
begin
|
378
387
|
logger = env["rack.logger"] = @config.logger
|
@@ -380,7 +389,6 @@ module FunctionsFramework
|
|
380
389
|
logger.info "FunctionsFramework: Handling HTTP #{request.request_method} request"
|
381
390
|
@function.call request
|
382
391
|
rescue ::StandardError => e
|
383
|
-
logger.warn e
|
384
392
|
e
|
385
393
|
end
|
386
394
|
interpret_response response
|
@@ -392,32 +400,45 @@ module FunctionsFramework
|
|
392
400
|
def initialize function, config
|
393
401
|
super config
|
394
402
|
@function = function
|
403
|
+
@cloud_events = CloudEvents::HttpBinding.default
|
404
|
+
@legacy_events = LegacyEventConverter.new
|
395
405
|
end
|
396
406
|
|
397
407
|
def call env
|
408
|
+
return notfound_response if excluded_path? env
|
398
409
|
logger = env["rack.logger"] = @config.logger
|
399
|
-
event =
|
400
|
-
begin
|
401
|
-
CloudEvents.decode_rack_env env
|
402
|
-
rescue ::StandardError => e
|
403
|
-
e
|
404
|
-
end
|
410
|
+
event = decode_event env
|
405
411
|
response =
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
e
|
414
|
-
end
|
412
|
+
case event
|
413
|
+
when CloudEvents::Event
|
414
|
+
handle_cloud_event event, logger
|
415
|
+
when ::Array
|
416
|
+
CloudEvents::HttpContentError.new "Batched CloudEvents are not supported"
|
417
|
+
when CloudEvents::CloudEventsError
|
418
|
+
event
|
415
419
|
else
|
416
|
-
|
417
|
-
string_response usage_message(e), "text/plain", 400
|
420
|
+
raise "Unexpected event type: #{event.class}"
|
418
421
|
end
|
419
422
|
interpret_response response
|
420
423
|
end
|
424
|
+
|
425
|
+
private
|
426
|
+
|
427
|
+
def decode_event env
|
428
|
+
@cloud_events.decode_rack_env(env) ||
|
429
|
+
@legacy_events.decode_rack_env(env) ||
|
430
|
+
raise(CloudEvents::HttpContentError, "Unrecognized event format")
|
431
|
+
rescue CloudEvents::CloudEventsError => e
|
432
|
+
e
|
433
|
+
end
|
434
|
+
|
435
|
+
def handle_cloud_event event, logger
|
436
|
+
logger.info "FunctionsFramework: Handling CloudEvent"
|
437
|
+
@function.call event
|
438
|
+
"ok"
|
439
|
+
rescue ::StandardError => e
|
440
|
+
e
|
441
|
+
end
|
421
442
|
end
|
422
443
|
end
|
423
444
|
end
|