functions_framework 0.4.1 → 0.7.0

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