functions_framework 0.2.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,270 +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
- # See https://github.com/cloudevents/spec/blob/master/http-protocol-binding.md
28
- #
29
- class HttpBinding
30
- ##
31
- # Returns a default binding, with JSON supported.
32
- #
33
- def self.default
34
- @default ||= begin
35
- http_binding = new
36
- json_format = JsonFormat.new
37
- http_binding.register_structured_formatter "json", json_format
38
- http_binding.register_batched_formatter "json", json_format
39
- http_binding
40
- end
41
- end
42
-
43
- ##
44
- # Create an empty HTTP binding.
45
- #
46
- def initialize
47
- @structured_formatters = {}
48
- @batched_formatters = {}
49
- end
50
-
51
- ##
52
- # Register a formatter for the given type.
53
- #
54
- # A formatter must respond to the methods `#encode` and `#decode`. See
55
- # {FunctionsFramework::CloudEvents::JsonFormat} for an example.
56
- #
57
- # @param type [String] The subtype format that should be handled by
58
- # this formatter.
59
- # @param formatter [Object] The formatter object.
60
- # @return [self]
61
- #
62
- def register_structured_formatter type, formatter
63
- formatters = @structured_formatters[type.to_s.strip.downcase] ||= []
64
- formatters << formatter unless formatters.include? formatter
65
- self
66
- end
67
-
68
- ##
69
- # Register a batch formatter for the given type.
70
- #
71
- # A batch formatter must respond to the methods `#encode_batch` and
72
- # `#decode_batch`. See {FunctionsFramework::CloudEvents::JsonFormat} for
73
- # an example.
74
- #
75
- # @param type [String] The subtype format that should be handled by
76
- # this formatter.
77
- # @param formatter [Object] The formatter object.
78
- # @return [self]
79
- #
80
- def register_batched_formatter type, formatter
81
- formatters = @batched_formatters[type.to_s.strip.downcase] ||= []
82
- formatters << formatter unless formatters.include? formatter
83
- self
84
- end
85
-
86
- ##
87
- # Decode an event from the given Rack environment hash. Following the
88
- # CloudEvents spec, this chooses a handler based on the Content-Type of
89
- # the request.
90
- #
91
- # @param env [Hash] The Rack environment.
92
- # @param format_args [keywords] Extra args to pass to the formatter.
93
- # @return [FunctionsFramework::CloudEvents::Event] if the request
94
- # includes a single structured or binary event.
95
- # @return [Array<FunctionsFramework::CloudEvents::Event>] if the request
96
- # includes a batch of structured events.
97
- # @return [nil] if the request was not recognized as a CloudEvent.
98
- #
99
- def decode_rack_env env, **format_args
100
- content_type_header = env["CONTENT_TYPE"]
101
- content_type = ContentType.new content_type_header if content_type_header
102
- input = env["rack.input"]
103
- if input && content_type&.media_type == "application"
104
- case content_type.subtype_prefix
105
- when "cloudevents"
106
- input.set_encoding content_type.charset if content_type.charset
107
- return decode_structured_content input.read, content_type.subtype_format, **format_args
108
- when "cloudevents-batch"
109
- input.set_encoding content_type.charset if content_type.charset
110
- return decode_batched_content input.read, content_type.subtype_format, **format_args
111
- end
112
- end
113
- decode_binary_content env, content_type
114
- end
115
-
116
- ##
117
- # Decode a single event from the given content data. This should be
118
- # passed the request body, if the Content-Type is of the form
119
- # `application/cloudevents+format`.
120
- #
121
- # @param input [String] The string content.
122
- # @param format [String] The format code (e.g. "json").
123
- # @param format_args [keywords] Extra args to pass to the formatter.
124
- # @return [FunctionsFramework::CloudEvents::Event]
125
- #
126
- def decode_structured_content input, format, **format_args
127
- handlers = @structured_formatters[format] || []
128
- handlers.reverse_each do |handler|
129
- event = handler.decode input, **format_args
130
- return event if event
131
- end
132
- raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
133
- end
134
-
135
- ##
136
- # Decode a batch of events from the given content data. This should be
137
- # passed the request body, if the Content-Type is of the form
138
- # `application/cloudevents-batch+format`.
139
- #
140
- # @param input [String] The string content.
141
- # @param format [String] The format code (e.g. "json").
142
- # @param format_args [keywords] Extra args to pass to the formatter.
143
- # @return [Array<FunctionsFramework::CloudEvents::Event>]
144
- #
145
- def decode_batched_content input, format, **format_args
146
- handlers = @batched_formatters[format] || []
147
- handlers.reverse_each do |handler|
148
- events = handler.decode_batch input, **format_args
149
- return events if events
150
- end
151
- raise HttpContentError, "Unknown cloudevents batch format: #{format.inspect}"
152
- end
153
-
154
- ##
155
- # Decode an event from the given Rack environment in binary content mode.
156
- #
157
- # @param env [Hash] Rack environment hash.
158
- # @param content_type [FunctionsFramework::CloudEvents::ContentType]
159
- # the content type from the Rack environment.
160
- # @return [FunctionsFramework::CloudEvents::Event] if a CloudEvent
161
- # could be decoded from the Rack environment.
162
- # @return [nil] if the Rack environment does not indicate a CloudEvent
163
- #
164
- def decode_binary_content env, content_type
165
- spec_version = env["HTTP_CE_SPECVERSION"]
166
- return nil if spec_version.nil?
167
- raise SpecVersionError, "Unrecognized specversion: #{spec_version}" unless spec_version == "1.0"
168
- input = env["rack.input"]
169
- data = if input
170
- input.set_encoding content_type.charset if content_type&.charset
171
- input.read
172
- end
173
- attributes = { "spec_version" => spec_version, "data" => data }
174
- attributes["data_content_type"] = content_type if content_type
175
- omit_names = ["specversion", "spec_version", "data", "datacontenttype", "data_content_type"]
176
- env.each do |key, value|
177
- match = /^HTTP_CE_(\w+)$/.match key
178
- next unless match
179
- attr_name = match[1].downcase
180
- attributes[attr_name] = value unless omit_names.include? attr_name
181
- end
182
- Event.create spec_version: spec_version, attributes: attributes
183
- end
184
-
185
- ##
186
- # Encode a single event to content data in the given format.
187
- #
188
- # The result is a two-element array where the first element is a headers
189
- # list (as defined in the Rack specification) and the second is a string
190
- # containing the HTTP body content. The headers list will contain only
191
- # one header, a `Content-Type` whose value is of the form
192
- # `application/cloudevents+format`.
193
- #
194
- # @param event [FunctionsFramework::CloudEvents::Event] The event.
195
- # @param format [String] The format code (e.g. "json")
196
- # @param format_args [keywords] Extra args to pass to the formatter.
197
- # @return [Array(headers,String)]
198
- #
199
- def encode_structured_content event, format, **format_args
200
- handlers = @structured_formatters[format] || []
201
- handlers.reverse_each do |handler|
202
- content = handler.encode event, **format_args
203
- return [{ "Content-Type" => "application/cloudevents+#{format}" }, content] if content
204
- end
205
- raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
206
- end
207
-
208
- ##
209
- # Encode a batch of events to content data in the given format.
210
- #
211
- # The result is a two-element array where the first element is a headers
212
- # list (as defined in the Rack specification) and the second is a string
213
- # containing the HTTP body content. The headers list will contain only
214
- # one header, a `Content-Type` whose value is of the form
215
- # `application/cloudevents-batch+format`.
216
- #
217
- # @param events [Array<FunctionsFramework::CloudEvents::Event>] The batch
218
- # of events.
219
- # @param format [String] The format code (e.g. "json").
220
- # @param format_args [keywords] Extra args to pass to the formatter.
221
- # @return [Array(headers,String)]
222
- #
223
- def encode_batched_content events, format, **format_args
224
- handlers = @batched_formatters[format] || []
225
- handlers.reverse_each do |handler|
226
- content = handler.encode_batch events, **format_args
227
- return [{ "Content-Type" => "application/cloudevents-batch+#{format}" }, content] if content
228
- end
229
- raise HttpContentError, "Unknown cloudevents format: #{format.inspect}"
230
- end
231
-
232
- ##
233
- # Encode an event to content and headers, in binary content mode.
234
- #
235
- # The result is a two-element array where the first element is a headers
236
- # list (as defined in the Rack specification) and the second is a string
237
- # containing the HTTP body content.
238
- #
239
- # @param event [FunctionsFramework::CloudEvents::Event] The event.
240
- # @return [Array(headers,String)]
241
- #
242
- def encode_binary_content event
243
- headers = {}
244
- body = nil
245
- event.to_h.each do |key, value|
246
- if key == "data"
247
- body = value
248
- elsif key == "datacontenttype"
249
- headers["Content-Type"] = value
250
- else
251
- headers["CE-#{key}"] = value
252
- end
253
- end
254
- if body.is_a? ::String
255
- headers["Content-Type"] ||= if body.encoding == ::Encoding.ASCII_8BIT
256
- "application/octet-stream"
257
- else
258
- "text/plain; charset=#{body.encoding.name.downcase}"
259
- end
260
- elsif body.nil?
261
- headers.delete "Content-Type"
262
- else
263
- body = ::JSON.dump body
264
- headers["Content-Type"] ||= "application/json; charset=#{body.encoding.name.downcase}"
265
- end
266
- [headers, body]
267
- end
268
- end
269
- end
270
- end
@@ -1,122 +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
- # 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