| @@ -12,11 +12,12 @@ module Agents | ||
| 12 | 12 |  | 
| 13 | 13 | form_configurable :url | 
| 14 | 14 | form_configurable :disable_redirect_follow, type: :array, values: ['true', 'false'] | 
| 15 | + form_configurable :headers_to_save | |
| 15 | 16 |  | 
| 16 | 17 | description <<-MD | 
| 17 | - The HttpStatusAgent will check a url and emit the resulting HTTP status code with the time that it waited for a reply. | |
| 18 | + 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. | |
| 18 | 19 |  | 
| 19 | - Specify a `Url` and the Http Status Agent will produce an event with the http status code. | |
| 20 | + 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. | |
| 20 | 21 |  | 
| 21 | 22 | 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. | 
| 22 | 23 | MD | 
| @@ -26,8 +27,11 @@ module Agents | ||
| 26 | 27 |  | 
| 27 | 28 |            { | 
| 28 | 29 | "url": "...", | 
| 29 | - "status": "..." | |
| 30 | - "elapsed_time": "..." | |
| 30 | + "status": "...", | |
| 31 | + "elapsed_time": "...", | |
| 32 | +            "headers": { | |
| 33 | + "...": "..." | |
| 34 | + } | |
| 31 | 35 | } | 
| 32 | 36 | MD | 
| 33 | 37 |  | 
| @@ -46,29 +50,47 @@ module Agents | ||
| 46 | 50 | errors.add(:base, "a url must be specified") unless options['url'].present? | 
| 47 | 51 | end | 
| 48 | 52 |  | 
| 53 | + def header_array(str) | |
| 54 | +      (str || '').split(',').map(&:strip) | |
| 55 | + end | |
| 56 | + | |
| 49 | 57 | def check | 
| 50 | - check_this_url interpolated[:url] | |
| 58 | + check_this_url interpolated[:url], header_array(interpolated[:headers_to_save]) | |
| 51 | 59 | end | 
| 52 | 60 |  | 
| 53 | 61 | def receive(incoming_events) | 
| 54 | 62 | incoming_events.each do |event| | 
| 55 | 63 | interpolate_with(event) do | 
| 56 | - check_this_url interpolated[:url] | |
| 64 | + check_this_url interpolated[:url], header_array(interpolated[:headers_to_save]) | |
| 57 | 65 | end | 
| 58 | 66 | end | 
| 59 | 67 | end | 
| 60 | 68 |  | 
| 61 | 69 | private | 
| 62 | 70 |  | 
| 63 | - def check_this_url(url) | |
| 71 | + def check_this_url(url, local_headers) | |
| 72 | + # Track time | |
| 64 | 73 |        measured_result = TimeTracker.track { ping(url) } | 
| 74 | + | |
| 75 | +      payload = { 'url' => url, 'response_received' => false, 'elapsed_time' => measured_result.elapsed_time } | |
| 76 | + | |
| 77 | + # Deal with failures | |
| 65 | 78 | if measured_result.result | 
| 66 | -        create_event payload: { 'url' => url, 'status' => measured_result.status.to_s, 'response_received' => true, 'elapsed_time' => measured_result.elapsed_time } | |
| 79 | +        payload.merge!({ 'response_received' => true, 'status' => measured_result.status.to_s }) | |
| 80 | + # Deal with headers | |
| 81 | + if local_headers.present? | |
| 82 | +          header_results = measured_result.result.headers.select {|header, value| local_headers.include?(header)} | |
| 83 | + # Fill in headers that we wanted, but weren't returned | |
| 84 | +          local_headers.each { |header| header_results[header] = nil unless header_results.has_key?(header) } | |
| 85 | +          payload.merge!({ 'headers' => header_results }) | |
| 86 | + end | |
| 87 | + create_event payload: payload | |
| 67 | 88 | memory['last_status'] = measured_result.status.to_s | 
| 68 | 89 | else | 
| 69 | -        create_event payload: { 'url' => url, 'response_received' => false, 'elapsed_time' => measured_result.elapsed_time } | |
| 90 | + create_event payload: payload | |
| 70 | 91 | memory['last_status'] = nil | 
| 71 | 92 | end | 
| 93 | + | |
| 72 | 94 | end | 
| 73 | 95 |  | 
| 74 | 96 | def ping(url) | 
| @@ -7,6 +7,7 @@ describe 'HttpStatusAgent' do | ||
| 7 | 7 | a.service = services(:generic) | 
| 8 | 8 | a.user = users(:jane) | 
| 9 | 9 | a.options['url'] = 'http://google.com' | 
| 10 | + a.options['headers_to_save'] = 'Server' | |
| 10 | 11 | a.save! | 
| 11 | 12 |  | 
| 12 | 13 | def a.interpolate_with(e, &block) | 
| @@ -76,11 +77,12 @@ describe 'HttpStatusAgent' do | ||
| 76 | 77 | before do | 
| 77 | 78 |  | 
| 78 | 79 | def agent.interpolated | 
| 79 | -        @interpolated ||= { :url => SecureRandom.uuid } | |
| 80 | +        @interpolated ||= { :url => SecureRandom.uuid, :headers_to_save => '' } | |
| 80 | 81 | end | 
| 81 | 82 |  | 
| 82 | - def agent.check_this_url url | |
| 83 | + def agent.check_this_url url, local_headers | |
| 83 | 84 | @url = url | 
| 85 | + @local_headers = local_headers | |
| 84 | 86 | end | 
| 85 | 87 |  | 
| 86 | 88 | def agent.checked_url | 
| @@ -103,10 +105,12 @@ describe 'HttpStatusAgent' do | ||
| 103 | 105 |        let(:successful_url) { SecureRandom.uuid } | 
| 104 | 106 |  | 
| 105 | 107 |        let(:status_code) { 200 } | 
| 108 | +      let(:header) { SecureRandom.uuid } | |
| 109 | +      let(:header_value) { SecureRandom.uuid } | |
| 106 | 110 |  | 
| 107 | 111 | let(:event_with_a_successful_ping) do | 
| 108 | - agent.faraday.set(successful_url, Struct.new(:status).new(status_code)) | |
| 109 | -        Event.new.tap { |e| e.payload = { url: successful_url } } | |
| 112 | +        agent.faraday.set(successful_url, Struct.new(:status, :headers).new(status_code, {})) | |
| 113 | +        Event.new.tap { |e| e.payload = { url: successful_url, headers_to_save: "" } } | |
| 110 | 114 | end | 
| 111 | 115 |  | 
| 112 | 116 | let(:events) do | 
| @@ -138,6 +142,11 @@ describe 'HttpStatusAgent' do | ||
| 138 | 142 | expect(agent.the_created_events[0][:payload]['elapsed_time']).not_to be_nil | 
| 139 | 143 | end | 
| 140 | 144 |  | 
| 145 | + it "should not return a header" do | |
| 146 | + agent.receive events | |
| 147 | + expect(agent.the_created_events[0][:payload]['headers']).to be_nil | |
| 148 | + end | |
| 149 | + | |
| 141 | 150 | describe "but the status code is not 200" do | 
| 142 | 151 |          let(:status_code) { 500 } | 
| 143 | 152 |  | 
| @@ -160,8 +169,8 @@ describe 'HttpStatusAgent' do | ||
| 160 | 169 | describe "but the ping returns a status code of 0" do | 
| 161 | 170 |  | 
| 162 | 171 | let(:event_with_a_successful_ping) do | 
| 163 | - agent.faraday.set(successful_url, Struct.new(:status).new(0)) | |
| 164 | -          Event.new.tap { |e| e.payload = { url: successful_url } } | |
| 172 | +          agent.faraday.set(successful_url, Struct.new(:status, :headers).new(0, {})) | |
| 173 | +          Event.new.tap { |e| e.payload = { url: successful_url, headers_to_save: "" } } | |
| 165 | 174 | end | 
| 166 | 175 |  | 
| 167 | 176 | it "should create one event" do | 
| @@ -190,8 +199,8 @@ describe 'HttpStatusAgent' do | ||
| 190 | 199 | describe "but the ping returns a status code of -1" do | 
| 191 | 200 |  | 
| 192 | 201 | let(:event_with_a_successful_ping) do | 
| 193 | - agent.faraday.set(successful_url, Struct.new(:status).new(-1)) | |
| 194 | -          Event.new.tap { |e| e.payload = { url: successful_url } } | |
| 202 | +          agent.faraday.set(successful_url, Struct.new(:status, :headers).new(-1, {})) | |
| 203 | +          Event.new.tap { |e| e.payload = { url: successful_url, headers_to_save: "" } } | |
| 195 | 204 | end | 
| 196 | 205 |  | 
| 197 | 206 | it "should create one event" do | 
| @@ -214,7 +223,7 @@ describe 'HttpStatusAgent' do | ||
| 214 | 223 | describe "and with one event with a failing ping" do | 
| 215 | 224 |  | 
| 216 | 225 |          let(:failing_url)    { SecureRandom.uuid } | 
| 217 | -        let(:event_with_a_failing_ping)    { Event.new.tap { |e| e.payload = { url: failing_url } } } | |
| 226 | +        let(:event_with_a_failing_ping)    { Event.new.tap { |e| e.payload = { url: failing_url, headers_to_save: "" } } } | |
| 218 | 227 |  | 
| 219 | 228 | let(:events) do | 
| 220 | 229 | [event_with_a_successful_ping, event_with_a_failing_ping] | 
| @@ -249,6 +258,39 @@ describe 'HttpStatusAgent' do | ||
| 249 | 258 |  | 
| 250 | 259 | end | 
| 251 | 260 |  | 
| 261 | + describe "with a header specified" do | |
| 262 | + let(:event_with_a_successful_ping) do | |
| 263 | +          agent.faraday.set(successful_url, Struct.new(:status, :headers).new(status_code, {header => header_value})) | |
| 264 | +          Event.new.tap { |e| e.payload = { url: successful_url, headers_to_save: header } } | |
| 265 | + end | |
| 266 | + | |
| 267 | + it "should return the header value" do | |
| 268 | + agent.receive events | |
| 269 | + expect(agent.the_created_events[0][:payload]['headers']).not_to be_nil | |
| 270 | + expect(agent.the_created_events[0][:payload]['headers'][header]).to eq(header_value) | |
| 271 | + end | |
| 272 | + | |
| 273 | + end | |
| 274 | + | |
| 275 | + describe "with existing and non-existing headers specified" do | |
| 276 | +        let(:nonexistant_header) { SecureRandom.uuid } | |
| 277 | + | |
| 278 | + let(:event_with_a_successful_ping) do | |
| 279 | +          agent.faraday.set(successful_url, Struct.new(:status, :headers).new(status_code, {header => header_value})) | |
| 280 | +          Event.new.tap { |e| e.payload = { url: successful_url, headers_to_save: header + "," + nonexistant_header } } | |
| 281 | + end | |
| 282 | + | |
| 283 | + it "should return the existing header's value" do | |
| 284 | + agent.receive events | |
| 285 | + expect(agent.the_created_events[0][:payload]['headers'][header]).to eq(header_value) | |
| 286 | + end | |
| 287 | + | |
| 288 | + it "should return nil for the nonexistant header" do | |
| 289 | + agent.receive events | |
| 290 | + expect(agent.the_created_events[0][:payload]['headers'][nonexistant_header]).to be_nil | |
| 291 | + end | |
| 292 | + | |
| 293 | + end | |
| 252 | 294 | end | 
| 253 | 295 |  | 
| 254 | 296 | describe "validations" do |