ianheggie/health_check

View on GitHub
lib/health_check/health_check_controller.rb

Summary

Maintainability
B
5 hrs
Test Coverage
# Copyright (c) 2010-2021 Ian Heggie, released under the MIT license.
# See MIT-LICENSE for details.
require "ipaddr"

module HealthCheck
  class HealthCheckController < ActionController::Base

    layout false if self.respond_to? :layout
    before_action :check_origin_ip
    before_action :authenticate

    def index
      last_modified = Time.now.utc
      max_age = HealthCheck.max_age
      if max_age > 1
        last_modified = Time.at((last_modified.to_f / max_age).floor * max_age).utc
      end
      is_public = (max_age > 1) && ! HealthCheck.basic_auth_username
      if stale?(last_modified: last_modified, public: is_public)
        checks = params[:checks] ? params[:checks].split('_') : ['standard']
        checks -= HealthCheck.middleware_checks if HealthCheck.installed_as_middleware
        begin
          errors = HealthCheck::Utils.process_checks(checks)
        rescue Exception => e
          errors = e.message.blank? ? e.class.to_s : e.message.to_s
        end
        response.headers['Cache-Control'] = "must-revalidate, max-age=#{max_age}"
        if errors.blank?
          send_response true, nil, :ok, :ok
          if HealthCheck.success_callbacks
            HealthCheck.success_callbacks.each do |callback|
              callback.call(checks)
            end 
          end
        else
          msg = HealthCheck.include_error_in_response_body ? "#{HealthCheck.failure}: #{errors}" : nil
          send_response false, msg, HealthCheck.http_status_for_error_text, HealthCheck.http_status_for_error_object
          
          # Log a single line as some uptime checkers only record that it failed, not the text returned
          msg = "#{HealthCheck.failure}: #{errors}"
          logger.send(HealthCheck.log_level, msg) if logger && HealthCheck.log_level
          if HealthCheck.failure_callbacks
            HealthCheck.failure_callbacks.each do |callback|
              callback.call(checks, msg)
            end
          end
        end
      end
    end

    protected

    def send_response(healthy, msg, text_status, obj_status)
      msg ||= healthy ? HealthCheck.success : HealthCheck.failure
      obj = { healthy: healthy, message: msg}
      respond_to do |format|
        format.html { render plain: msg, status: text_status, content_type: 'text/plain' }
        format.json { render json: obj, status: obj_status }
        format.xml { render xml: obj, status: obj_status }
        format.any { render plain: msg, status: text_status, content_type: 'text/plain' }
      end
    end

    def authenticate
      return unless HealthCheck.basic_auth_username && HealthCheck.basic_auth_password
      authenticate_or_request_with_http_basic('Health Check') do |username, password|
        username == HealthCheck.basic_auth_username && password == HealthCheck.basic_auth_password
      end
    end

    def check_origin_ip
      request_ipaddr = IPAddr.new(HealthCheck.accept_proxied_requests ? request.remote_ip : request.ip)
      unless HealthCheck.origin_ip_whitelist.blank? ||
          HealthCheck.origin_ip_whitelist.any? { |addr| IPAddr.new(addr).include? request_ipaddr }
        render plain: 'Health check is not allowed for the requesting IP',
               status: HealthCheck.http_status_for_ip_whitelist_error,
               content_type: 'text/plain'
      end
    end

    # turn cookies for CSRF off
    def protect_against_forgery?
      false
    end

  end
end