From b095317e4c4fafa7e2bb27bf4a1f290470b8b62c Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Tue, 5 Apr 2016 16:52:44 +0900 Subject: [PATCH] Add an option `event_headers_style` to PostAgent It is defaulted to `capitalized`, normalizing emitted headers by capitalizing the names so downstream agents don't need to bother with the letter cases. Previously, the style of header names in an emitted event would vary depending on the server and the backend HTTP library. Most notably, the `net_http` backend downcases header names whereas other backends do not. For backward compatibility, preexisting PostAgents are automatically configured to have `event_headers_style` set to `raw` via migration. --- app/models/agents/post_agent.rb | 42 ++++++++++++++++++- ...72512_post_agent_set_event_header_style.rb | 11 +++++ spec/models/agents/post_agent_spec.rb | 22 +++++++++- 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20160405072512_post_agent_set_event_header_style.rb diff --git a/app/models/agents/post_agent.rb b/app/models/agents/post_agent.rb index d59dea5a..082d8538 100644 --- a/app/models/agents/post_agent.rb +++ b/app/models/agents/post_agent.rb @@ -25,6 +25,13 @@ module Agents If `emit_events` is set to `true`, the server response will be emitted as an Event and can be fed to a WebsiteAgent for parsing (using its `data_from_event` and `type` options). No data processing will be attempted by this Agent, so the Event's "body" value will always be raw text. + The Event will also have a "headers" hash and a "status" integer value. + Set `event_headers_style` to one of the following values to normalize the keys of "headers" for downstream agents' convenience: + + * `capitalized` (default) - Header names are capitalized; e.g. "Content-Type" + * `downcased` - Header names are downcased; e.g. "content-type" + * `snakecased` - Header names are snakecased; e.g. "content_type" + * `raw` - Backward compatibility option to leave them unmodified from what the underlying HTTP library returns. Other Options: @@ -92,6 +99,12 @@ module Agents errors.add(:base, "if provided, emit_events must be true or false") end + begin + normalize_response_headers({}) + rescue ArgumentError => e + errors.add(:base, e.message) + end + unless %w[post get put delete patch].include?(method) errors.add(:base, "method must be 'post', 'get', 'put', 'delete', or 'patch'") end @@ -124,6 +137,29 @@ module Agents private + def normalize_response_headers(headers) + case interpolated['event_headers_style'] + when nil, '', 'capitalized' + normalize = ->name { + name.gsub(/(?:\A|(?<=-))([[:alpha:]])|([[:alpha:]]+)/) { + $1 ? $1.upcase : $2.downcase + } + } + when 'downcased' + normalize = :downcase.to_proc + when 'snakecased', nil + normalize = ->name { name.tr('A-Z-', 'a-z_') } + when 'raw' + normalize = ->name { name } # :itself.to_proc in Ruby >= 2.2 + else + raise ArgumentError, "if provided, event_headers_style must be 'capitalized', 'downcased', 'snakecased' or 'raw'" + end + + headers.each_with_object({}) { |(key, value), hash| + hash[normalize[key]] = value + } + end + def handle(data, payload = {}) url = interpolated(payload)[:post_url] headers = headers() @@ -156,7 +192,11 @@ module Agents } if boolify(interpolated['emit_events']) - create_event payload: { body: response.body, headers: response.headers, status: response.status } + create_event payload: { + body: response.body, + headers: normalize_response_headers(response.headers), + status: response.status + } end end end diff --git a/db/migrate/20160405072512_post_agent_set_event_header_style.rb b/db/migrate/20160405072512_post_agent_set_event_header_style.rb new file mode 100644 index 00000000..f00c4f68 --- /dev/null +++ b/db/migrate/20160405072512_post_agent_set_event_header_style.rb @@ -0,0 +1,11 @@ +class PostAgentSetEventHeaderStyle < ActiveRecord::Migration + def up + Agent.of_type("Agents::PostAgent").each do |post_agent| + if post_agent.send(:boolify, post_agent.options['emit_events']) && + !post_agent.options.key?('event_headers_style') + post_agent.options['event_headers_style'] = 'raw' + post_agent.save! + end + end + end +end diff --git a/spec/models/agents/post_agent_spec.rb b/spec/models/agents/post_agent_spec.rb index 853ea571..77b0f947 100644 --- a/spec/models/agents/post_agent_spec.rb +++ b/spec/models/agents/post_agent_spec.rb @@ -52,7 +52,7 @@ describe Agents::PostAgent do raise "unexpected Content-Type: #{content_type}" end end - { status: 200, body: "a webpage!", headers: { 'Content-Type' => 'text/html' } } + { status: 200, body: "a webpage!", headers: { 'Content-type' => 'text/html' } } } end @@ -226,10 +226,28 @@ describe Agents::PostAgent do expect(@checker.events.last.payload['body']).to eq 'a webpage!' end - it "emits the response headers" do + it "emits the response headers capitalized by default" do @checker.check expect(@checker.events.last.payload['headers']).to eq({ 'Content-Type' => 'text/html' }) end + + it "emits the response headers capitalized" do + @checker.options['event_headers_style'] = 'capitalized' + @checker.check + expect(@checker.events.last.payload['headers']).to eq({ 'Content-Type' => 'text/html' }) + end + + it "emits the response headers downcased" do + @checker.options['event_headers_style'] = 'downcased' + @checker.check + expect(@checker.events.last.payload['headers']).to eq({ 'content-type' => 'text/html' }) + end + + it "emits the response headers snakecased" do + @checker.options['event_headers_style'] = 'snakecased' + @checker.check + expect(@checker.events.last.payload['headers']).to eq({ 'content_type' => 'text/html' }) + end end end end