├── .gitignore ├── .ruby-gemset ├── .ruby-version ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── rack-heartbeat.rb └── rack │ ├── heartbeat.rb │ └── heartbeat │ └── railtie.rb ├── rack-heartbeat.gemspec └── test ├── helper.rb └── rack └── test_heartbeat.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | .bundle 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.1 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'rake' 6 | gem 'coveralls', :group => :test, :require => false 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 James Cox [https://github.com/imajes] 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rack::Heartbeat 2 | 3 | A tiny gem that installs a Rack middleware to respond to heartbeat requests. This saves you all the trouble of creating custom controllers, routes, and static files, and prevents your logs from bloating. 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | gem 'rack-heartbeat' 10 | 11 | And then execute: 12 | 13 | $ bundle 14 | 15 | Or install it yourself as: 16 | 17 | $ gem install rack-heartbeat 18 | 19 | ## Usage 20 | 21 | Once the gem is installed, you're done. Just browse to http://localhost:3000/heartbeat to get a text response with "OK". 22 | 23 | If you want to customize the url for any reason, you can configure that, too: 24 | 25 | ```ruby 26 | # config/initializers/rack_heartbeat.rb 27 | Rack::Heartbeat.setup do |config| 28 | config.heartbeat_path = 'health-check.txt' 29 | end 30 | ``` 31 | 32 | You can also customize headers and response, for example if you want to do the cross-domain request, you can 33 | either set the CORS headers, or just send an image: 34 | 35 | ```ruby 36 | # config/initializers/rack_heartbeat.rb 37 | Rack::Heartbeat.setup do |config| 38 | config.heartbeat_response = Base64.decode64('R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==') 39 | config.heartbeat_headers = {'Content-Type' => 'image/gif'} 40 | end 41 | ``` 42 | 43 | ## Contributing 44 | 45 | 1. Fork it 46 | 2. Create your feature branch (`git checkout -b my-new-feature`) 47 | 3. Commit your changes (`git commit -am 'Added some feature'`) 48 | 4. Push to the branch (`git push origin my-new-feature`) 49 | 5. Create new Pull Request 50 | 51 | ## Contributors 52 | 53 | * [James Cox](https://github.com/imajes) 54 | * [Steve Mitchell](https://github.com/theSteveMitchell) 55 | * [Igor Rzegocki](https://github.com/ajgon) 56 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | require 'rake/testtask' 4 | 5 | Rake::TestTask.new(:test) do |t| 6 | t.libs << 'test' 7 | t.test_files = FileList['test/**/test*.rb'] 8 | t.verbose = true 9 | end 10 | -------------------------------------------------------------------------------- /lib/rack-heartbeat.rb: -------------------------------------------------------------------------------- 1 | require 'rack/heartbeat' 2 | require 'rack/heartbeat/railtie' if defined?(Rails) 3 | -------------------------------------------------------------------------------- /lib/rack/heartbeat.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | # A heartbeat mechanism for the server. This will add a _/heartbeat_ endpoint 3 | # that returns status 200 and content OK when executed. 4 | 5 | class Heartbeat 6 | @@heartbeat_path = 'heartbeat' 7 | @@heartbeat_response = 'OK' 8 | @@heartbeat_headers = {"Content-Type" => "text/plain"} 9 | 10 | class << self 11 | def heartbeat_path 12 | @@heartbeat_path 13 | end 14 | 15 | def heartbeat_response 16 | @@heartbeat_response 17 | end 18 | 19 | def heartbeat_headers 20 | @@heartbeat_headers 21 | end 22 | 23 | def heartbeat_path=(path) 24 | @@heartbeat_path = path 25 | end 26 | 27 | def heartbeat_response=(response) 28 | @@heartbeat_response = response 29 | end 30 | 31 | def heartbeat_headers=(headers_hash) 32 | @@heartbeat_headers = headers_hash 33 | end 34 | end 35 | 36 | def initialize(app) 37 | @app = app 38 | end 39 | 40 | def call(env) 41 | if env['PATH_INFO'] == "/#{heartbeat_path}" 42 | NewRelic::Agent.ignore_transaction if defined? NewRelic 43 | [200, heartbeat_headers, [heartbeat_response]] 44 | else 45 | @app.call(env) 46 | end 47 | end 48 | 49 | def heartbeat_path 50 | self.class.heartbeat_path 51 | end 52 | 53 | def heartbeat_response 54 | self.class.heartbeat_response 55 | end 56 | 57 | def heartbeat_headers 58 | self.class.heartbeat_headers 59 | end 60 | 61 | def self.setup 62 | yield self 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/rack/heartbeat/railtie.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | 3 | class Heartbeat::Railtie < Rails::Railtie 4 | initializer "rack.heartbeat.initializer" do |app| 5 | app.middleware.insert 0, Rack::Heartbeat 6 | end 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /rack-heartbeat.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |gem| 4 | gem.authors = ["James Cox"] 5 | gem.email = ["james@imaj.es"] 6 | gem.description = %q{provides a simple endpoint for your rails app for a heartbeat client to connect to} 7 | gem.summary = %q{provides a simple endpoint for your rails app for a heartbeat client to connect to} 8 | gem.homepage = "https://github.com/imajes/rack-heartbeat" 9 | 10 | gem.files = `git ls-files`.split($\) 11 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 12 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 13 | gem.name = "rack-heartbeat" 14 | gem.require_paths = ["lib"] 15 | gem.version = '1.1.0' 16 | 17 | gem.add_development_dependency('minitest') 18 | gem.add_development_dependency('rack-test') 19 | gem.add_runtime_dependency('rack') 20 | end 21 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | Coveralls.wear! 3 | SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter 4 | require 'minitest/autorun' 5 | require 'minitest/pride' 6 | require 'rack-heartbeat' 7 | -------------------------------------------------------------------------------- /test/rack/test_heartbeat.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | require 'base64' 3 | 4 | class Rack::TestHeartbeat < Minitest::Test 5 | def setup 6 | @app = Minitest::Mock.new 7 | @heartbeat = Rack::Heartbeat.new(@app) 8 | end 9 | 10 | def teardown 11 | Rack::Heartbeat.setup do |config| 12 | config.heartbeat_path = 'heartbeat' 13 | config.heartbeat_headers = {'Content-Type' => 'text/plain'} 14 | config.heartbeat_response = 'OK' 15 | end 16 | end 17 | 18 | def test_initialize_sets_default_heartbeat_path 19 | assert_match(/\Aheartbeat\z/, @heartbeat.heartbeat_path) 20 | end 21 | 22 | def test_call_when_env_empty_sends_app_call 23 | env = {} 24 | 25 | @app.expect :call, nil, [env] 26 | @heartbeat.call(env) 27 | @app.verify 28 | end 29 | 30 | def test_call_when_path_info_is_nil_sends_app_call 31 | env = {'PATH_INFO' => nil} 32 | 33 | @app.expect :call, nil, [env] 34 | @heartbeat.call(env) 35 | @app.verify 36 | end 37 | 38 | def test_call_when_path_info_does_not_match_heartbeat_sends_app_call 39 | env = {'PATH_INFO' => 'some-path'} 40 | 41 | @app.expect :call, nil, [env] 42 | @heartbeat.call(env) 43 | @app.verify 44 | end 45 | 46 | def test_call_when_path_info_matches_heartbeat_path_returns_200_response 47 | env = {'PATH_INFO' => '/heartbeat'} 48 | 49 | assert_equal [200, {'Content-Type' => 'text/plain'}, ['OK']], @heartbeat.call(env) 50 | end 51 | 52 | def test_setup_configures_heartbeat_path 53 | expected_path = 'health-check.txt' 54 | expected_headers = {'Content-Type' => 'image/gif'} 55 | expected_response = Base64.decode64('R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==') 56 | env = {'PATH_INFO' => "/#{expected_path}"} 57 | 58 | Rack::Heartbeat.setup do |config| 59 | config.heartbeat_path = expected_path 60 | config.heartbeat_headers = expected_headers 61 | config.heartbeat_response = expected_response 62 | end 63 | 64 | assert_match expected_path, Rack::Heartbeat.heartbeat_path 65 | assert_equal expected_headers, Rack::Heartbeat.heartbeat_headers 66 | assert_equal expected_response, Rack::Heartbeat.heartbeat_response 67 | assert_equal [200, expected_headers, [expected_response]], @heartbeat.call(env) 68 | end 69 | end 70 | --------------------------------------------------------------------------------