Class: CloudEvents::HttpBinding
- Inherits:
-
Object
- Object
- CloudEvents::HttpBinding
- Defined in:
- lib/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.
Constant Summary collapse
- JSON_FORMAT =
The name of the JSON decoder/encoder
"json"
Instance Attribute Summary collapse
-
#default_encoder_name ⇒ String?
The name of the encoder to use if none is specified.
Class Method Summary collapse
-
.default ⇒ HttpBinding
Returns a default HTTP binding, including support for JSON format.
Instance Method Summary collapse
-
#decode_event(env, allow_opaque: false, **format_args) ⇒ CloudEvents::Event+
Decode an event from the given Rack environment hash.
-
#decode_rack_env(env, **format_args) ⇒ CloudEvents::Event, ...
deprecated
Deprecated.
Will be removed in version 1.0. Use #decode_event instead.
-
#encode_batched_content(event_batch, format_name, **format_args) ⇒ Array(headers,String)
deprecated
Deprecated.
Will be removed in version 1.0. Use #encode_event instead.
-
#encode_binary_content(event, legacy_data_encode: true, **format_args) ⇒ Array(headers,String)
deprecated
Deprecated.
Will be removed in version 1.0. Use #encode_event instead.
-
#encode_event(event, structured_format: false, **format_args) ⇒ Array(headers,String)
Encode an event or batch of events into HTTP headers and body.
-
#encode_structured_content(event, format_name, **format_args) ⇒ Array(headers,String)
deprecated
Deprecated.
Will be removed in version 1.0. Use #encode_event instead.
-
#initialize ⇒ HttpBinding
constructor
Create an empty HTTP binding.
-
#probable_event?(env) ⇒ boolean
Analyze a Rack environment hash and determine whether it is probably a CloudEvent.
-
#register_formatter(formatter, deprecated_name = nil, encoder_name: nil) ⇒ self
Register a formatter for all operations it supports, based on which methods are implemented by the formatter object.
-
#register_formatter_methods(formatter, decode_event: false, encode_event: nil, decode_data: false, encode_data: false) ⇒ self
Registers the given formatter for the given operations.
Constructor Details
#initialize ⇒ HttpBinding
Create an empty HTTP binding.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/cloud_events/http_binding.rb', line 40 def initialize @event_decoders = Format::Multi.new do |result| result&.key?(:event) || result&.key?(:event_batch) ? result : nil end @event_encoders = {} @data_decoders = Format::Multi.new do |result| result&.key?(:data) && result&.key?(:content_type) ? result : nil end @data_encoders = Format::Multi.new do |result| result&.key?(:content) && result&.key?(:content_type) ? result : nil end text_format = TextFormat.new @data_decoders.formats.replace [text_format, DefaultDataFormat] @data_encoders.formats.replace [text_format, DefaultDataFormat] @default_encoder_name = nil end |
Instance Attribute Details
#default_encoder_name ⇒ String?
The name of the encoder to use if none is specified
124 125 126 |
# File 'lib/cloud_events/http_binding.rb', line 124 def default_encoder_name @default_encoder_name end |
Class Method Details
.default ⇒ HttpBinding
Returns a default HTTP binding, including support for JSON format.
28 29 30 31 32 33 34 35 |
# File 'lib/cloud_events/http_binding.rb', line 28 def self.default @default ||= begin http_binding = new http_binding.register_formatter JsonFormat.new, encoder_name: JSON_FORMAT http_binding.default_encoder_name = JSON_FORMAT http_binding end end |
Instance Method Details
#decode_event(env, allow_opaque: false, **format_args) ⇒ 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.
Note that this method will read the body (i.e. rack.input
) stream.
If you need to access the body after calling this method, you will need
to rewind the stream. To determine whether the request is a CloudEvent
without reading the body, use #probable_event?.
165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/cloud_events/http_binding.rb', line 165 def decode_event env, allow_opaque: false, **format_args content_type_string = env["CONTENT_TYPE"] content_type = ContentType.new content_type_string if content_type_string content = read_with_charset env["rack.input"], content_type&.charset result = decode_binary_content(content, content_type, env, false, **format_args) || decode_structured_content(content, content_type, allow_opaque, **format_args) if result.nil? content_type_string = content_type_string ? content_type_string.inspect : "not present" raise NotCloudEventError, "Content-Type is #{content_type_string}, and CE-SpecVersion is not present" end result end |
#decode_rack_env(env, **format_args) ⇒ CloudEvents::Event, ...
Will be removed in version 1.0. Use #decode_event instead.
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.
240 241 242 243 244 245 246 247 |
# File 'lib/cloud_events/http_binding.rb', line 240 def decode_rack_env env, **format_args content_type_string = env["CONTENT_TYPE"] content_type = ContentType.new content_type_string if content_type_string content = read_with_charset env["rack.input"], content_type&.charset env["rack.input"].rewind rescue nil decode_binary_content(content, content_type, env, true, **format_args) || decode_structured_content(content, content_type, false, **format_args) end |
#encode_batched_content(event_batch, format_name, **format_args) ⇒ Array(headers,String)
Will be removed in version 1.0. Use #encode_event instead.
Encode a batch of events in structured content mode in the given format.
277 278 279 280 281 282 283 |
# File 'lib/cloud_events/http_binding.rb', line 277 def encode_batched_content event_batch, format_name, **format_args result = @event_encoders[format_name]&.encode_event event_batch: event_batch, data_encoder: @data_encoders, **format_args return [{ "Content-Type" => result[:content_type].to_s }, result[:content]] if result raise ::ArgumentError, "Unknown format name: #{format_name.inspect}" end |
#encode_binary_content(event, legacy_data_encode: true, **format_args) ⇒ Array(headers,String)
Will be removed in version 1.0. Use #encode_event instead.
Encode an event in binary content mode.
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'lib/cloud_events/http_binding.rb', line 294 def encode_binary_content event, legacy_data_encode: true, **format_args headers = {} event.to_h.each do |key, value| unless ["data", "data_encoded", "datacontenttype"].include? key headers["CE-#{key}"] = percent_encode value end end body, content_type = if legacy_data_encode || event.spec_version.start_with?("0.") legacy_extract_event_data event else normal_extract_event_data event, format_args end headers["Content-Type"] = content_type.to_s if content_type [headers, body] end |
#encode_event(event, structured_format: false, **format_args) ⇒ Array(headers,String)
Encode an event or batch of events into HTTP headers and body.
You may provide an event, an array of events, or an opaque event. You may also specify what content mode and format to use.
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. When using structured content mode, the
headers list will contain only a Content-Type
header and the body will
contain the serialized event. When using binary mode, the header list
will contain the serialized event attributes and the body will contain
the serialized event data.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/cloud_events/http_binding.rb', line 201 def encode_event event, structured_format: false, **format_args if event.is_a? Event::Opaque [{ "Content-Type" => event.content_type.to_s }, event.content] elsif !structured_format if event.is_a? ::Array raise ::ArgumentError, "Encoding a batch requires structured_format" end encode_binary_content event, legacy_data_encode: false, **format_args else structured_format = default_encoder_name if structured_format == true raise ::ArgumentError, "Format name not specified, and no default is set" unless structured_format case event when ::Array encode_batched_content event, structured_format, **format_args when Event encode_structured_content event, structured_format, **format_args else raise ::ArgumentError, "Unknown event type: #{event.class}" end end end |
#encode_structured_content(event, format_name, **format_args) ⇒ Array(headers,String)
Will be removed in version 1.0. Use #encode_event instead.
Encode a single event in structured content mode in the given format.
259 260 261 262 263 264 265 |
# File 'lib/cloud_events/http_binding.rb', line 259 def encode_structured_content event, format_name, **format_args result = @event_encoders[format_name]&.encode_event event: event, data_encoder: @data_encoders, **format_args return [{ "Content-Type" => result[:content_type].to_s }, result[:content]] if result raise ::ArgumentError, "Unknown format name: #{format_name.inspect}" end |
#probable_event?(env) ⇒ boolean
Analyze a Rack environment hash and determine whether it is probably a CloudEvent. This is done by examining headers only, and does not read or parse the request body. The result is a best guess: false negatives or false positives are possible for edge cases, but the logic should generally detect canonically-formatted events.
136 137 138 139 140 141 |
# File 'lib/cloud_events/http_binding.rb', line 136 def probable_event? env return true if env["HTTP_CE_SPECVERSION"] content_type = ContentType.new env["CONTENT_TYPE"].to_s content_type.media_type == "application" && ["cloudevents", "cloudevents-batch"].include?(content_type.subtype_base) end |
#register_formatter(formatter, deprecated_name = nil, encoder_name: nil) ⇒ self
Register a formatter for all operations it supports, based on which methods are implemented by the formatter object. See Format for a list of possible methods.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/cloud_events/http_binding.rb', line 71 def register_formatter formatter, deprecated_name = nil, encoder_name: nil encoder_name ||= deprecated_name encoder_name = encoder_name.to_s.strip.downcase if encoder_name decode_event = formatter.respond_to? :decode_event encode_event = encoder_name if formatter.respond_to? :encode_event decode_data = formatter.respond_to? :decode_data encode_data = formatter.respond_to? :encode_data register_formatter_methods formatter, decode_event: decode_event, encode_event: encode_event, decode_data: decode_data, encode_data: encode_data self end |
#register_formatter_methods(formatter, decode_event: false, encode_event: nil, decode_data: false, encode_data: false) ⇒ self
Registers the given formatter for the given operations. Some arguments
are activated by passing true
, whereas those that rely on a format name
are activated by passing in a name string.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/cloud_events/http_binding.rb', line 102 def register_formatter_methods formatter, decode_event: false, encode_event: nil, decode_data: false, encode_data: false @event_decoders.formats.unshift formatter if decode_event if encode_event encoders = @event_encoders[encode_event] ||= Format::Multi.new do |result| result&.key?(:content) && result&.key?(:content_type) ? result : nil end encoders.formats.unshift formatter end @data_decoders.formats.unshift formatter if decode_data @data_encoders.formats.unshift formatter if encode_data self end |