Class: Sensu::API::HTTPHandler
- Inherits:
-
EM::HttpServer::Server
- Object
- EM::HttpServer::Server
- Sensu::API::HTTPHandler
- Includes:
- Routes, Utilities::FilterResponseContent
- Defined in:
- lib/sensu/api/http_handler.rb
Constant Summary
Constants included from Routes
Routes::DELETE_METHOD, Routes::GET_METHOD, Routes::GET_ROUTES, Routes::HEAD_METHOD, Routes::OPTIONS_METHOD, Routes::POST_METHOD, Routes::ROUTES
Constants included from Routes::Silenced
Routes::Silenced::SILENCED_CHECK_URI, Routes::Silenced::SILENCED_CLEAR_URI, Routes::Silenced::SILENCED_ID_URI, Routes::Silenced::SILENCED_SUBSCRIPTION_URI, Routes::Silenced::SILENCED_URI
Constants included from Routes::Results
Routes::Results::RESULTS_CLIENT_URI, Routes::Results::RESULTS_URI, Routes::Results::RESULT_URI
Constants included from Routes::Stashes
Routes::Stashes::STASHES_URI, Routes::Stashes::STASH_URI
Constants included from Routes::Aggregates
Routes::Aggregates::AGGREGATES_URI, Routes::Aggregates::AGGREGATE_CHECKS_URI, Routes::Aggregates::AGGREGATE_CLIENTS_URI, Routes::Aggregates::AGGREGATE_RESULTS_SEVERITY_URI, Routes::Aggregates::AGGREGATE_URI
Constants included from Routes::Resolve
Constants included from Routes::Events
Routes::Events::EVENTS_CLIENT_URI, Routes::Events::EVENTS_URI, Routes::Events::EVENT_URI
Constants included from Routes::Request
Constants included from Routes::Checks
Routes::Checks::CHECKS_URI, Routes::Checks::CHECK_URI
Constants included from Routes::Clients
Routes::Clients::CLIENTS_URI, Routes::Clients::CLIENT_HISTORY_URI, Routes::Clients::CLIENT_URI
Constants included from Routes::Health
Constants included from Routes::Info
Constants included from Routes::Settings
Routes::Settings::SETTINGS_URI
Instance Attribute Summary collapse
-
#logger ⇒ Object
Returns the value of attribute logger.
-
#redis ⇒ Object
Returns the value of attribute redis.
-
#settings ⇒ Object
Returns the value of attribute settings.
-
#transport ⇒ Object
Returns the value of attribute transport.
Instance Method Summary collapse
-
#accepted! ⇒ Object
Respond to the HTTP request with a ‘202` (Accepted) response.
-
#allowed_http_methods? ⇒ Array
Determine the allowed HTTP methods for a route.
-
#authorized? ⇒ TrueClass, FalseClass
Determine if an HTTP request is authorized.
-
#bad_request! ⇒ Object
Respond to the HTTP request with a ‘400` (Bad Request) response.
-
#connected? ⇒ Boolean
Determine if the API is connected to Redis and the Transport.
-
#create_response ⇒ Object
Create an EM HTTP Server HTTP response object, ‘@response`.
-
#created! ⇒ Object
Respond to the HTTP request with a ‘201` (Created) response.
-
#determine_route_method ⇒ Symbol
Determine the route method for the HTTP request method and URI.
-
#error! ⇒ Object
Respond to the HTTP request with a ‘500` (Internal Server Error) response.
-
#http_request_errback(error) ⇒ Object
Catch uncaught/unexpected errors, log them, and attempt to respond with a ‘500` (Internal Server Error) HTTP response.
-
#integer_parameter(value) ⇒ Integer?
Determine if a parameter has an integer value and if so return it as one.
-
#log_request ⇒ Object
Log the HTTP request.
-
#log_response ⇒ Object
Log the HTTP response.
-
#method_not_allowed!(allowed_http_methods = []) ⇒ Object
Respond to the HTTP request with a ‘405` (Method Not Allowed) response.
-
#no_content! ⇒ Object
Respond to the HTTP request with a ‘204` (No Content) response.
-
#not_found! ⇒ Object
Respond to the HTTP request with a ‘404` (Not Found) response.
-
#pagination(items) ⇒ Array
Paginate the provided items.
-
#parse_parameters ⇒ Object
Parse the HTTP request query string for parameters.
-
#parse_uri(regex) ⇒ Array
Parse the HTTP request URI using a regular expression, returning the URI unescaped match data values.
-
#precondition_failed! ⇒ Object
Respond to the HTTP request with a ‘412` (Precondition Failed) response.
-
#process_http_request ⇒ Object
Process a HTTP request.
-
#read_data(rules = {}) {|Object| ... } ⇒ Object
Read JSON data from the HTTP request content and validate it with the provided rules.
-
#request_details ⇒ Object
Create a hash containing the HTTP request details.
-
#respond ⇒ Object
Respond to an HTTP request.
-
#route_request ⇒ Object
Route the HTTP request.
-
#set_cors_headers ⇒ Object
Set the cors (Cross-origin resource sharing) HTTP headers.
-
#set_headers ⇒ Object
Set the HTTP response headers, including the request ID and cors headers (via ‘set_cores_headers()`).
-
#unauthorized! ⇒ Object
Respond to the HTTP request with a ‘401` (Unauthroized) response.
Methods included from Routes::Silenced
#fetch_silenced, #get_silenced, #get_silenced_check, #get_silenced_id, #get_silenced_subscription, #post_silenced, #post_silenced_clear
Methods included from Routes::Results
#delete_result, #get_result, #get_results, #get_results_client, #post_results
Methods included from Routes::Stashes
#delete_stash, #get_stash, #get_stashes, #post_stash, #post_stashes
Methods included from Routes::Aggregates
#delete_aggregate, #get_aggregate, #get_aggregate_checks, #get_aggregate_clients, #get_aggregate_results_severity, #get_aggregates
Methods included from Routes::Resolve
Methods included from Routes::Events
#delete_event, #get_event, #get_events, #get_events_client
Methods included from Routes::Request
Methods included from Routes::Checks
#delete_check, #get_check, #get_checks
Methods included from Routes::Clients
#delete_client, #get_client, #get_client_history, #get_clients, #post_clients
Methods included from Routes::Health
Methods included from Routes::Info
Methods included from Routes::Settings
Instance Attribute Details
#logger ⇒ Object
Returns the value of attribute logger.
16 17 18 |
# File 'lib/sensu/api/http_handler.rb', line 16 def logger @logger end |
#redis ⇒ Object
Returns the value of attribute redis.
16 17 18 |
# File 'lib/sensu/api/http_handler.rb', line 16 def redis @redis end |
#settings ⇒ Object
Returns the value of attribute settings.
16 17 18 |
# File 'lib/sensu/api/http_handler.rb', line 16 def settings @settings end |
#transport ⇒ Object
Returns the value of attribute transport.
16 17 18 |
# File 'lib/sensu/api/http_handler.rb', line 16 def transport @transport end |
Instance Method Details
#accepted! ⇒ Object
Respond to the HTTP request with a ‘202` (Accepted) response.
273 274 275 276 277 |
# File 'lib/sensu/api/http_handler.rb', line 273 def accepted! @response_status = 202 @response_status_string = "Accepted" respond end |
#allowed_http_methods? ⇒ Array
Determine the allowed HTTP methods for a route. The route regular expressions and associated route method calls are provided by ‘ROUTES`. This method returns an array of HTTP methods that have a route that matches the HTTP request URI.
343 344 345 346 347 348 349 350 |
# File 'lib/sensu/api/http_handler.rb', line 343 def allowed_http_methods? ROUTES.map { |http_method, routes| match = routes.detect do |route| @http_request_uri =~ route[0] end match ? http_method : nil }.flatten.compact end |
#authorized? ⇒ TrueClass, FalseClass
Determine if an HTTP request is authorized. This method compares the configured API user and password (if any) with the HTTP request basic authentication credentials. No authentication is done if the API user and password are not configured. OPTIONS HTTP requests bypass authentication.
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/sensu/api/http_handler.rb', line 219 def api = @settings[:api] if api && api[:user] && api[:password] if @http_request_method == OPTIONS_METHOD true elsif @http[:authorization] scheme, base64 = @http[:authorization].split("\s") if scheme == "Basic" user, password = Base64.decode64(base64).split(":") user == api[:user] && password == api[:password] else false end else false end else true end end |
#bad_request! ⇒ Object
Respond to the HTTP request with a ‘400` (Bad Request) response.
290 291 292 293 294 |
# File 'lib/sensu/api/http_handler.rb', line 290 def bad_request! @response_status = 400 @response_status_string = "Bad Request" respond end |
#connected? ⇒ Boolean
Determine if the API is connected to Redis and the Transport. This method sets the ‘@response_content` if the API is not connected or it has not yet initialized the connection objects. The `/info` and `/health` routes are excluded from the connectivity checks.
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/sensu/api/http_handler.rb', line 245 def connected? connected = true if @redis && @transport unless @http_request_uri =~ INFO_URI || @http_request_uri =~ HEALTH_URI unless @redis.connected? @response_content = {:error => "not connected to redis"} connected = false end unless @transport.connected? @response_content = {:error => "not connected to transport"} connected = false end end else @response_content = {:error => "redis and transport connections not initialized"} connected = false end connected end |
#create_response ⇒ Object
Create an EM HTTP Server HTTP response object, ‘@response`. The response object is use to build up the response status, status string, content type, and content. The response object is responsible for sending the HTTP response to the HTTP client and closing the connection afterwards.
136 137 138 |
# File 'lib/sensu/api/http_handler.rb', line 136 def create_response @response = EM::DelegatedHttpResponse.new(self) end |
#created! ⇒ Object
Respond to the HTTP request with a ‘201` (Created) response.
266 267 268 269 270 |
# File 'lib/sensu/api/http_handler.rb', line 266 def created! @response_status = 201 @response_status_string = "Created" respond end |
#determine_route_method ⇒ Symbol
Determine the route method for the HTTP request method and URI. The route regular expressions and associated route method calls are provided by ‘ROUTES`. This method will return the first route method name (Ruby symbol) that has matching URI regular expression. If an HTTP method is not supported, or there is not a matching regular expression, `nil` will be returned.
361 362 363 364 365 366 367 368 369 370 |
# File 'lib/sensu/api/http_handler.rb', line 361 def determine_route_method if ROUTES.has_key?(@http_request_method) route = ROUTES[@http_request_method].detect do |route| @http_request_uri =~ route[0] end route ? route[1] : nil else nil end end |
#error! ⇒ Object
Respond to the HTTP request with a ‘500` (Internal Server Error) response.
331 332 333 334 335 |
# File 'lib/sensu/api/http_handler.rb', line 331 def error! @response_status = 500 @response_status_string = "Internal Server Error" respond end |
#http_request_errback(error) ⇒ Object
Catch uncaught/unexpected errors, log them, and attempt to respond with a ‘500` (Internal Server Error) HTTP response. This method is called by EM HTTP Server.
425 426 427 428 429 430 431 |
# File 'lib/sensu/api/http_handler.rb', line 425 def http_request_errback(error) @logger.error("unexpected api error", { :error => error.to_s, :backtrace => error.backtrace.join("\n") }) error! rescue nil end |
#integer_parameter(value) ⇒ Integer?
Determine if a parameter has an integer value and if so return it as one. This method will return ‘nil` if the parameter value is not an integer.
98 99 100 |
# File 'lib/sensu/api/http_handler.rb', line 98 def integer_parameter(value) value =~ /\A[0-9]+\z/ ? value.to_i : nil end |
#log_request ⇒ Object
Log the HTTP request. The debug log level is used for requests as response logging includes the same information.
45 46 47 |
# File 'lib/sensu/api/http_handler.rb', line 45 def log_request @logger.debug("api request", request_details) end |
#log_response ⇒ Object
Log the HTTP response. This method calculates the request/response time. The debug log level is used for the response body log event, as it is generally very verbose and unnecessary in most cases.
53 54 55 56 57 58 59 60 |
# File 'lib/sensu/api/http_handler.rb', line 53 def log_response @logger.info("api response", { :request => request_details, :status => @response.status, :content_length => @response.content.to_s.bytesize, :time => (Time.now.to_f - @request_start_time).round(3) }) end |
#method_not_allowed!(allowed_http_methods = []) ⇒ Object
Respond to the HTTP request with a ‘405` (Method Not Allowed) response.
314 315 316 317 318 319 |
# File 'lib/sensu/api/http_handler.rb', line 314 def method_not_allowed!(allowed_http_methods=[]) @response.headers["Allow"] = allowed_http_methods.join(", ") @response_status = 405 @response_status_string = "Method Not Allowed" respond end |
#no_content! ⇒ Object
Respond to the HTTP request with a ‘204` (No Content) response.
281 282 283 284 285 286 |
# File 'lib/sensu/api/http_handler.rb', line 281 def no_content! @response_status = 204 @response_status_string = "No Content" @response_content = nil respond end |
#not_found! ⇒ Object
Respond to the HTTP request with a ‘404` (Not Found) response.
307 308 309 310 311 |
# File 'lib/sensu/api/http_handler.rb', line 307 def not_found! @response_status = 404 @response_status_string = "Not Found" respond end |
#pagination(items) ⇒ Array
Paginate the provided items. This method uses two HTTP query parameters to determine how to paginate the items, ‘limit` and `offset`. The parameter `limit` specifies how many items are to be returned in the response. The parameter `offset` specifies the items array index, skipping a number of items. This method sets the “X-Pagination” HTTP response header to a JSON object containing the `limit`, `offset` and `total` number of items that are being paginated.
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/sensu/api/http_handler.rb', line 174 def pagination(items) limit = integer_parameter(@params[:limit]) offset = integer_parameter(@params[:offset]) || 0 unless limit.nil? @response.headers["X-Pagination"] = Sensu::JSON.dump( :limit => limit, :offset => offset, :total => items.length ) paginated = items.slice(offset, limit) Array(paginated) else items end end |
#parse_parameters ⇒ Object
Parse the HTTP request query string for parameters. This method creates ‘@params`, a hash of parsed query parameters, used by the API routes. This method also creates `@filter_params`, a hash of parsed response content filter parameters.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/sensu/api/http_handler.rb', line 77 def parse_parameters @params = {} if @http_query_string @http_query_string.split("&").each do |pair| key, value = pair.split("=") @params[key.to_sym] = value if key.start_with?("filter.") filter_param = key.sub(/^filter\./, "") @filter_params ||= {} @filter_params[filter_param] = value end end end end |
#parse_uri(regex) ⇒ Array
Parse the HTTP request URI using a regular expression, returning the URI unescaped match data values.
67 68 69 70 |
# File 'lib/sensu/api/http_handler.rb', line 67 def parse_uri(regex) uri_match = regex.match(@http_request_uri)[1..-1] uri_match.map { |s| URI.decode_www_form_component(s) } end |
#precondition_failed! ⇒ Object
Respond to the HTTP request with a ‘412` (Precondition Failed) response.
323 324 325 326 327 |
# File 'lib/sensu/api/http_handler.rb', line 323 def precondition_failed! @response_status = 412 @response_status_string = "Precondition Failed" respond end |
#process_http_request ⇒ Object
Process a HTTP request. Log the request, parse the HTTP query parameters, create the HTTP response object, set the cors HTTP response headers, determine if the request is authorized, determine if the API is connected to Redis and the Transport, and then route the HTTP request (responding to the request). This method is called by EM HTTP Server when handling a new connection.
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/sensu/api/http_handler.rb', line 404 def process_http_request log_request parse_parameters create_response set_headers if if connected? route_request else error! end else end end |
#read_data(rules = {}) {|Object| ... } ⇒ Object
Read JSON data from the HTTP request content and validate it with the provided rules. If the HTTP request content does not contain valid JSON or it does not pass validation, this method returns a ‘400` (Bad Request) HTTP response.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/sensu/api/http_handler.rb', line 110 def read_data(rules={}) begin data = Sensu::JSON.load(@http_content) valid = data.is_a?(Hash) && rules.all? do |key, rule| value = data[key] (Array(rule[:type]).any? {|type| value.is_a?(type)} || (rule[:nil_ok] && value.nil?)) && (value.nil? || rule[:regex].nil?) || (rule[:regex] && value.is_a?(String) && (value =~ rule[:regex]) == 0) end if valid yield(data) else bad_request! end rescue Sensu::JSON::ParseError bad_request! end end |
#request_details ⇒ Object
Create a hash containing the HTTP request details. This method determines the remote address for the HTTP client (using EventMachine Connection ‘get_peername()`).
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/sensu/api/http_handler.rb', line 23 def request_details return @request_details if @request_details @request_id = @http.fetch(:x_request_id, random_uuid) @request_start_time = Time.now.to_f _, remote_address = Socket.unpack_sockaddr_in(get_peername) @request_details = { :request_id => @request_id, :remote_address => remote_address, :user_agent => @http[:user_agent], :method => @http_request_method, :uri => @http_request_uri, :query_string => @http_query_string, :body => @http_content } if @http[:x_forwarded_for] @request_details[:x_forwarded_for] = @http[:x_forwarded_for] end @request_details end |
#respond ⇒ Object
Respond to an HTTP request. The routes set ‘@response_status`, `@response_status_string`, and `@response_content` appropriately. The HTTP response status defaults to `200` with the status string `OK`. If filter params were provided, `@response_content` is filtered (mutated). The Sensu API only returns JSON response content, `@response_content` is assumed to be a Ruby object that can be serialized as JSON.
197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/sensu/api/http_handler.rb', line 197 def respond @response.status = @response_status || 200 @response.status_string = @response_status_string || "OK" if @response_content && @http_request_method != HEAD_METHOD if @http_request_method == GET_METHOD && @filter_params filter_response_content! end @response.content_type "application/json" @response.content = Sensu::JSON.dump(@response_content) end @response.headers["Connection"] = "close" log_response @response.send_response end |
#route_request ⇒ Object
Route the HTTP request. OPTIONS HTTP requests will always return a ‘200` with no response content. This method uses `determine_route_method()` to determine the symbolized route method to send/call. If a route method does not exist for the HTTP request method and URI, this method uses `allowed_http_methods?()` to determine if a 404 (Not Found) or 405 (Method Not Allowed) HTTP response should be used.
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/sensu/api/http_handler.rb', line 379 def route_request if @http_request_method == OPTIONS_METHOD respond else route_method = determine_route_method if route_method send(route_method) else allowed_http_methods = allowed_http_methods? if allowed_http_methods.empty? not_found! else method_not_allowed!(allowed_http_methods) end end end end |
#set_cors_headers ⇒ Object
Set the cors (Cross-origin resource sharing) HTTP headers.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/sensu/api/http_handler.rb', line 141 def set_cors_headers api = @settings[:api] || {} api[:cors] ||= { "Origin" => "*", "Methods" => "GET, POST, PUT, DELETE, OPTIONS", "Credentials" => "true", "Headers" => "Origin, X-Requested-With, Content-Type, Accept, Authorization" } if api[:cors].is_a?(Hash) api[:cors].each do |header, value| @response.headers["Access-Control-Allow-#{header}"] = value end end end |
#set_headers ⇒ Object
Set the HTTP response headers, including the request ID and cors headers (via ‘set_cores_headers()`).
158 159 160 161 |
# File 'lib/sensu/api/http_handler.rb', line 158 def set_headers @response.headers["X-Request-ID"] = @request_id set_cors_headers end |
#unauthorized! ⇒ Object
Respond to the HTTP request with a ‘401` (Unauthroized) response. This method sets the “WWW-Autenticate” HTTP response header.
299 300 301 302 303 304 |
# File 'lib/sensu/api/http_handler.rb', line 299 def @response.headers["WWW-Authenticate"] = 'Basic realm="Restricted Area"' @response_status = 401 @response_status_string = "Unauthorized" respond end |