Class: FunctionsFramework::CloudEvents::HttpBinding
- Inherits:
-
Object
- Object
- FunctionsFramework::CloudEvents::HttpBinding
- Defined in:
- lib/functions_framework/cloud_events/http_binding.rb
Overview
HTTP binding for CloudEvents.
This class implements HTTP binding, including unmarshalling of events from Rack environment data, and marshalling of events to Rack environment data. It supports binary (i.e. header-based) HTTP content, as well as structured (body-based) content that can delegate to formatters such as JSON.
Supports the CloudEvents 0.3 and CloudEvents 1.0 variants of this format. See https://github.com/cloudevents/spec/blob/v0.3/http-transport-binding.md and https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md.
Class Method Summary collapse
-
.default ⇒ Object
Returns a default binding, with JSON supported.
Instance Method Summary collapse
-
#decode_batched_content(input, format, **format_args) ⇒ Array<FunctionsFramework::CloudEvents::Event>
Decode a batch of events from the given content data.
-
#decode_binary_content(env, content_type) ⇒ FunctionsFramework::CloudEvents::Event?
Decode an event from the given Rack environment in binary content mode.
-
#decode_rack_env(env, **format_args) ⇒ FunctionsFramework::CloudEvents::Event, ...
Decode an event from the given Rack environment hash.
-
#decode_structured_content(input, format, **format_args) ⇒ FunctionsFramework::CloudEvents::Event
Decode a single event from the given content data.
-
#encode_batched_content(events, format, **format_args) ⇒ Array(headers,String)
Encode a batch of events to content data in the given format.
-
#encode_binary_content(event) ⇒ Array(headers,String)
Encode an event to content and headers, in binary content mode.
-
#encode_structured_content(event, format, **format_args) ⇒ Array(headers,String)
Encode a single event to content data in the given format.
-
#initialize ⇒ HttpBinding
constructor
Create an empty HTTP binding.
-
#percent_decode(str) ⇒ String
Decode a percent-encoded string to a UTF-8 string.
-
#percent_encode(str) ⇒ String
Transcode an arbitrarily-encoded string to UTF-8, then percent-encode non-printing and non-ascii characters to result in an ASCII string suitable for setting as an HTTP header value.
-
#register_batched_formatter(type, formatter) ⇒ self
Register a batch formatter for the given type.
-
#register_structured_formatter(type, formatter) ⇒ self
Register a formatter for the given type.
Constructor Details
#initialize ⇒ HttpBinding
Create an empty HTTP binding.
48 49 50 51 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 48 def initialize @structured_formatters = {} @batched_formatters = {} end |
Class Method Details
.default ⇒ Object
Returns a default binding, with JSON supported.
35 36 37 38 39 40 41 42 43 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 35 def self.default @default ||= begin http_binding = new json_format = JsonFormat.new http_binding.register_structured_formatter "json", json_format http_binding.register_batched_formatter "json", json_format http_binding end end |
Instance Method Details
#decode_batched_content(input, format, **format_args) ⇒ Array<FunctionsFramework::CloudEvents::Event>
Decode a batch of events from the given content data. This should be
passed the request body, if the Content-Type is of the form
application/cloudevents-batch+format
.
147 148 149 150 151 152 153 154 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 147 def decode_batched_content input, format, **format_args handlers = @batched_formatters[format] || [] handlers.reverse_each do |handler| events = handler.decode_batch input, **format_args return events if events end raise HttpContentError, "Unknown cloudevents batch format: #{format.inspect}" end |
#decode_binary_content(env, content_type) ⇒ FunctionsFramework::CloudEvents::Event?
Decode an event from the given Rack environment in binary content mode.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 166 def decode_binary_content env, content_type spec_version = env["HTTP_CE_SPECVERSION"] return nil if spec_version.nil? raise SpecVersionError, "Unrecognized specversion: #{spec_version}" unless spec_version == "1.0" input = env["rack.input"] data = if input input.set_encoding content_type.charset if content_type&.charset input.read end attributes = { "spec_version" => spec_version, "data" => data } attributes["data_content_type"] = content_type if content_type omit_names = ["specversion", "spec_version", "data", "datacontenttype", "data_content_type"] env.each do |key, value| match = /^HTTP_CE_(\w+)$/.match key next unless match attr_name = match[1].downcase attributes[attr_name] = percent_decode value unless omit_names.include? attr_name end Event.create spec_version: spec_version, attributes: attributes end |
#decode_rack_env(env, **format_args) ⇒ FunctionsFramework::CloudEvents::Event, ...
Decode an event from the given Rack environment hash. Following the CloudEvents spec, this chooses a handler based on the Content-Type of the request.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 101 def decode_rack_env env, **format_args content_type_header = env["CONTENT_TYPE"] content_type = ContentType.new content_type_header if content_type_header input = env["rack.input"] if input && content_type&.media_type == "application" case content_type.subtype_base when "cloudevents" input.set_encoding content_type.charset if content_type.charset return decode_structured_content input.read, content_type.subtype_format, **format_args when "cloudevents-batch" input.set_encoding content_type.charset if content_type.charset return decode_batched_content input.read, content_type.subtype_format, **format_args end end decode_binary_content env, content_type end |
#decode_structured_content(input, format, **format_args) ⇒ FunctionsFramework::CloudEvents::Event
Decode a single event from the given content data. This should be
passed the request body, if the Content-Type is of the form
application/cloudevents+format
.
128 129 130 131 132 133 134 135 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 128 def decode_structured_content input, format, **format_args handlers = @structured_formatters[format] || [] handlers.reverse_each do |handler| event = handler.decode input, **format_args return event if event end raise HttpContentError, "Unknown cloudevents format: #{format.inspect}" end |
#encode_batched_content(events, format, **format_args) ⇒ Array(headers,String)
Encode a batch of events to content data in the given format.
The result is a two-element array where the first element is a headers
list (as defined in the Rack specification) and the second is a string
containing the HTTP body content. The headers list will contain only
one header, a Content-Type
whose value is of the form
application/cloudevents-batch+format
.
225 226 227 228 229 230 231 232 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 225 def encode_batched_content events, format, **format_args handlers = @batched_formatters[format] || [] handlers.reverse_each do |handler| content = handler.encode_batch events, **format_args return [{ "Content-Type" => "application/cloudevents-batch+#{format}" }, content] if content end raise HttpContentError, "Unknown cloudevents format: #{format.inspect}" end |
#encode_binary_content(event) ⇒ Array(headers,String)
Encode an event to content and headers, in binary content mode.
The result is a two-element array where the first element is a headers list (as defined in the Rack specification) and the second is a string containing the HTTP body content.
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 244 def encode_binary_content event headers = {} body = nil event.to_h.each do |key, value| if key == "data" body = value elsif key == "datacontenttype" headers["Content-Type"] = value else headers["CE-#{key}"] = percent_encode value end end if body.is_a? ::String headers["Content-Type"] ||= if body.encoding == ::Encoding.ASCII_8BIT "application/octet-stream" else "text/plain; charset=#{body.encoding.name.downcase}" end elsif body.nil? headers.delete "Content-Type" else body = ::JSON.dump body headers["Content-Type"] ||= "application/json; charset=#{body.encoding.name.downcase}" end [headers, body] end |
#encode_structured_content(event, format, **format_args) ⇒ Array(headers,String)
Encode a single event to content data in the given format.
The result is a two-element array where the first element is a headers
list (as defined in the Rack specification) and the second is a string
containing the HTTP body content. The headers list will contain only
one header, a Content-Type
whose value is of the form
application/cloudevents+format
.
201 202 203 204 205 206 207 208 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 201 def encode_structured_content event, format, **format_args handlers = @structured_formatters[format] || [] handlers.reverse_each do |handler| content = handler.encode event, **format_args return [{ "Content-Type" => "application/cloudevents+#{format}" }, content] if content end raise HttpContentError, "Unknown cloudevents format: #{format.inspect}" end |
#percent_decode(str) ⇒ String
Decode a percent-encoded string to a UTF-8 string.
278 279 280 281 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 278 def percent_decode str decoded_str = str.gsub(/%[0-9a-fA-F]{2}/) { |m| [m[1..-1].to_i(16)].pack "C" } decoded_str.force_encoding ::Encoding::UTF_8 end |
#percent_encode(str) ⇒ String
Transcode an arbitrarily-encoded string to UTF-8, then percent-encode non-printing and non-ascii characters to result in an ASCII string suitable for setting as an HTTP header value.
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 292 def percent_encode str arr = [] utf_str = str.to_s.encode ::Encoding::UTF_8 utf_str.each_byte do |byte| if byte >= 33 && byte <= 126 && byte != 37 arr << byte else hi = byte / 16 hi = hi > 9 ? 55 + hi : 48 + hi lo = byte % 16 lo = lo > 9 ? 55 + lo : 48 + lo arr << 37 << hi << lo end end arr.pack "C*" end |
#register_batched_formatter(type, formatter) ⇒ self
Register a batch formatter for the given type.
A batch formatter must respond to the methods #encode_batch
and
#decode_batch
. See JsonFormat for
an example.
82 83 84 85 86 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 82 def register_batched_formatter type, formatter formatters = @batched_formatters[type.to_s.strip.downcase] ||= [] formatters << formatter unless formatters.include? formatter self end |
#register_structured_formatter(type, formatter) ⇒ self
Register a formatter for the given type.
A formatter must respond to the methods #encode
and #decode
. See
JsonFormat for an example.
64 65 66 67 68 |
# File 'lib/functions_framework/cloud_events/http_binding.rb', line 64 def register_structured_formatter type, formatter formatters = @structured_formatters[type.to_s.strip.downcase] ||= [] formatters << formatter unless formatters.include? formatter self end |