File: http_response_code.rb

package info (click to toggle)
ruby-god 0.13.7-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, buster
  • size: 832 kB
  • sloc: ruby: 6,641; ansic: 237; makefile: 3
file content (184 lines) | stat: -rw-r--r-- 6,195 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
require 'net/http'
require 'net/https'

module God
  module Conditions

    # Condition Symbol :http_response_code
    # Type: Poll
    #
    # Trigger based on the response from an HTTP request.
    #
    # Paramaters
    #   Required
    #     +host+ is the hostname to connect [required]
    #     --one of code_is or code_is_not--
    #     +code_is+ trigger if the response code IS one of these
    #               e.g. 500 or '500' or [404, 500] or %w{404 500}
    #     +code_is_not+ trigger if the response code IS NOT one of these
    #                   e.g. 200 or '200' or [200, 302] or %w{200 302}
    #  Optional
    #     +port+ is the port to connect (default 80)
    #     +path+ is the path to connect (default '/')
    #     +headers+ is the hash of HTTP headers to send (default none)
    #     +times+ is the number of times after which to trigger (default 1)
    #             e.g. 3 (times in a row) or [3, 5] (three out of fives times)
    #     +timeout+ is the time to wait for a connection (default 60.seconds)
    #     +ssl+ should the connection use ssl (default false)
    #
    # Examples
    #
    # Trigger if the response code from www.example.com/foo/bar
    # is not a 200 (or if the connection is refused or times out:
    #
    #   on.condition(:http_response_code) do |c|
    #     c.host = 'www.example.com'
    #     c.path = '/foo/bar'
    #     c.code_is_not = 200
    #   end
    #
    # Trigger if the response code is a 404 or a 500 (will not
    # be triggered by a connection refusal or timeout):
    #
    #   on.condition(:http_response_code) do |c|
    #     c.host = 'www.example.com'
    #     c.path = '/foo/bar'
    #     c.code_is = [404, 500]
    #   end
    #
    # Trigger if the response code is not a 200 five times in a row:
    #
    #   on.condition(:http_response_code) do |c|
    #     c.host = 'www.example.com'
    #     c.path = '/foo/bar'
    #     c.code_is_not = 200
    #     c.times = 5
    #   end
    #
    # Trigger if the response code is not a 200 or does not respond
    # within 10 seconds:
    #
    #   on.condition(:http_response_code) do |c|
    #     c.host = 'www.example.com'
    #     c.path = '/foo/bar'
    #     c.code_is_not = 200
    #     c.timeout = 10
    #   end
    class HttpResponseCode < PollCondition
      attr_accessor :code_is,      # e.g. 500 or '500' or [404, 500] or %w{404 500}
                    :code_is_not,  # e.g. 200 or '200' or [200, 302] or %w{200 302}
                    :times,        # e.g. 3 or [3, 5]
                    :host,         # e.g. www.example.com
                    :port,         # e.g. 8080
                    :ssl,          # e.g. true or false
                    :ca_file,      # e.g /path/to/pem_file for ssl verification (checkout http://curl.haxx.se/ca/cacert.pem)
                    :timeout,      # e.g. 60.seconds
                    :path,         # e.g. '/'
                    :headers       # e.g. {'Host' => 'myvirtual.mydomain.com'}

      def initialize
        super
        self.port = 80
        self.path = '/'
        self.headers = {}
        self.times = [1, 1]
        self.timeout = 60.seconds
        self.ssl = false
        self.ca_file = nil
      end

      def prepare
        self.code_is = Array(self.code_is).map { |x| x.to_i } if self.code_is
        self.code_is_not = Array(self.code_is_not).map { |x| x.to_i } if self.code_is_not

        if self.times.kind_of?(Integer)
          self.times = [self.times, self.times]
        end

        @timeline = Timeline.new(self.times[1])
        @history = Timeline.new(self.times[1])
      end

      def reset
        @timeline.clear
        @history.clear
      end

      def valid?
        valid = true
        valid &= complain("Attribute 'host' must be specified", self) if self.host.nil?
        valid &= complain("One (and only one) of attributes 'code_is' and 'code_is_not' must be specified", self) if
          (self.code_is.nil? && self.code_is_not.nil?) || (self.code_is && self.code_is_not)
        valid
      end

      def test
        response = nil

        connection = Net::HTTP.new(self.host, self.port)
        connection.use_ssl = self.port == 443 ? true : self.ssl
        connection.verify_mode = OpenSSL::SSL::VERIFY_NONE if connection.use_ssl?

        if connection.use_ssl? && self.ca_file
          pem = File.read(self.ca_file)
          connection.ca_file = self.ca_file
          connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
        end

        connection.start do |http|
          http.read_timeout = self.timeout
          response = http.get(self.path, self.headers)
        end

        actual_response_code = response.code.to_i
        if self.code_is && self.code_is.include?(actual_response_code)
          pass(actual_response_code)
        elsif self.code_is_not && !self.code_is_not.include?(actual_response_code)
          pass(actual_response_code)
        else
          fail(actual_response_code)
        end
      rescue Errno::ECONNREFUSED
        self.code_is ? fail('Refused') : pass('Refused')
      rescue Errno::ECONNRESET
        self.code_is ? fail('Reset') : pass('Reset')
      rescue EOFError
        self.code_is ? fail('EOF') : pass('EOF')
      rescue Timeout::Error
        self.code_is ? fail('Timeout') : pass('Timeout')
      rescue Errno::ETIMEDOUT
        self.code_is ? fail('Timedout') : pass('Timedout')
      rescue Exception => failure
        self.code_is ? fail(failure.class.name) : pass(failure.class.name)
      end

      private

      def pass(code)
        @timeline << true
        if @timeline.select { |x| x }.size >= self.times.first
          self.info = "http response abnormal #{history(code, true)}"
          true
        else
          self.info = "http response nominal #{history(code, true)}"
          false
        end
      end

      def fail(code)
        @timeline << false
        self.info = "http response nominal #{history(code, false)}"
        false
      end

      def history(code, passed)
        entry = code.to_s.dup
        entry = '*' + entry if passed
        @history << entry
        '[' + @history.join(", ") + ']'
      end

    end

  end
end