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 |
# 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 = read_with_charset input, content_type&.charset if input 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" content = read_with_charset input, content_type.charset return decode_structured_content content, content_type.subtype_format, **format_args when "cloudevents-batch" content = read_with_charset input, content_type.charset return decode_batched_content content, 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 216 217 218 |
# 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 if content content_type = "application/cloudevents-batch+#{format}; charset=#{charset_of content}" return [{ "Content-Type" => content_type }, content] end 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.
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/cloud_events/http_binding.rb', line 230 def encode_binary_content event headers = {} body = nil event.to_h.each do |key, value| case key when "data" body = value when "datacontenttype" headers["Content-Type"] = value else headers["CE-#{key}"] = percent_encode value end end case body when ::String headers["Content-Type"] ||= string_content_type body when 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
.
182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/cloud_events/http_binding.rb', line 182 def encode_structured_content event, format, **format_args handlers = @structured_formatters[format] || [] handlers.reverse_each do |handler| content = handler.encode event, **format_args if content content_type = "application/cloudevents+#{format}; charset=#{charset_of content}" return [{ "Content-Type" => content_type }, content] end end raise HttpContentError, "Unknown cloudevents format: #{format.inspect}" end |
#percent_decode(str) ⇒ String
Decode a percent-encoded string to a UTF-8 string.
262 263 264 265 266 |
# File 'lib/cloud_events/http_binding.rb', line 262 def percent_decode str str = str.gsub(/"((?:[^"\\]|\\.)*)"/) { ::Regexp.last_match(1).gsub(/\\(.)/, '\1') } 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.
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/cloud_events/http_binding.rb', line 277 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 != 34 && 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 |