functions_framework 0.4.1 → 0.7.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.
@@ -1,310 +0,0 @@
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 "functions_framework/cloud_events/json_format"
16
-
17
- module FunctionsFramework
18
- module CloudEvents
19
- ##
20
- # HTTP binding for CloudEvents.
21
- #
22
- # This class implements HTTP binding, including unmarshalling of events from
23
- # Rack environment data, and marshalling of events to Rack environment data.
24
- # It supports binary (i.e. header-based) HTTP content, as well as structured
25
- # (body-based) content that can delegate to formatters such as JSON.
26
- #
27
- # Supports the CloudEvents 0.3 and CloudEvents 1.0 variants of this format.
28
- # See https://github.com/cloudevents/spec/blob/v0.3/http-transport-binding.md
29
- # and https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md.
30
- #
31
- class HttpBinding
32
- ##
33
- # Returns a default binding, with JSON supported.
34
- #
35
- def self.default
36
- @default ||= begin
37
- http_binding = new
38
- json_format = JsonFormat.new
39
- http_binding.register_structured_formatter "json", json_format
40
- http_binding.register_batched_formatter "json", json_format
41
- http_binding
42
- end
43
- end
44
-
45
- ##
46
- # Create an empty HTTP binding.
47
- #
48
- def initialize
49
- @structured_formatters = {}
50
- @batched_formatters = {}
51
- end
52
-
53
- ##
54
- # Register a formatter for the given type.
55
- #
56
- # A formatter must respond to the methods `#encode` and `#decode`. See
57
- # {FunctionsFramework::CloudEvents::JsonFormat} for an example.
58
- #
59
- # @param type [String] The subtype format that should be handled by
60
- # this formatter.
61
- # @param formatter [Object] The formatter object.
62
- # @return [self]
63
- #
64
- def register_structured_formatter type, formatter
65
- formatters = @structured_formatters[type.to_s.strip.downcase] ||= []
66
- formatters << formatter unless formatters.include? formatter
67
- self
68
- end
69
-
70
- ##
71
- # Register a batch formatter for the given type.
72
- #
73
- # A batch formatter must respond to the methods `#encode_batch` and
74
- # `#decode_batch`. See {FunctionsFramework::CloudEvents::JsonFormat} for
75
- # an example.
76
- #
77
- # @param type [String] The subtype format that should be handled by
78
- # this formatter.
79
- # @param formatter [Object] The formatter object.
80
- # @return [self]
81
- #
82
- def register_batched_formatter type, formatter
83
- formatters = @batched_formatters[type.to_s.strip.downcase] ||= []
84
- formatters << formatter unless formatters.include? formatter
85
- self
86
- end
87
-
88
- ##
89
- # Decode an event from the given Rack environment hash. Following the
90
- # CloudEvents spec, this chooses a handler based on the Content-Type of
91
- # the request.
92
- #
93
- # @param env [Hash] The Rack environment.
94
- # @param format_args [keywords] Extra args to pass to the formatter.
95
- # @return [FunctionsFramework::CloudEvents::Event] if the request
96
- # includes a single structured or binary event.
97
- # @return [Array<FunctionsFramework::CloudEvents::Event>] if the request
98
- # includes a batch of structured events.
99
- # @return [nil] if the request was not recognized as a CloudEvent.
100
- #
101
- def decode_rack_env env, **format_args
102
- content_type_header = env["CONTENT_TYPE"]
103
- content_type = ContentType.new content_type_header if content_type_header
104
- input = env["rack.input"]
105
- if input && content_type&.media_type == "application"
106
- case content_type.subtype_base
107
- when "cloudevents"
108
- input.set_encoding content_type.charset if content_type.charset
109
- return decode_structured_content input.read, content_type.subtype_format, **format_args
110
- when "cloudevents-batch"
111
- input.set_encoding content_type.charset if content_type.charset
112
- return decode_batched_content input.read, content_type.subtype_format, **format_args
113
- end
114
- end
115
- decode_binary_content env, content_type
116
- end
117
-
118
- ##
119
- # Decode a single event from the given content data. This should be
120
- # passed the request body, if the Content-Type is of the form
121
- # `application/cloudevents+format`.
122
- #
123
- # @param input [String] The string content.
124
- # @param format [String] The format code (e.g. "json").
125
- # @param format_args [keywords] Extra args to pass to the formatter.
126
- # @return [FunctionsFramework::CloudEvents::Event]
127
- #
128
- def decode_structured_content input, format, **format_args
129
- handlers = @structured_formatters[format] || []
130
- handlers.reverse_each do |handler|
131
- event = handler.decode input, **format_args
132
- return event if event
133
- end
134
- raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
135
- end
136
-
137
- ##
138
- # Decode a batch of events from the given content data. This should be
139
- # passed the request body, if the Content-Type is of the form
140
- # `application/cloudevents-batch+format`.
141
- #
142
- # @param input [String] The string content.
143
- # @param format [String] The format code (e.g. "json").
144
- # @param format_args [keywords] Extra args to pass to the formatter.
145
- # @return [Array<FunctionsFramework::CloudEvents::Event>]
146
- #
147
- def decode_batched_content input, format, **format_args
148
- handlers = @batched_formatters[format] || []
149
- handlers.reverse_each do |handler|
150
- events = handler.decode_batch input, **format_args
151
- return events if events
152
- end
153
- raise HttpContentError, "Unknown cloudevents batch format: #{format.inspect}"
154
- end
155
-
156
- ##
157
- # Decode an event from the given Rack environment in binary content mode.
158
- #
159
- # @param env [Hash] Rack environment hash.
160
- # @param content_type [FunctionsFramework::CloudEvents::ContentType]
161
- # the content type from the Rack environment.
162
- # @return [FunctionsFramework::CloudEvents::Event] if a CloudEvent
163
- # could be decoded from the Rack environment.
164
- # @return [nil] if the Rack environment does not indicate a CloudEvent
165
- #
166
- def decode_binary_content env, content_type
167
- spec_version = env["HTTP_CE_SPECVERSION"]
168
- return nil if spec_version.nil?
169
- raise SpecVersionError, "Unrecognized specversion: #{spec_version}" unless spec_version == "1.0"
170
- input = env["rack.input"]
171
- data = if input
172
- input.set_encoding content_type.charset if content_type&.charset
173
- input.read
174
- end
175
- attributes = { "spec_version" => spec_version, "data" => data }
176
- attributes["data_content_type"] = content_type if content_type
177
- omit_names = ["specversion", "spec_version", "data", "datacontenttype", "data_content_type"]
178
- env.each do |key, value|
179
- match = /^HTTP_CE_(\w+)$/.match key
180
- next unless match
181
- attr_name = match[1].downcase
182
- attributes[attr_name] = percent_decode value unless omit_names.include? attr_name
183
- end
184
- Event.create spec_version: spec_version, attributes: attributes
185
- end
186
-
187
- ##
188
- # Encode a single event to content data in the given format.
189
- #
190
- # The result is a two-element array where the first element is a headers
191
- # list (as defined in the Rack specification) and the second is a string
192
- # containing the HTTP body content. The headers list will contain only
193
- # one header, a `Content-Type` whose value is of the form
194
- # `application/cloudevents+format`.
195
- #
196
- # @param event [FunctionsFramework::CloudEvents::Event] The event.
197
- # @param format [String] The format code (e.g. "json")
198
- # @param format_args [keywords] Extra args to pass to the formatter.
199
- # @return [Array(headers,String)]
200
- #
201
- def encode_structured_content event, format, **format_args
202
- handlers = @structured_formatters[format] || []
203
- handlers.reverse_each do |handler|
204
- content = handler.encode event, **format_args
205
- return [{ "Content-Type" => "application/cloudevents+#{format}" }, content] if content
206
- end
207
- raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
208
- end
209
-
210
- ##
211
- # Encode a batch of events to content data in the given format.
212
- #
213
- # The result is a two-element array where the first element is a headers
214
- # list (as defined in the Rack specification) and the second is a string
215
- # containing the HTTP body content. The headers list will contain only
216
- # one header, a `Content-Type` whose value is of the form
217
- # `application/cloudevents-batch+format`.
218
- #
219
- # @param events [Array<FunctionsFramework::CloudEvents::Event>] The batch
220
- # of events.
221
- # @param format [String] The format code (e.g. "json").
222
- # @param format_args [keywords] Extra args to pass to the formatter.
223
- # @return [Array(headers,String)]
224
- #
225
- def encode_batched_content events, format, **format_args
226
- handlers = @batched_formatters[format] || []
227
- handlers.reverse_each do |handler|
228
- content = handler.encode_batch events, **format_args
229
- return [{ "Content-Type" => "application/cloudevents-batch+#{format}" }, content] if content
230
- end
231
- raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
232
- end
233
-
234
- ##
235
- # Encode an event to content and headers, in binary content mode.
236
- #
237
- # The result is a two-element array where the first element is a headers
238
- # list (as defined in the Rack specification) and the second is a string
239
- # containing the HTTP body content.
240
- #
241
- # @param event [FunctionsFramework::CloudEvents::Event] The event.
242
- # @return [Array(headers,String)]
243
- #
244
- def encode_binary_content event
245
- headers = {}
246
- body = nil
247
- event.to_h.each do |key, value|
248
- if key == "data"
249
- body = value
250
- elsif key == "datacontenttype"
251
- headers["Content-Type"] = value
252
- else
253
- headers["CE-#{key}"] = percent_encode value
254
- end
255
- end
256
- if body.is_a? ::String
257
- headers["Content-Type"] ||= if body.encoding == ::Encoding.ASCII_8BIT
258
- "application/octet-stream"
259
- else
260
- "text/plain; charset=#{body.encoding.name.downcase}"
261
- end
262
- elsif body.nil?
263
- headers.delete "Content-Type"
264
- else
265
- body = ::JSON.dump body
266
- headers["Content-Type"] ||= "application/json; charset=#{body.encoding.name.downcase}"
267
- end
268
- [headers, body]
269
- end
270
-
271
- ##
272
- # Decode a percent-encoded string to a UTF-8 string.
273
- #
274
- # @param str [String] Incoming ascii string from an HTTP header, with one
275
- # cycle of percent-encoding.
276
- # @return [String] Resulting decoded string in UTF-8.
277
- #
278
- def percent_decode str
279
- decoded_str = str.gsub(/%[0-9a-fA-F]{2}/) { |m| [m[1..-1].to_i(16)].pack "C" }
280
- decoded_str.force_encoding ::Encoding::UTF_8
281
- end
282
-
283
- ##
284
- # Transcode an arbitrarily-encoded string to UTF-8, then percent-encode
285
- # non-printing and non-ascii characters to result in an ASCII string
286
- # suitable for setting as an HTTP header value.
287
- #
288
- # @param str [String] Incoming arbitrary string that can be represented
289
- # in UTF-8.
290
- # @return [String] Resulting encoded string in ASCII.
291
- #
292
- def percent_encode str
293
- arr = []
294
- utf_str = str.to_s.encode ::Encoding::UTF_8
295
- utf_str.each_byte do |byte|
296
- if byte >= 33 && byte <= 126 && byte != 37
297
- arr << byte
298
- else
299
- hi = byte / 16
300
- hi = hi > 9 ? 55 + hi : 48 + hi
301
- lo = byte % 16
302
- lo = lo > 9 ? 55 + lo : 48 + lo
303
- arr << 37 << hi << lo
304
- end
305
- end
306
- arr.pack "C*"
307
- end
308
- end
309
- end
310
- end
@@ -1,173 +0,0 @@
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
- # Supports the CloudEvents 0.3 and CloudEvents 1.0 variants of this format.
24
- # See https://github.com/cloudevents/spec/blob/v0.3/json-format.md and
25
- # https://github.com/cloudevents/spec/blob/v1.0/json-format.md.
26
- #
27
- class JsonFormat
28
- ##
29
- # Decode an event from the given input JSON string.
30
- #
31
- # @param json [String] A JSON-formatted string
32
- # @return [FunctionsFramework::CloudEvents::Event]
33
- #
34
- def decode json, **_other_kwargs
35
- structure = ::JSON.parse json
36
- decode_hash_structure structure
37
- end
38
-
39
- ##
40
- # Encode an event to a JSON string.
41
- #
42
- # @param event [FunctionsFramework::CloudEvents::Event] An input event.
43
- # @param sort [boolean] Whether to sort keys of the JSON output.
44
- # @return [String] The JSON representation.
45
- #
46
- def encode event, sort: false, **_other_kwargs
47
- structure = encode_hash_structure event
48
- structure = sort_keys structure if sort
49
- ::JSON.dump structure
50
- end
51
-
52
- ##
53
- # Decode a batch of events from the given input string.
54
- #
55
- # @param json [String] A JSON-formatted string
56
- # @return [Array<FunctionsFramework::CloudEvents::Event>]
57
- #
58
- def decode_batch json, **_other_kwargs
59
- structure_array = Array(::JSON.parse(json))
60
- structure_array.map do |structure|
61
- decode_hash_structure structure
62
- end
63
- end
64
-
65
- ##
66
- # Encode a batch of event to a JSON string.
67
- #
68
- # @param events [Array<FunctionsFramework::CloudEvents::Event>] An array
69
- # of input events.
70
- # @param sort [boolean] Whether to sort keys of the JSON output.
71
- # @return [String] The JSON representation.
72
- #
73
- def encode_batch events, sort: false, **_other_kwargs
74
- structure_array = Array(events).map do |event|
75
- structure = encode_hash_structure event
76
- sort ? sort_keys(structure) : structure
77
- end
78
- ::JSON.dump structure_array
79
- end
80
-
81
- ##
82
- # Decode a single event from a hash data structure with keys and types
83
- # conforming to the JSON envelope.
84
- #
85
- # @param structure [Hash] An input hash.
86
- # @return [FunctionsFramework::CloudEvents::Event]
87
- #
88
- def decode_hash_structure structure
89
- spec_version = structure["specversion"].to_s
90
- case spec_version
91
- when "0.3"
92
- decode_hash_structure_v0 structure
93
- when /^1(\.|$)/
94
- decode_hash_structure_v1 structure
95
- else
96
- raise SpecVersionError, "Unrecognized specversion: #{spec_version}"
97
- end
98
- end
99
-
100
- ##
101
- # Encode a single event to a hash data structure with keys and types
102
- # conforming to the JSON envelope.
103
- #
104
- # @param event [FunctionsFramework::CloudEvents::Event] An input event.
105
- # @return [String] The hash structure.
106
- #
107
- def encode_hash_structure event
108
- case event
109
- when Event::V0
110
- encode_hash_structure_v0 event
111
- when Event::V1
112
- encode_hash_structure_v1 event
113
- else
114
- raise SpecVersionError, "Unrecognized specversion: #{event.spec_version}"
115
- end
116
- end
117
-
118
- private
119
-
120
- def sort_keys hash
121
- result = {}
122
- hash.keys.sort.each do |key|
123
- result[key] = hash[key]
124
- end
125
- result
126
- end
127
-
128
- def decode_hash_structure_v0 structure
129
- data = structure["data"]
130
- content_type = structure["datacontenttype"]
131
- if data.is_a?(::String) && content_type.is_a?(::String)
132
- content_type = ContentType.new content_type
133
- if content_type.subtype == "json" || content_type.subtype_format == "json"
134
- structure = structure.dup
135
- structure["data"] = ::JSON.parse data rescue data
136
- structure["datacontenttype"] = content_type
137
- end
138
- end
139
- Event::V0.new attributes: structure
140
- end
141
-
142
- def decode_hash_structure_v1 structure
143
- if structure.key? "data_base64"
144
- structure = structure.dup
145
- structure["data"] = ::Base64.decode64 structure.delete "data_base64"
146
- end
147
- Event::V1.new attributes: structure
148
- end
149
-
150
- def encode_hash_structure_v0 event
151
- structure = event.to_h
152
- data = event.data
153
- content_type = event.data_content_type
154
- if data.is_a?(::String) && !content_type.nil?
155
- if content_type.subtype == "json" || content_type.subtype_format == "json"
156
- structure["data"] = ::JSON.parse data rescue data
157
- end
158
- end
159
- structure
160
- end
161
-
162
- def encode_hash_structure_v1 event
163
- structure = event.to_h
164
- data = structure["data"]
165
- if data.is_a?(::String) && data.encoding == ::Encoding::ASCII_8BIT
166
- structure.delete "data"
167
- structure["data_base64"] = ::Base64.encode64 data
168
- end
169
- structure
170
- end
171
- end
172
- end
173
- end