From 3c8d6655a313ccd03ca10408a88a2abfc7737640 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Mon, 20 Jun 2016 00:37:15 -0700 Subject: [PATCH] Expand HTTP status agent (#1521) * Add WIP support for HTTP headers * Actually pass the header to check_this_url * Fix an unmerged hunk * Fix some syntax errors * Fix an outdated variable name * Comment on which sections do what * Get rid of (another) unmerged hunk * Show a form element for the header field * Fix event emitter conditional * Adjust tests for header logic * Test for not returning a header * Refactor payload generation * Rename 'header' to 'headers' * Add multiple header support * Update HttpStatusAgent docs * Fix (some) failing tests * Fix remaining tests * Add specs for HttpStatusAgent's header code * Super tiny cleanups --- app/models/agents/http_status_agent.rb | 40 +++++++++++---- spec/controllers/http_status_agent_spec.rb | 60 ++++++++++++++++++---- 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/app/models/agents/http_status_agent.rb b/app/models/agents/http_status_agent.rb index 571d51ef..0eef8b24 100644 --- a/app/models/agents/http_status_agent.rb +++ b/app/models/agents/http_status_agent.rb @@ -12,11 +12,12 @@ module Agents form_configurable :url form_configurable :disable_redirect_follow, type: :array, values: ['true', 'false'] + form_configurable :headers_to_save description <<-MD - The HttpStatusAgent will check a url and emit the resulting HTTP status code with the time that it waited for a reply. + The HttpStatusAgent will check a url and emit the resulting HTTP status code with the time that it waited for a reply. Additionally, it will optionally emit the value of one or more specified headers. - Specify a `Url` and the Http Status Agent will produce an event with the http status code. + Specify a `Url` and the Http Status Agent will produce an event with the HTTP status code. If you specify one or more `Headers to save` (comma-delimited) as well, that header or headers' value(s) will be included in the event. The `disable redirect follow` option causes the Agent to not follow HTTP redirects. For example, setting this to `true` will cause an agent that receives a 301 redirect to `http://yahoo.com` to return a status of 301 instead of following the redirect and returning 200. MD @@ -26,8 +27,11 @@ module Agents { "url": "...", - "status": "..." - "elapsed_time": "..." + "status": "...", + "elapsed_time": "...", + "headers": { + "...": "..." + } } MD @@ -46,29 +50,47 @@ module Agents errors.add(:base, "a url must be specified") unless options['url'].present? end + def header_array(str) + (str || '').split(',').map(&:strip) + end + def check - check_this_url interpolated[:url] + check_this_url interpolated[:url], header_array(interpolated[:headers_to_save]) end def receive(incoming_events) incoming_events.each do |event| interpolate_with(event) do - check_this_url interpolated[:url] + check_this_url interpolated[:url], header_array(interpolated[:headers_to_save]) end end end private - def check_this_url(url) + def check_this_url(url, local_headers) + # Track time measured_result = TimeTracker.track { ping(url) } + + payload = { 'url' => url, 'response_received' => false, 'elapsed_time' => measured_result.elapsed_time } + + # Deal with failures if measured_result.result - create_event payload: { 'url' => url, 'status' => measured_result.status.to_s, 'response_received' => true, 'elapsed_time' => measured_result.elapsed_time } + payload.merge!({ 'response_received' => true, 'status' => measured_result.status.to_s }) + # Deal with headers + if local_headers.present? + header_results = measured_result.result.headers.select {|header, value| local_headers.include?(header)} + # Fill in headers that we wanted, but weren't returned + local_headers.each { |header| header_results[header] = nil unless header_results.has_key?(header) } + payload.merge!({ 'headers' => header_results }) + end + create_event payload: payload memory['last_status'] = measured_result.status.to_s else - create_event payload: { 'url' => url, 'response_received' => false, 'elapsed_time' => measured_result.elapsed_time } + create_event payload: payload memory['last_status'] = nil end + end def ping(url) diff --git a/spec/controllers/http_status_agent_spec.rb b/spec/controllers/http_status_agent_spec.rb index 6c8a724d..d0881f7a 100644 --- a/spec/controllers/http_status_agent_spec.rb +++ b/spec/controllers/http_status_agent_spec.rb @@ -7,6 +7,7 @@ describe 'HttpStatusAgent' do a.service = services(:generic) a.user = users(:jane) a.options['url'] = 'http://google.com' + a.options['headers_to_save'] = 'Server' a.save! def a.interpolate_with(e, &block) @@ -76,11 +77,12 @@ describe 'HttpStatusAgent' do before do def agent.interpolated - @interpolated ||= { :url => SecureRandom.uuid } + @interpolated ||= { :url => SecureRandom.uuid, :headers_to_save => '' } end - def agent.check_this_url url + def agent.check_this_url url, local_headers @url = url + @local_headers = local_headers end def agent.checked_url @@ -103,10 +105,12 @@ describe 'HttpStatusAgent' do let(:successful_url) { SecureRandom.uuid } let(:status_code) { 200 } + let(:header) { SecureRandom.uuid } + let(:header_value) { SecureRandom.uuid } let(:event_with_a_successful_ping) do - agent.faraday.set(successful_url, Struct.new(:status).new(status_code)) - Event.new.tap { |e| e.payload = { url: successful_url } } + agent.faraday.set(successful_url, Struct.new(:status, :headers).new(status_code, {})) + Event.new.tap { |e| e.payload = { url: successful_url, headers_to_save: "" } } end let(:events) do @@ -138,6 +142,11 @@ describe 'HttpStatusAgent' do expect(agent.the_created_events[0][:payload]['elapsed_time']).not_to be_nil end + it "should not return a header" do + agent.receive events + expect(agent.the_created_events[0][:payload]['headers']).to be_nil + end + describe "but the status code is not 200" do let(:status_code) { 500 } @@ -160,8 +169,8 @@ describe 'HttpStatusAgent' do describe "but the ping returns a status code of 0" do let(:event_with_a_successful_ping) do - agent.faraday.set(successful_url, Struct.new(:status).new(0)) - Event.new.tap { |e| e.payload = { url: successful_url } } + agent.faraday.set(successful_url, Struct.new(:status, :headers).new(0, {})) + Event.new.tap { |e| e.payload = { url: successful_url, headers_to_save: "" } } end it "should create one event" do @@ -190,8 +199,8 @@ describe 'HttpStatusAgent' do describe "but the ping returns a status code of -1" do let(:event_with_a_successful_ping) do - agent.faraday.set(successful_url, Struct.new(:status).new(-1)) - Event.new.tap { |e| e.payload = { url: successful_url } } + agent.faraday.set(successful_url, Struct.new(:status, :headers).new(-1, {})) + Event.new.tap { |e| e.payload = { url: successful_url, headers_to_save: "" } } end it "should create one event" do @@ -214,7 +223,7 @@ describe 'HttpStatusAgent' do describe "and with one event with a failing ping" do let(:failing_url) { SecureRandom.uuid } - let(:event_with_a_failing_ping) { Event.new.tap { |e| e.payload = { url: failing_url } } } + let(:event_with_a_failing_ping) { Event.new.tap { |e| e.payload = { url: failing_url, headers_to_save: "" } } } let(:events) do [event_with_a_successful_ping, event_with_a_failing_ping] @@ -249,6 +258,39 @@ describe 'HttpStatusAgent' do end + describe "with a header specified" do + let(:event_with_a_successful_ping) do + agent.faraday.set(successful_url, Struct.new(:status, :headers).new(status_code, {header => header_value})) + Event.new.tap { |e| e.payload = { url: successful_url, headers_to_save: header } } + end + + it "should return the header value" do + agent.receive events + expect(agent.the_created_events[0][:payload]['headers']).not_to be_nil + expect(agent.the_created_events[0][:payload]['headers'][header]).to eq(header_value) + end + + end + + describe "with existing and non-existing headers specified" do + let(:nonexistant_header) { SecureRandom.uuid } + + let(:event_with_a_successful_ping) do + agent.faraday.set(successful_url, Struct.new(:status, :headers).new(status_code, {header => header_value})) + Event.new.tap { |e| e.payload = { url: successful_url, headers_to_save: header + "," + nonexistant_header } } + end + + it "should return the existing header's value" do + agent.receive events + expect(agent.the_created_events[0][:payload]['headers'][header]).to eq(header_value) + end + + it "should return nil for the nonexistant header" do + agent.receive events + expect(agent.the_created_events[0][:payload]['headers'][nonexistant_header]).to be_nil + end + + end end describe "validations" do