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.
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<CloudEvents::Event>
Decode a batch of events from the given content data.
-
#decode_binary_content(env, content_type) ⇒ CloudEvents::Event?
Decode an event from the given Rack environment in binary content mode.
-
#decode_rack_env(env, **format_args) ⇒ CloudEvents::Event, ...
Decode an event from the given Rack environment hash.
-
#decode_structured_content(input, format, **format_args) ⇒ 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.
33 34 35 36 |
# File 'lib/cloud_events/http_binding.rb', line 33 def initialize @structured_formatters = {} @batched_formatters = {} end |
Class Method Details
.default ⇒ Object
Returns a default binding, with JSON supported.
20 21 22 23 24 25 26 27 28 |
# File 'lib/cloud_events/http_binding.rb', line 20 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<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
.
131 132 133 134 135 136 137 138 |
# File 'lib/cloud_events/http_binding.rb', line 131 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) ⇒ CloudEvents::Event?
Decode an event from the given Rack environment in binary content mode.
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/cloud_events/http_binding.rb', line 150 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) ⇒ 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.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/cloud_events/http_binding.rb', line 85 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) ⇒ 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
.
112 113 114 115 116 117 118 119 |
# File 'lib/cloud_events/http_binding.rb', line 112 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
.
208 209 210 211 212 213 214 215 |
# File 'lib/cloud_events/http_binding.rb', line 208 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.
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/cloud_events/http_binding.rb', line 227 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
.
185 186 187 188 189 190 191 192 |
# File 'lib/cloud_events/http_binding.rb', line 185 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.
261 262 263 264 |
# File 'lib/cloud_events/http_binding.rb', line 261 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.
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/cloud_events/http_binding.rb', line 275 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.
66 67 68 69 70 |
# File 'lib/cloud_events/http_binding.rb', line 66 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.
49 50 51 52 53 |
# File 'lib/cloud_events/http_binding.rb', line 49 def register_structured_formatter type, formatter formatters = @structured_formatters[type.to_s.strip.downcase] ||= [] formatters << formatter unless formatters.include? formatter self end |